Skip to content

Commit b1fb458

Browse files
committed
Add PromiseCompletion class to replace promise polling
1 parent 706e0f4 commit b1fb458

7 files changed

Lines changed: 134 additions & 90 deletions

File tree

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
#ifndef PROMISE_COMPLETION
2+
#define PROMISE_COMPLETION
3+
4+
#include <nan.h>
5+
6+
#include "async_baton.h"
7+
8+
// PromiseCompletion forwards either the resolved result or the rejection reasonbly
9+
// to the native layer, once the promise completes
10+
//
11+
// inherits ObjectWrap so it can be used in v8 and managed by the garbage collector
12+
class PromiseCompletion : public Nan::ObjectWrap
13+
{
14+
// callback type called when a promise completes
15+
typedef void (*Callback) (bool isFulfilled, AsyncBaton *baton, v8::Local<v8::Value> resultOfPromise);
16+
17+
Nan::Persistent<v8::Function> promiseFulfilled;
18+
Nan::Persistent<v8::Function> promiseRejected;
19+
20+
Callback callback;
21+
AsyncBaton *baton;
22+
23+
static void CallCallback(bool isFulfilled, const Nan::FunctionCallbackInfo<v8::Value> &info);
24+
25+
static NAN_METHOD(PromiseFulfilled);
26+
static NAN_METHOD(PromiseRejected);
27+
static NAN_METHOD(New);
28+
29+
void Setup(v8::Local<v8::Value> thenProp, v8::Local<v8::Value> result, AsyncBaton *baton, Callback callback);
30+
public:
31+
// If result is a promise, this will instantiate a new PromiseCompletion
32+
// and have it forward the promise result / reason via the baton and callback
33+
static bool ForwardIfPromise(v8::Local<v8::Value> result, AsyncBaton *baton, Callback callback);
34+
};
35+
36+
#endif
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
#include "../include/promise_completion.h"
2+
3+
bool PromiseCompletion::ForwardIfPromise(v8::Local<v8::Value> result, AsyncBaton *baton, Callback callback)
4+
{
5+
Nan::HandleScope scope;
6+
7+
// check if the result is a promise
8+
if (result->IsObject() && Nan::Has(result->ToObject(), Nan::New("then").ToLocalChecked()).FromJust()) {
9+
v8::Local<v8::Value> thenProp = Nan::Get(result->ToObject(), Nan::New("then").ToLocalChecked()).ToLocalChecked();
10+
11+
if (thenProp->IsFunction()) {
12+
// we can be reasonbly certain that the result is a promise
13+
14+
// create a new v8 instance of PromiseCompletion
15+
v8::Local<v8::FunctionTemplate> newTemplate = Nan::New<v8::FunctionTemplate>(New);
16+
newTemplate->InstanceTemplate()->SetInternalFieldCount(1);
17+
v8::Local<v8::Object> object = Nan::NewInstance(newTemplate->GetFunction()).ToLocalChecked();
18+
19+
// set up the native promiseCompletion object
20+
PromiseCompletion *promiseCompletion = ObjectWrap::Unwrap<PromiseCompletion>(object);
21+
promiseCompletion->Setup(thenProp, result, baton, callback);
22+
23+
return true;
24+
}
25+
}
26+
27+
return false;
28+
}
29+
30+
// creates a new instance of PromiseCompletion, wrapped in a v8 object
31+
NAN_METHOD(PromiseCompletion::New) {
32+
PromiseCompletion *promiseCompletion = new PromiseCompletion();
33+
promiseCompletion->Wrap(info.This());
34+
info.GetReturnValue().Set(info.This());
35+
}
36+
37+
// sets up a Promise to forward the promise result via the baton and callback
38+
void PromiseCompletion::Setup(v8::Local<v8::Value> thenProp, v8::Local<v8::Value> result, AsyncBaton *baton, Callback callback) {
39+
v8::Local<v8::Object> promise = result->ToObject();
40+
41+
baton->promise.Reset(promise);
42+
43+
v8::Local<v8::Object> thisHandle = handle();
44+
45+
v8::Local<v8::Value> argv[2] = {
46+
Nan::New<v8::FunctionTemplate>(PromiseFulfilled, thisHandle)->GetFunction(),
47+
Nan::New<v8::FunctionTemplate>(PromiseRejected, thisHandle)->GetFunction()
48+
};
49+
50+
this->callback = callback;
51+
this->baton = baton;
52+
53+
// call the promise's .then method with resolve and reject callbacks
54+
Nan::Callback* thenFn = new Nan::Callback(thenProp.As<v8::Function>());
55+
thenFn->Call(promise, 2, argv);
56+
}
57+
58+
// calls the callback stored in the PromiseCompletion, passing the baton that
59+
// was provided in construction
60+
void PromiseCompletion::CallCallback(bool isFulfilled, const Nan::FunctionCallbackInfo<v8::Value> &info) {
61+
v8::Local<v8::Value> resultOfPromise;
62+
63+
if (info.Length() > 0) {
64+
resultOfPromise = info[0];
65+
}
66+
67+
PromiseCompletion *promiseCompletion = ObjectWrap::Unwrap<PromiseCompletion>(info.Data()->ToObject());
68+
69+
(*promiseCompletion->callback)(isFulfilled, promiseCompletion->baton, resultOfPromise);
70+
}
71+
72+
NAN_METHOD(PromiseCompletion::PromiseFulfilled) {
73+
CallCallback(true, info);
74+
}
75+
76+
NAN_METHOD(PromiseCompletion::PromiseRejected) {
77+
CallCallback(false, info);
78+
}

