Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .jshintrc
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
"maxlen": 80,
"node": true,
"predef": [
"-Promise"
"Promise"
],
"proto": true,
"quotmark": "double",
Expand Down
1 change: 0 additions & 1 deletion examples/general.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
var nodegit = require("../");
var path = require("path");
var Promise = require("nodegit-promise");
var oid;
var odb;
var repo;
Expand Down
1 change: 0 additions & 1 deletion examples/index-add-and-remove.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
var nodegit = require("../");
var path = require("path");
var Promise = require("nodegit-promise");
var promisify = require("promisify-node");
var fse = promisify(require("fs-extra"));

Expand Down
1 change: 0 additions & 1 deletion generate/index.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
var Promise = require('nodegit-promise');
var generateJson = require("./scripts/generateJson");
var generateNativeCode = require("./scripts/generateNativeCode");
var generateMissingTests = require("./scripts/generateMissingTests");
Expand Down
1 change: 0 additions & 1 deletion generate/scripts/generateMissingTests.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
const path = require("path");
const Promise = require("nodegit-promise");
const promisify = require("promisify-node");
const fse = promisify(require("fs-extra"));
const utils = require("./utils");
Expand Down
16 changes: 16 additions & 0 deletions generate/templates/manual/include/async_baton.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
#ifndef ASYNC_BATON
#define ASYNC_BATON

#include <uv.h>
#include <nan.h>

// Base class for Batons used for callbacks (for example,
// JS functions passed as callback parameters,
// or field properties of configuration objects whose values are callbacks)
struct AsyncBaton {
uv_async_t req;

bool done;
};

#endif
46 changes: 46 additions & 0 deletions generate/templates/manual/include/promise_completion.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
#ifndef PROMISE_COMPLETION
#define PROMISE_COMPLETION

#include <nan.h>

#include "async_baton.h"

// PromiseCompletion forwards either the resolved result or the rejection reason
// to the native layer, once the promise completes
//
// inherits ObjectWrap so it can be used in v8 and managed by the garbage collector
// it isn't wired up to be instantiated or accessed from the JS layer other than
// for the purpose of promise result forwarding
class PromiseCompletion : public Nan::ObjectWrap
{
// callback type called when a promise completes
typedef void (*Callback) (bool isFulfilled, AsyncBaton *baton, v8::Local<v8::Value> resultOfPromise);

static NAN_METHOD(New);
static NAN_METHOD(PromiseFulfilled);
static NAN_METHOD(PromiseRejected);

// persistent handles for NAN_METHODs
static Nan::Persistent<v8::Function> newFn;
static Nan::Persistent<v8::Function> promiseFulfilled;
static Nan::Persistent<v8::Function> promiseRejected;

static v8::Local<v8::Value> Bind(Nan::Persistent<v8::Function> &method, v8::Local<v8::Object> object);
static void CallCallback(bool isFulfilled, const Nan::FunctionCallbackInfo<v8::Value> &info);

// callback and baton stored for the promise that this PromiseCompletion is
// attached to. when the promise completes, the callback will be called with
// the result, and the stored baton.
Callback callback;
AsyncBaton *baton;

void Setup(v8::Local<v8::Function> thenFn, v8::Local<v8::Value> result, AsyncBaton *baton, Callback callback);
public:
// If result is a promise, this will instantiate a new PromiseCompletion
// and have it forward the promise result / reason via the baton and callback
static bool ForwardIfPromise(v8::Local<v8::Value> result, AsyncBaton *baton, Callback callback);

static void InitializeComponent();
};

#endif
103 changes: 103 additions & 0 deletions generate/templates/manual/src/promise_completion.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
#include "../include/promise_completion.h"

Nan::Persistent<v8::Function> PromiseCompletion::newFn;
Nan::Persistent<v8::Function> PromiseCompletion::promiseFulfilled;
Nan::Persistent<v8::Function> PromiseCompletion::promiseRejected;

