Skip to content

Commit 8ffea15

Browse files
rolftimmermansmhdawson
authored andcommitted
Implement Promises.
PR-URL: #137 Reviewed-By: Anna Henningsen <[email protected]> Reviewed-By: Michael Dawson <[email protected]> Reviewed-By: Jason Ginchereau <[email protected]>
1 parent 148ecfa commit 8ffea15

File tree

8 files changed

+185
-0
lines changed

8 files changed

+185
-0
lines changed

napi-inl.h

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -336,6 +336,17 @@ inline bool Value::IsFunction() const {
336336
return Type() == napi_function;
337337
}
338338

339+
inline bool Value::IsPromise() const {
340+
if (_value == nullptr) {
341+
return false;
342+
}
343+
344+
bool result;
345+
napi_status status = napi_is_promise(_env, _value, &result);
346+
NAPI_THROW_IF_FAILED(_env, status, false);
347+
return result;
348+
}
349+
339350
inline bool Value::IsBuffer() const {
340351
if (_value == nullptr) {
341352
return false;
@@ -1379,6 +1390,36 @@ inline Object Function::New(size_t argc, const napi_value* args) const {
13791390
return Object(_env, result);
13801391
}
13811392

1393+
////////////////////////////////////////////////////////////////////////////////
1394+
// Promise class
1395+
////////////////////////////////////////////////////////////////////////////////
1396+
1397+
inline Promise::Deferred Promise::Deferred::New(napi_env env) {
1398+
return Promise::Deferred(env);
1399+
}
1400+
1401+
inline Promise::Deferred::Deferred(napi_env env) : _env(env) {
1402+
napi_status status = napi_create_promise(_env, &_deferred, &_promise);
1403+
NAPI_THROW_IF_FAILED(_env, status);
1404+
}
1405+
1406+
inline Promise Promise::Deferred::Promise() const {
1407+
return Napi::Promise(_env, _promise);
1408+
}
1409+
1410+
inline void Promise::Deferred::Resolve(napi_value value) const {
1411+
napi_status status = napi_resolve_deferred(_env, _deferred, value);
1412+
NAPI_THROW_IF_FAILED(_env, status);
1413+
}
1414+
1415+
inline void Promise::Deferred::Reject(napi_value value) const {
1416+
napi_status status = napi_reject_deferred(_env, _deferred, value);
1417+
NAPI_THROW_IF_FAILED(_env, status);
1418+
}
1419+
1420+
inline Promise::Promise(napi_env env, napi_value value) : Object(env, value) {
1421+
}
1422+
13821423
////////////////////////////////////////////////////////////////////////////////
13831424
// Buffer<T> class
13841425
////////////////////////////////////////////////////////////////////////////////

napi.h

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,7 @@ namespace Napi {
176176
bool IsTypedArray() const; ///< Tests if a value is a JavaScript typed array.
177177
bool IsObject() const; ///< Tests if a value is a JavaScript object.
178178
bool IsFunction() const; ///< Tests if a value is a JavaScript function.
179+
bool IsPromise() const; ///< Tests if a value is a JavaScript promise.
179180
bool IsBuffer() const; ///< Tests if a value is a Node buffer.
180181

181182
/// Casts to another type of `Napi::Value`, when the actual type is known or assumed.
@@ -792,6 +793,27 @@ namespace Napi {
792793
Object New(size_t argc, const napi_value* args) const;
793794
};
794795

796+
class Promise : public Object {
797+
public:
798+
class Deferred {
799+
public:
800+
static Deferred New(napi_env env);
801+
Deferred(napi_env env);
802+
803+
Napi::Promise Promise() const;
804+
805+
void Resolve(napi_value value) const;
806+
void Reject(napi_value value) const;
807+
808+
private:
809+
napi_env _env;
810+
napi_deferred _deferred;
811+
napi_value _promise;
812+
};
813+
814+
Promise(napi_env env, napi_value value);
815+
};
816+
795817
template <typename T>
796818
class Buffer : public Object {
797819
public:

test/binding.cc

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ Object InitExternal(Env env);
1010
Object InitFunction(Env env);
1111
Object InitName(Env env);
1212
Object InitObject(Env env);
13+
Object InitPromise(Env env);
1314
Object InitTypedArray(Env env);
1415
Object InitObjectWrap(Env env);
1516

@@ -22,6 +23,7 @@ Object Init(Env env, Object exports) {
2223
exports.Set("function", InitFunction(env));
2324
exports.Set("name", InitName(env));
2425
exports.Set("object", InitObject(env));
26+
exports.Set("promise", InitPromise(env));
2527
exports.Set("typedarray", InitTypedArray(env));
2628
exports.Set("objectwrap", InitObjectWrap(env));
2729
return exports;

test/binding.gyp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
'function.cc',
1111
'name.cc',
1212
'object.cc',
13+
'promise.cc',
1314
'typedarray.cc',
1415
'objectwrap.cc',
1516
],

test/common/index.js

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
/* Test helpers ported from test/common/index.js in Node.js project. */
2+
'use strict';
3+
const assert = require('assert');
4+
5+
const noop = () => {};
6+
7+
const mustCallChecks = [];
8+
9+
function runCallChecks(exitCode) {
10+
if (exitCode !== 0) return;
11+
12+
const failed = mustCallChecks.filter(function(context) {
13+
if ('minimum' in context) {
14+
context.messageSegment = `at least ${context.minimum}`;
15+
return context.actual < context.minimum;
16+
} else {
17+
context.messageSegment = `exactly ${context.exact}`;
18+
return context.actual !== context.exact;
19+
}
20+
});
21+
22+
failed.forEach(function(context) {
23+
console.log('Mismatched %s function calls. Expected %s, actual %d.',
24+
context.name,
25+
context.messageSegment,
26+
context.actual);
27+
console.log(context.stack.split('\n').slice(2).join('\n'));
28+
});
29+
30+
if (failed.length) process.exit(1);
31+
}
32+
33+
exports.mustCall = function(fn, exact) {
34+
return _mustCallInner(fn, exact, 'exact');
35+
};
36+
37+
function _mustCallInner(fn, criteria = 1, field) {
38+
if (typeof fn === 'number') {
39+
criteria = fn;
40+
fn = noop;
41+
} else if (fn === undefined) {
42+
fn = noop;
43+
}
44+
45+
if (typeof criteria !== 'number')
46+
throw new TypeError(`Invalid ${field} value: ${criteria}`);
47+
48+
const context = {
49+
[field]: criteria,
50+
actual: 0,
51+
stack: (new Error()).stack,
52+
name: fn.name || '<anonymous>'
53+
};
54+
55+
// add the exit listener only once to avoid listener leak warnings
56+
if (mustCallChecks.length === 0) process.on('exit', runCallChecks);
57+
58+
mustCallChecks.push(context);
59+
60+
return function() {
61+
context.actual++;
62+
return fn.apply(this, arguments);
63+
};
64+
}
65+
66+
exports.mustNotCall = function(msg) {
67+
return function mustNotCall() {
68+
assert.fail(msg || 'function should not have been called');
69+
};
70+
};

test/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ let testModules = [
1414
'function',
1515
'name',
1616
'object',
17+
'promise',
1718
'typedarray',
1819
'objectwrap'
1920
];

test/promise.cc

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
#include "napi.h"
2+
3+
using namespace Napi;
4+
5+
Value IsPromise(const CallbackInfo& info) {
6+
return Boolean::New(info.Env(), info[0].IsPromise());
7+
}
8+
9+
Value ResolvePromise(const CallbackInfo& info) {
10+
auto deferred = Promise::Deferred::New(info.Env());
11+
deferred.Resolve(info[0]);
12+
return deferred.Promise();
13+
}
14+
15+
Value RejectPromise(const CallbackInfo& info) {
16+
auto deferred = Promise::Deferred::New(info.Env());
17+
deferred.Reject(info[0]);
18+
return deferred.Promise();
19+
}
20+
21+
Object InitPromise(Env env) {
22+
Object exports = Object::New(env);
23+
24+
exports["isPromise"] = Function::New(env, IsPromise);
25+
exports["resolvePromise"] = Function::New(env, ResolvePromise);
26+
exports["rejectPromise"] = Function::New(env, RejectPromise);
27+
28+
return exports;
29+
}

test/promise.js

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
'use strict';
2+
const buildType = process.config.target_defaults.default_configuration;
3+
const assert = require('assert');
4+
const common = require('./common');
5+
6+
test(require(`./build/${buildType}/binding.node`));
7+
test(require(`./build/${buildType}/binding_noexcept.node`));
8+
9+
function test(binding) {
10+
assert.strictEqual(binding.promise.isPromise({}), false);
11+
12+
const resolving = binding.promise.resolvePromise('resolved');
13+
assert.strictEqual(binding.promise.isPromise(resolving), true);
14+
resolving.then(common.mustCall()).catch(common.mustNotCall());
15+
16+
const rejecting = binding.promise.rejectPromise('error');
17+
assert.strictEqual(binding.promise.isPromise(rejecting), true);
18+
rejecting.then(common.mustNotCall()).catch(common.mustCall());
19+
}

0 commit comments

Comments
 (0)