‎generate/templates/partials/callback_helpers.cc‎

Lines changed: 8 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -73,18 +73,10 @@ void {{ cppClassName }}::{{ cppFunctionName }}_{{ cbFunction.name }}_async(uv_as
7373
Nan::TryCatch tryCatch;
7474
Local<v8::Value> result = callback->Call({{ cbFunction.args|jsArgsCount }}, argv);
7575

76-
if (result->IsObject() && Nan::Has(result->ToObject(), Nan::New("then").ToLocalChecked()).FromJust()) {
77-
Local<v8::Value> thenProp = Nan::Get(result->ToObject(), Nan::New("then").ToLocalChecked()).ToLocalChecked();
78-
79-
if (thenProp->IsFunction()) {
80-
// we can be reasonbly certain that the result is a promise
81-
Local<Object> promise = result->ToObject();
82-
83-
baton->promise.Reset(promise);
76+
uv_close((uv_handle_t*) &baton->req, NULL);
8477

85-
uv_close((uv_handle_t*) &baton->req, (uv_close_cb) {{ cppFunctionName}}_{{ cbFunction.name }}_setupAsyncPromisePolling);
86-
return;
87-
}
78+
if(PromiseCompletion::ForwardIfPromise(result, baton, {{ cppFunctionName }}_{{ cbFunction.name }}_promiseCompleted)) {
79+
return;
8880
}
8981

9082
{% each cbFunction|returnsInfo false true as _return %}
@@ -113,36 +105,14 @@ void {{ cppClassName }}::{{ cppFunctionName }}_{{ cbFunction.name }}_async(uv_as
113105
{% endeach %}
114106

115107
baton->done = true;
116-
uv_close((uv_handle_t*) &baton->req, NULL);
117-
}
118-
119-
void {{ cppClassName }}::{{ cppFunctionName }}_{{ cbFunction.name }}_setupAsyncPromisePolling(uv_async_t* req) {
120-
{{ cppFunctionName }}_{{ cbFunction.name|titleCase }}Baton* baton = static_cast<{{ cppFunctionName }}_{{ cbFunction.name|titleCase }}Baton*>(req->data);
121-
uv_async_init(uv_default_loop(), &baton->req, (uv_async_cb) {{ cppFunctionName }}_{{ cbFunction.name }}_asyncPromisePolling);
122-
uv_async_send(&baton->req);
123108
}
124109

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

128-
{{ cppFunctionName }}_{{ cbFunction.name|titleCase }}Baton* baton = static_cast<{{ cppFunctionName }}_{{ cbFunction.name|titleCase }}Baton*>(req->data);
129-
Local<Object> promise = Nan::New<Object>(baton->promise);
130-
Nan::Callback* isPendingFn = new Nan::Callback(Nan::Get(promise, Nan::New("isPending").ToLocalChecked()).ToLocalChecked().As<Function>());
131-
Local<Value> argv[1]; // MSBUILD won't assign an array of length 0
132-
Local<Boolean> isPending = isPendingFn->Call(promise, 0, argv)->ToBoolean();
133-
134-
if (isPending->Value()) {
135-
uv_async_send(&baton->req);
136-
return;
137-
}
138-
139-
Nan::Callback* isFulfilledFn = new Nan::Callback(Nan::Get(promise, Nan::New("isFulfilled").ToLocalChecked()).ToLocalChecked().As<Function>());
140-
Local<Boolean> isFulfilled = isFulfilledFn->Call(promise, 0, argv)->ToBoolean();
141-
142-
if (isFulfilled->Value()) {
143-
Nan::Callback* resultFn = new Nan::Callback(Nan::Get(promise, Nan::New("value").ToLocalChecked()).ToLocalChecked().As<Function>());
144-
Local<v8::Value> result = resultFn->Call(promise, 0, argv);
113+
{{ cppFunctionName }}_{{ cbFunction.name|titleCase }}Baton* baton = static_cast<{{ cppFunctionName }}_{{ cbFunction.name|titleCase }}Baton*>(_baton);
145114

115+
if (isFulfilled) {
146116
{% each cbFunction|returnsInfo false true as _return %}
147117
if (result.IsEmpty() || result->IsNativeError()) {
148118
baton->result = {{ cbFunction.return.error }};
@@ -167,23 +137,18 @@ void {{ cppClassName }}::{{ cppFunctionName }}_{{ cbFunction.name }}_asyncPromis
167137
baton->result = {{ cbFunction.return.noResults }};
168138
}
169139
{% endeach %}
170-
baton->done = true;
171140
}
172141
else {
173142
// promise was rejected
174143
{{ cppClassName }}* instance = static_cast<{{ cppClassName }}*>(baton->{% each cbFunction.args|argsInfo as arg %}
175144
{% if arg.payload == true %}{{arg.name}}{% elsif arg.lastArg %}{{arg.name}}{% endif %}
176145
{% endeach %});
177146
Local<v8::Object> parent = instance->handle();
178-
Nan::Callback* reasonFn = new Nan::Callback(Nan::Get(promise, Nan::New("reason").ToLocalChecked()).ToLocalChecked().As<Function>());
179-
Local<v8::Value> reason = reasonFn->Call(promise, 0, argv);
180-
parent->SetHiddenValue(Nan::New("NodeGitPromiseError").ToLocalChecked(), reason);
147+
parent->SetHiddenValue(Nan::New("NodeGitPromiseError").ToLocalChecked(), result);
181148

182149
baton->result = {{ cbFunction.return.error }};
183-
baton->done = true;
184150
}
185-
186-
uv_close((uv_handle_t*) &baton->req, NULL);
151+
baton->done = true;
187152
}
188153
{%endif%}
189154
{%endeach%}

‎generate/templates/partials/field_accessors.cc‎

Lines changed: 8 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -167,18 +167,10 @@
167167
Nan::TryCatch tryCatch;
168168
Local<v8::Value> result = instance->{{ field.name }}->Call({{ field.args|jsArgsCount }}, argv);
169169

170-
if (result->IsObject() && Nan::Has(result->ToObject(), Nan::New("then").ToLocalChecked()).FromJust()) {
171-
Local<v8::Value> thenProp = Nan::Get(result->ToObject(), Nan::New("then").ToLocalChecked()).ToLocalChecked();
172-
173-
if (thenProp->IsFunction()) {
174-
// we can be reasonbly certain that the result is a promise
175-
Local<Object> promise = result->ToObject();
176-
177-
baton->promise.Reset(promise);
170+
uv_close((uv_handle_t*) &baton->req, NULL);
178171

179-
uv_close((uv_handle_t*) &baton->req, (uv_close_cb) {{ field.name }}_setupAsyncPromisePolling);
180-
return;
181-
}
172+
if(PromiseCompletion::ForwardIfPromise(result, baton, {{ cppClassName }}::{{ field.name }}_promiseCompleted)) {
173+
return;
182174
}
183175

184176
{% each field|returnsInfo false true as _return %}
@@ -206,36 +198,14 @@
206198
}
207199
{% endeach %}
208200
baton->done = true;
209-
uv_close((uv_handle_t*) &baton->req, NULL);
210-
}
211-
void {{ cppClassName }}::{{ field.name }}_setupAsyncPromisePolling(uv_async_t* req) {
212-
{{ field.name|titleCase }}Baton* baton = static_cast<{{ field.name|titleCase }}Baton*>(req->data);
213-
uv_async_init(uv_default_loop(), &baton->req, (uv_async_cb) {{ field.name }}_asyncPromisePolling);
214-
uv_async_send(&baton->req);
215201
}
216202

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

220-
{{ field.name|titleCase }}Baton* baton = static_cast<{{ field.name|titleCase }}Baton*>(req->data);
221-
Local<Object> promise = Nan::New<Object>(baton->promise);
222-
223-
Nan::Callback* isPendingFn = new Nan::Callback(Nan::Get(promise, Nan::New("isPending").ToLocalChecked()).ToLocalChecked().As<Function>());
224-
Local<Value> argv[1]; // MSBUILD won't assign an array of length 0
225-
Local<Boolean> isPending = isPendingFn->Call(promise, 0, argv)->ToBoolean();
226-
227-
if (isPending->Value()) {
228-
uv_async_send(&baton->req);
229-
return;
230-
}
231-
232-
Nan::Callback* isFulfilledFn = new Nan::Callback(Nan::Get(promise, Nan::New("isFulfilled").ToLocalChecked()).ToLocalChecked().As<Function>());
233-
Local<Boolean> isFulfilled = isFulfilledFn->Call(promise, 0, argv)->ToBoolean();
234-
235-
if (isFulfilled->Value()) {
236-
Nan::Callback* resultFn = new Nan::Callback(Nan::Get(promise, Nan::New("value").ToLocalChecked()).ToLocalChecked().As<Function>());
237-
Local<v8::Value> result = resultFn->Call(promise, 0, argv);
206+
{{ field.name|titleCase }}Baton* baton = static_cast<{{ field.name|titleCase }}Baton*>(_baton);
238207

208+
if (isFulfilled) {
239209
{% each field|returnsInfo false true as _return %}
240210
if (result.IsEmpty() || result->IsNativeError()) {
241211
baton->result = {{ field.return.error }};
@@ -260,23 +230,18 @@
260230
baton->result = {{ field.return.noResults }};
261231
}
262232
{% endeach %}
263-
baton->done = true;
264233
}
265234
else {
266235
// promise was rejected
267236
{{ cppClassName }}* instance = static_cast<{{ cppClassName }}*>(baton->{% each field.args|argsInfo as arg %}
268237
{% if arg.payload == true %}{{arg.name}}{% elsif arg.lastArg %}{{arg.name}}{% endif %}
269238
{% endeach %});
270239
Local<v8::Object> parent = instance->handle();
271-
Nan::Callback* reasonFn = new Nan::Callback(Nan::Get(promise, Nan::New("reason").ToLocalChecked()).ToLocalChecked().As<Function>());
272-
Local<v8::Value> reason = reasonFn->Call(promise, 0, argv);
273-
parent->SetHiddenValue(Nan::New("NodeGitPromiseError").ToLocalChecked(), reason);
240+
parent->SetHiddenValue(Nan::New("NodeGitPromiseError").ToLocalChecked(), result);
274241

275242
baton->result = {{ field.return.error }};
276-
baton->done = true;
277243
}
278-
279-
uv_close((uv_handle_t*) &baton->req, NULL);
244+
baton->done = true;
280245
}
281246
{% endif %}
282247
{% endif %}

‎generate/templates/templates/binding.gyp‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515

1616
"sources": [
1717
"src/nodegit.cc",
18+
"src/promise_completion.cc",
1819
"src/wrapper.cc",
1920
"src/functions/copy.cc",
2021
"src/functions/sleep_for_ms.cc",

‎generate/templates/templates/class_header.h‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
#include <queue>
77

88
#include "async_baton.h"
9+
#include "promise_completion.h"
910

1011
extern "C" {
1112
#include <git2.h>
@@ -63,8 +64,7 @@ class {{ cppClassName }} : public Nan::ObjectWrap {
6364
);
6465

6566
static void {{ function.cppFunctionName }}_{{ arg.name }}_async(uv_async_t* req, int status);
66-
static void {{ function.cppFunctionName }}_{{ arg.name }}_setupAsyncPromisePolling(uv_async_t* req);
67-
static void {{ function.cppFunctionName }}_{{ arg.name }}_asyncPromisePolling(uv_async_t* req, int status);
67+
static void {{ function.cppFunctionName }}_{{ arg.name }}_promiseCompleted(bool isFulfilled, AsyncBaton *_baton, v8::Local<v8::Value> result);
6868
struct {{ function.cppFunctionName }}_{{ arg.name|titleCase }}Baton : AsyncBaton {
6969
{% each arg.args|argsInfo as cbArg %}
7070
{{ cbArg.cType }} {{ cbArg.name }};

‎generate/templates/templates/struct_header.h‎

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,8 +48,7 @@ class {{ cppClassName }} : public Nan::ObjectWrap {
4848
);
4949

5050
static void {{ field.name }}_async(uv_async_t* req, int status);
51-
static void {{ field.name }}_setupAsyncPromisePolling(uv_async_t* req);
52-
static void {{ field.name }}_asyncPromisePolling(uv_async_t* req, int status);
51+
static void {{ field.name }}_promiseCompleted(bool isFulfilled, AsyncBaton *_baton, v8::Local<v8::Value> result);
5352
struct {{ field.name|titleCase }}Baton : public AsyncBaton {
5453
{% each field.args|argsInfo as arg %}
5554
{{ arg.cType }} {{ arg.name}};

0 commit comments

Comments
 (0)