// initializes the persistent handles for NAN_METHODs
void PromiseCompletion::InitializeComponent() {
v8::Local<v8::FunctionTemplate> newTemplate = Nan::New<v8::FunctionTemplate>(New);
newTemplate->InstanceTemplate()->SetInternalFieldCount(1);
newFn.Reset(newTemplate->GetFunction());

promiseFulfilled.Reset(Nan::New<v8::FunctionTemplate>(PromiseFulfilled)->GetFunction());
promiseRejected.Reset(Nan::New<v8::FunctionTemplate>(PromiseRejected)->GetFunction());
}

bool PromiseCompletion::ForwardIfPromise(v8::Local<v8::Value> result, AsyncBaton *baton, Callback callback)
{
Nan::HandleScope scope;

// check if the result is a promise
if (result->IsObject()) {
Nan::MaybeLocal<v8::Value> maybeThenProp = Nan::Get(result->ToObject(), Nan::New("then").ToLocalChecked());
if (!maybeThenProp.IsEmpty()) {
v8::Local<v8::Value> thenProp = maybeThenProp.ToLocalChecked();
if(thenProp->IsFunction()) {
// we can be reasonably certain that the result is a promise

// create a new v8 instance of PromiseCompletion
v8::Local<v8::Object> object = Nan::NewInstance(Nan::New(newFn)).ToLocalChecked();

// set up the native PromiseCompletion object
PromiseCompletion *promiseCompletion = ObjectWrap::Unwrap<PromiseCompletion>(object);
promiseCompletion->Setup(thenProp.As<v8::Function>(), result, baton, callback);

return true;
}
}
}

return false;
}

// creates a new instance of PromiseCompletion, wrapped in a v8 object
NAN_METHOD(PromiseCompletion::New) {
PromiseCompletion *promiseCompletion = new PromiseCompletion();
promiseCompletion->Wrap(info.This());
info.GetReturnValue().Set(info.This());
}

// sets up a Promise to forward the promise result via the baton and callback
void PromiseCompletion::Setup(v8::Local<v8::Function> thenFn, v8::Local<v8::Value> result, AsyncBaton *baton, Callback callback) {
this->callback = callback;
this->baton = baton;

v8::Local<v8::Object> promise = result->ToObject();

v8::Local<v8::Object> thisHandle = handle();

v8::Local<v8::Value> argv[2] = {
Bind(promiseFulfilled, thisHandle),

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel like these should bind to result and not thisHandle. They are instance properties of result and we're rebinding the context of them.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

promiseFulfilled and promiseRejected are instance properties of PromiseCompletion objects - https://github.com/srajko/nodegit/blob/8dae154aa00b5c23f482b746ba4efcfb524a812d/generate/templates/manual/src/promise_completion.cc#L13-L14 https://github.com/srajko/nodegit/blob/8dae154aa00b5c23f482b746ba4efcfb524a812d/generate/templates/manual/src/promise_completion.cc#L97-L103

We need to bind thisHandle to them so that they can access the PromiseCompletion instance here - https://github.com/srajko/nodegit/blob/8dae154aa00b5c23f482b746ba4efcfb524a812d/generate/templates/manual/src/promise_completion.cc#L92-L94

There might be a better way to do the binding though - using the JS Function.bind method was the best I could come up with, short of leaking memory by creating a function template for each instance of PromiseCompletion :-)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So I was mistaken about what was going on. I thought this was related to how the Promise.resolve functions passed in now were being bound to themselves in JS. Apparently that is a requirement of Chromium and is Promise library implementation specific. The actual then library does not have this requirement and that was the backing lib for nodegit-promise which is why we didn't need to bind the functions being passed in during tests. With that resolved I'm good with this PR.

Bind(promiseRejected, thisHandle)
};

// call the promise's .then method with resolve and reject callbacks
Nan::Callback(thenFn).Call(promise, 2, argv);
}

// binds an object to be the context of the function.
// there might be a better way to do this than calling Function.bind...
v8::Local<v8::Value> PromiseCompletion::Bind(Nan::Persistent<v8::Function> &function, v8::Local<v8::Object> object) {
Nan::EscapableHandleScope scope;

v8::Local<v8::Function> bind =
Nan::Get(Nan::New(function), Nan::New("bind").ToLocalChecked())
.ToLocalChecked().As<v8::Function>();

v8::Local<v8::Value> argv[1] = { object };

return scope.Escape(bind->Call(Nan::New(function), 1, argv));
}

// calls the callback stored in the PromiseCompletion, passing the baton that
// was provided in construction
void PromiseCompletion::CallCallback(bool isFulfilled, const Nan::FunctionCallbackInfo<v8::Value> &info) {
v8::Local<v8::Value> resultOfPromise;

if (info.Length() > 0) {
resultOfPromise = info[0];
}

PromiseCompletion *promiseCompletion = ObjectWrap::Unwrap<PromiseCompletion>(info.This()->ToObject());

(*promiseCompletion->callback)(isFulfilled, promiseCompletion->baton, resultOfPromise);
}

NAN_METHOD(PromiseCompletion::PromiseFulfilled) {
CallCallback(true, info);
}

NAN_METHOD(PromiseCompletion::PromiseRejected) {
CallCallback(false, info);
}
51 changes: 8 additions & 43 deletions generate/templates/partials/callback_helpers.cc
Original file line number Diff line number Diff line change
Expand Up @@ -77,18 +77,10 @@ void {{ cppClassName }}::{{ cppFunctionName }}_{{ cbFunction.name }}_async(uv_as
Nan::TryCatch tryCatch;
Local<v8::Value> result = callback->Call({{ cbFunction.args|jsArgsCount }}, argv);

if (result->IsObject() && Nan::Has(result->ToObject(), Nan::New("then").ToLocalChecked()).FromJust()) {
Local<v8::Value> thenProp = Nan::Get(result->ToObject(), Nan::New("then").ToLocalChecked()).ToLocalChecked();

if (thenProp->IsFunction()) {
// we can be reasonbly certain that the result is a promise
Local<Object> promise = result->ToObject();

baton->promise.Reset(promise);
uv_close((uv_handle_t*) &baton->req, NULL);

uv_close((uv_handle_t*) &baton->req, (uv_close_cb) {{ cppFunctionName}}_{{ cbFunction.name }}_setupAsyncPromisePolling);
return;
}
if(PromiseCompletion::ForwardIfPromise(result, baton, {{ cppFunctionName }}_{{ cbFunction.name }}_promiseCompleted)) {
return;
}

{% each cbFunction|returnsInfo false true as _return %}
Expand Down Expand Up @@ -117,36 +109,14 @@ void {{ cppClassName }}::{{ cppFunctionName }}_{{ cbFunction.name }}_async(uv_as
{% endeach %}

baton->done = true;
uv_close((uv_handle_t*) &baton->req, NULL);
}

void {{ cppClassName }}::{{ cppFunctionName }}_{{ cbFunction.name }}_setupAsyncPromisePolling(uv_async_t* req) {
{{ cppFunctionName }}_{{ cbFunction.name|titleCase }}Baton* baton = static_cast<{{ cppFunctionName }}_{{ cbFunction.name|titleCase }}Baton*>(req->data);
uv_async_init(uv_default_loop(), &baton->req, (uv_async_cb) {{ cppFunctionName }}_{{ cbFunction.name }}_asyncPromisePolling);
uv_async_send(&baton->req);
}

void {{ cppClassName }}::{{ cppFunctionName }}_{{ cbFunction.name }}_asyncPromisePolling(uv_async_t* req, int status) {
void {{ cppClassName }}::{{ cppFunctionName }}_{{ cbFunction.name }}_promiseCompleted(bool isFulfilled, AsyncBaton *_baton, v8::Local<v8::Value> result) {
Nan::HandleScope scope;

{{ cppFunctionName }}_{{ cbFunction.name|titleCase }}Baton* baton = static_cast<{{ cppFunctionName }}_{{ cbFunction.name|titleCase }}Baton*>(req->data);
Local<Object> promise = Nan::New<Object>(baton->promise);
Nan::Callback* isPendingFn = new Nan::Callback(Nan::Get(promise, Nan::New("isPending").ToLocalChecked()).ToLocalChecked().As<Function>());
Local<Value> argv[1]; // MSBUILD won't assign an array of length 0
Local<Boolean> isPending = isPendingFn->Call(promise, 0, argv)->ToBoolean();

if (isPending->Value()) {
uv_async_send(&baton->req);
return;
}

Nan::Callback* isFulfilledFn = new Nan::Callback(Nan::Get(promise, Nan::New("isFulfilled").ToLocalChecked()).ToLocalChecked().As<Function>());
Local<Boolean> isFulfilled = isFulfilledFn->Call(promise, 0, argv)->ToBoolean();

if (isFulfilled->Value()) {
Nan::Callback* resultFn = new Nan::Callback(Nan::Get(promise, Nan::New("value").ToLocalChecked()).ToLocalChecked().As<Function>());
Local<v8::Value> result = resultFn->Call(promise, 0, argv);
{{ cppFunctionName }}_{{ cbFunction.name|titleCase }}Baton* baton = static_cast<{{ cppFunctionName }}_{{ cbFunction.name|titleCase }}Baton*>(_baton);

if (isFulfilled) {
{% each cbFunction|returnsInfo false true as _return %}
if (result.IsEmpty() || result->IsNativeError()) {
baton->result = {{ cbFunction.return.error }};
Expand All @@ -171,23 +141,18 @@ void {{ cppClassName }}::{{ cppFunctionName }}_{{ cbFunction.name }}_asyncPromis
baton->result = {{ cbFunction.return.noResults }};
}
{% endeach %}
baton->done = true;
}
else {
// promise was rejected
{{ cppClassName }}* instance = static_cast<{{ cppClassName }}*>(baton->{% each cbFunction.args|argsInfo as arg %}
{% if arg.payload == true %}{{arg.name}}{% elsif arg.lastArg %}{{arg.name}}{% endif %}
{% endeach %});
Local<v8::Object> parent = instance->handle();
Nan::Callback* reasonFn = new Nan::Callback(Nan::Get(promise, Nan::New("reason").ToLocalChecked()).ToLocalChecked().As<Function>());
Local<v8::Value> reason = reasonFn->Call(promise, 0, argv);
parent->SetHiddenValue(Nan::New("NodeGitPromiseError").ToLocalChecked(), reason);
parent->SetHiddenValue(Nan::New("NodeGitPromiseError").ToLocalChecked(), result);

baton->result = {{ cbFunction.return.error }};
baton->done = true;
}

uv_close((uv_handle_t*) &baton->req, NULL);
baton->done = true;
}
{%endif%}
{%endeach%}
51 changes: 8 additions & 43 deletions generate/templates/partials/field_accessors.cc
Original file line number Diff line number Diff line change
Expand Up @@ -171,18 +171,10 @@
Nan::TryCatch tryCatch;
Local<v8::Value> result = instance->{{ field.name }}->Call({{ field.args|jsArgsCount }}, argv);

if (result->IsObject() && Nan::Has(result->ToObject(), Nan::New("then").ToLocalChecked()).FromJust()) {
Local<v8::Value> thenProp = Nan::Get(result->ToObject(), Nan::New("then").ToLocalChecked()).ToLocalChecked();

if (thenProp->IsFunction()) {
// we can be reasonbly certain that the result is a promise
Local<Object> promise = result->ToObject();

baton->promise.Reset(promise);
uv_close((uv_handle_t*) &baton->req, NULL);

uv_close((uv_handle_t*) &baton->req, (uv_close_cb) {{ field.name }}_setupAsyncPromisePolling);
return;
}
if(PromiseCompletion::ForwardIfPromise(result, baton, {{ cppClassName }}::{{ field.name }}_promiseCompleted)) {
return;
}

{% each field|returnsInfo false true as _return %}
Expand Down Expand Up @@ -210,36 +202,14 @@
}
{% endeach %}
baton->done = true;
uv_close((uv_handle_t*) &baton->req, NULL);
}
void {{ cppClassName }}::{{ field.name }}_setupAsyncPromisePolling(uv_async_t* req) {
{{ field.name|titleCase }}Baton* baton = static_cast<{{ field.name|titleCase }}Baton*>(req->data);
uv_async_init(uv_default_loop(), &baton->req, (uv_async_cb) {{ field.name }}_asyncPromisePolling);
uv_async_send(&baton->req);
}

void {{ cppClassName }}::{{ field.name }}_asyncPromisePolling(uv_async_t* req, int status) {
void {{ cppClassName }}::{{ field.name }}_promiseCompleted(bool isFulfilled, AsyncBaton *_baton, v8::Local<v8::Value> result) {
Nan::HandleScope scope;

{{ field.name|titleCase }}Baton* baton = static_cast<{{ field.name|titleCase }}Baton*>(req->data);
Local<Object> promise = Nan::New<Object>(baton->promise);

Nan::Callback* isPendingFn = new Nan::Callback(Nan::Get(promise, Nan::New("isPending").ToLocalChecked()).ToLocalChecked().As<Function>());
Local<Value> argv[1]; // MSBUILD won't assign an array of length 0
Local<Boolean> isPending = isPendingFn->Call(promise, 0, argv)->ToBoolean();

if (isPending->Value()) {
uv_async_send(&baton->req);
return;
}

Nan::Callback* isFulfilledFn = new Nan::Callback(Nan::Get(promise, Nan::New("isFulfilled").ToLocalChecked()).ToLocalChecked().As<Function>());
Local<Boolean> isFulfilled = isFulfilledFn->Call(promise, 0, argv)->ToBoolean();

if (isFulfilled->Value()) {
Nan::Callback* resultFn = new Nan::Callback(Nan::Get(promise, Nan::New("value").ToLocalChecked()).ToLocalChecked().As<Function>());
Local<v8::Value> result = resultFn->Call(promise, 0, argv);
{{ field.name|titleCase }}Baton* baton = static_cast<{{ field.name|titleCase }}Baton*>(_baton);

if (isFulfilled) {
{% each field|returnsInfo false true as _return %}
if (result.IsEmpty() || result->IsNativeError()) {
baton->result = {{ field.return.error }};
Expand All @@ -264,23 +234,18 @@
baton->result = {{ field.return.noResults }};
}
{% endeach %}
baton->done = true;
}
else {
// promise was rejected
{{ cppClassName }}* instance = static_cast<{{ cppClassName }}*>(baton->{% each field.args|argsInfo as arg %}
{% if arg.payload == true %}{{arg.name}}{% elsif arg.lastArg %}{{arg.name}}{% endif %}
{% endeach %});
Local<v8::Object> parent = instance->handle();
Nan::Callback* reasonFn = new Nan::Callback(Nan::Get(promise, Nan::New("reason").ToLocalChecked()).ToLocalChecked().As<Function>());
Local<v8::Value> reason = reasonFn->Call(promise, 0, argv);
parent->SetHiddenValue(Nan::New("NodeGitPromiseError").ToLocalChecked(), reason);
parent->SetHiddenValue(Nan::New("NodeGitPromiseError").ToLocalChecked(), result);

baton->result = {{ field.return.error }};
baton->done = true;
}

uv_close((uv_handle_t*) &baton->req, NULL);
baton->done = true;
}
{% endif %}
{% endif %}
Expand Down
1 change: 1 addition & 0 deletions generate/templates/templates/binding.gyp
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
"sources": [
"src/lock_master.cc",
"src/nodegit.cc",
"src/promise_completion.cc",
"src/wrapper.cc",
"src/functions/copy.cc",
"src/functions/sleep_for_ms.cc",
Expand Down
Loading