Skip to content

Commit ab3e5fe

Browse files
feat: add Then and Catch methods to Promise (#1668)
1 parent 2941941 commit ab3e5fe

File tree

5 files changed

+247
-0
lines changed

5 files changed

+247
-0
lines changed

doc/promises.md

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,5 +75,56 @@ Rejects the Promise object held by the `Napi::Promise::Deferred` object.
7575

7676
* `[in] value`: The Node-API primitive value with which to reject the `Napi::Promise`.
7777

78+
## Promise Methods
79+
80+
### Then
81+
82+
```cpp
83+
Napi::Promise Napi::Promise::Then(napi_value onFulfilled) const;
84+
Napi::Promise Napi::Promise::Then(const Function& onFulfilled) const;
85+
```
86+
87+
Attaches a fulfillment handler to the promise and returns a new promise.
88+
89+
**Parameters:**
90+
* `[in] onFulfilled`: The fulfillment handler for the promise. May be any of:
91+
- `napi_value` – a JavaScript function to be called when the promise is fulfilled.
92+
- `const Function&` – the [`Napi::Function`](function.md) to be called when the promise is fulfilled.
93+
94+
**Returns:** A new `Napi::Promise` that resolves or rejects based on the handler's result.
95+
96+
### Then
97+
98+
```cpp
99+
Napi::Promise Napi::Promise::Then(napi_value onFulfilled, napi_value onRejected) const;
100+
Napi::Promise Napi::Promise::Then(const Function& onFulfilled,
101+
const Function& onRejected) const;
102+
```
103+
104+
Attaches a fulfillment and rejection handlers to the promise and returns a new promise.
105+
106+
**Parameters:**
107+
* `[in] onFulfilled`: The fulfillment handler for the promise. May be any of:
108+
- `napi_value` – a JavaScript function to be called when the promise is fulfilled.
109+
- `const Function&` – the [`Napi::Function`](function.md) to be called when the promise is fulfilled.
110+
* `[in] onRejected` (optional): The rejection handler for the promise. May be any of:
111+
- `napi_value` – a JavaScript function to be called when the promise is rejected.
112+
- `const Function&` – the [`Napi::Function`](function.md) to be called when the promise is rejected.
113+
114+
### Catch
115+
```cpp
116+
Napi::Promise Napi::Promise::Catch(napi_value onRejected) const;
117+
Napi::Promise Napi::Promise::Catch(const Function& onRejected) const;
118+
```
119+
120+
Attaches a rejection handler to the promise and returns a new promise.
121+
122+
**Parameters:**
123+
* `[in] onRejected`: The rejection handler for the promise. May be any of:
124+
- `napi_value` – a JavaScript function to be called when the promise is rejected.
125+
- `const Function&` – the [`Napi::Function`](function.md) to be called when the promise is rejected.
126+
127+
**Returns:** A new `Napi::Promise` that handles rejection cases.
78128
79129
[`Napi::Object`]: ./object.md
130+
[`Napi::Function`]: ./function.md

napi-inl.h

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2811,8 +2811,95 @@ inline void Promise::CheckCast(napi_env env, napi_value value) {
28112811
NAPI_CHECK(result, "Promise::CheckCast", "value is not promise");
28122812
}
28132813

2814+
inline Promise::Promise() : Object() {}
2815+
28142816
inline Promise::Promise(napi_env env, napi_value value) : Object(env, value) {}
28152817

2818+
inline MaybeOrValue<Promise> Promise::Then(napi_value onFulfilled) const {
2819+
EscapableHandleScope scope(_env);
2820+
#ifdef NODE_ADDON_API_ENABLE_MAYBE
2821+
Value thenMethod;
2822+
if (!Get("then").UnwrapTo(&thenMethod)) {
2823+
return Nothing<Promise>();
2824+
}
2825+
MaybeOrValue<Value> result =
2826+
thenMethod.As<Function>().Call(*this, {onFulfilled});
2827+
if (result.IsJust()) {
2828+
return Just(scope.Escape(result.Unwrap()).As<Promise>());
2829+
}
2830+
return Nothing<Promise>();
2831+
#else
2832+
Function thenMethod = Get("then").As<Function>();
2833+
MaybeOrValue<Value> result = thenMethod.Call(*this, {onFulfilled});
2834+
if (scope.Env().IsExceptionPending()) {
2835+
return Promise();
2836+
}
2837+
return scope.Escape(result).As<Promise>();
2838+
#endif
2839+
}
2840+
2841+
inline MaybeOrValue<Promise> Promise::Then(napi_value onFulfilled,
2842+
napi_value onRejected) const {
2843+
EscapableHandleScope scope(_env);
2844+
#ifdef NODE_ADDON_API_ENABLE_MAYBE
2845+
Value thenMethod;
2846+
if (!Get("then").UnwrapTo(&thenMethod)) {
2847+
return Nothing<Promise>();
2848+
}
2849+
MaybeOrValue<Value> result =
2850+
thenMethod.As<Function>().Call(*this, {onFulfilled, onRejected});
2851+
if (result.IsJust()) {
2852+
return Just(scope.Escape(result.Unwrap()).As<Promise>());
2853+
}
2854+
return Nothing<Promise>();
2855+
#else
2856+
Function thenMethod = Get("then").As<Function>();
2857+
MaybeOrValue<Value> result =
2858+
thenMethod.Call(*this, {onFulfilled, onRejected});
2859+
if (scope.Env().IsExceptionPending()) {
2860+
return Promise();
2861+
}
2862+
return scope.Escape(result).As<Promise>();
2863+
#endif
2864+
}
2865+
2866+
inline MaybeOrValue<Promise> Promise::Catch(napi_value onRejected) const {
2867+
EscapableHandleScope scope(_env);
2868+
#ifdef NODE_ADDON_API_ENABLE_MAYBE
2869+
Value catchMethod;
2870+
if (!Get("catch").UnwrapTo(&catchMethod)) {
2871+
return Nothing<Promise>();
2872+
}
2873+
MaybeOrValue<Value> result =
2874+
catchMethod.As<Function>().Call(*this, {onRejected});
2875+
if (result.IsJust()) {
2876+
return Just(scope.Escape(result.Unwrap()).As<Promise>());
2877+
}
2878+
return Nothing<Promise>();
2879+
#else
2880+
Function catchMethod = Get("catch").As<Function>();
2881+
MaybeOrValue<Value> result = catchMethod.Call(*this, {onRejected});
2882+
if (scope.Env().IsExceptionPending()) {
2883+
return Promise();
2884+
}
2885+
return scope.Escape(result).As<Promise>();
2886+
#endif
2887+
}
2888+
2889+
inline MaybeOrValue<Promise> Promise::Then(const Function& onFulfilled) const {
2890+
return Then(static_cast<napi_value>(onFulfilled));
2891+
}
2892+
2893+
inline MaybeOrValue<Promise> Promise::Then(const Function& onFulfilled,
2894+
const Function& onRejected) const {
2895+
return Then(static_cast<napi_value>(onFulfilled),
2896+
static_cast<napi_value>(onRejected));
2897+
}
2898+
2899+
inline MaybeOrValue<Promise> Promise::Catch(const Function& onRejected) const {
2900+
return Catch(static_cast<napi_value>(onRejected));
2901+
}
2902+
28162903
////////////////////////////////////////////////////////////////////////////////
28172904
// Buffer<T> class
28182905
////////////////////////////////////////////////////////////////////////////////

napi.h

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1574,7 +1574,18 @@ class Promise : public Object {
15741574

15751575
static void CheckCast(napi_env env, napi_value value);
15761576

1577+
Promise();
15771578
Promise(napi_env env, napi_value value);
1579+
1580+
MaybeOrValue<Promise> Then(napi_value onFulfilled) const;
1581+
MaybeOrValue<Promise> Then(napi_value onFulfilled,
1582+
napi_value onRejected) const;
1583+
MaybeOrValue<Promise> Catch(napi_value onRejected) const;
1584+
1585+
MaybeOrValue<Promise> Then(const Function& onFulfilled) const;
1586+
MaybeOrValue<Promise> Then(const Function& onFulfilled,
1587+
const Function& onRejected) const;
1588+
MaybeOrValue<Promise> Catch(const Function& onRejected) const;
15781589
};
15791590

15801591
template <typename T>

test/promise.cc

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
#include "napi.h"
2+
#include "test_helper.h"
23

34
using namespace Napi;
45

@@ -23,6 +24,74 @@ Value PromiseReturnsCorrectEnv(const CallbackInfo& info) {
2324
return Boolean::New(info.Env(), deferred.Env() == info.Env());
2425
}
2526

27+
Value ThenMethodOnFulfilled(const CallbackInfo& info) {
28+
auto deferred = Promise::Deferred::New(info.Env());
29+
Function onFulfilled = info[0].As<Function>();
30+
31+
Promise resultPromise = MaybeUnwrap(deferred.Promise().Then(onFulfilled));
32+
33+
bool isPromise = resultPromise.IsPromise();
34+
deferred.Resolve(Number::New(info.Env(), 42));
35+
36+
Object result = Object::New(info.Env());
37+
result["isPromise"] = Boolean::New(info.Env(), isPromise);
38+
result["promise"] = resultPromise;
39+
40+
return result;
41+
}
42+
43+
Value ThenMethodOnFulfilledOnRejectedResolve(const CallbackInfo& info) {
44+
auto deferred = Promise::Deferred::New(info.Env());
45+
Function onFulfilled = info[0].As<Function>();
46+
Function onRejected = info[1].As<Function>();
47+
48+
Promise resultPromise =
49+
MaybeUnwrap(deferred.Promise().Then(onFulfilled, onRejected));
50+
51+
bool isPromise = resultPromise.IsPromise();
52+
deferred.Resolve(Number::New(info.Env(), 42));
53+
54+
Object result = Object::New(info.Env());
55+
result["isPromise"] = Boolean::New(info.Env(), isPromise);
56+
result["promise"] = resultPromise;
57+
58+
return result;
59+
}
60+
61+
Value ThenMethodOnFulfilledOnRejectedReject(const CallbackInfo& info) {
62+
auto deferred = Promise::Deferred::New(info.Env());
63+
Function onFulfilled = info[0].As<Function>();
64+
Function onRejected = info[1].As<Function>();
65+
66+
Promise resultPromise =
67+
MaybeUnwrap(deferred.Promise().Then(onFulfilled, onRejected));
68+
69+
bool isPromise = resultPromise.IsPromise();
70+
deferred.Reject(String::New(info.Env(), "Rejected"));
71+
72+
Object result = Object::New(info.Env());
73+
result["isPromise"] = Boolean::New(info.Env(), isPromise);
74+
result["promise"] = resultPromise;
75+
76+
return result;
77+
}
78+
79+
Value CatchMethod(const CallbackInfo& info) {
80+
auto deferred = Promise::Deferred::New(info.Env());
81+
Function onRejected = info[0].As<Function>();
82+
83+
Promise resultPromise = MaybeUnwrap(deferred.Promise().Catch(onRejected));
84+
85+
bool isPromise = resultPromise.IsPromise();
86+
deferred.Reject(String::New(info.Env(), "Rejected"));
87+
88+
Object result = Object::New(info.Env());
89+
result["isPromise"] = Boolean::New(info.Env(), isPromise);
90+
result["promise"] = resultPromise;
91+
92+
return result;
93+
}
94+
2695
Object InitPromise(Env env) {
2796
Object exports = Object::New(env);
2897

@@ -31,6 +100,12 @@ Object InitPromise(Env env) {
31100
exports["rejectPromise"] = Function::New(env, RejectPromise);
32101
exports["promiseReturnsCorrectEnv"] =
33102
Function::New(env, PromiseReturnsCorrectEnv);
103+
exports["thenMethodOnFulfilled"] = Function::New(env, ThenMethodOnFulfilled);
104+
exports["thenMethodOnFulfilledOnRejectedResolve"] =
105+
Function::New(env, ThenMethodOnFulfilledOnRejectedResolve);
106+
exports["thenMethodOnFulfilledOnRejectedReject"] =
107+
Function::New(env, ThenMethodOnFulfilledOnRejectedReject);
108+
exports["catchMethod"] = Function::New(env, CatchMethod);
34109

35110
return exports;
36111
}

test/promise.js

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,4 +17,27 @@ async function test (binding) {
1717
rejecting.then(common.mustNotCall()).catch(common.mustCall());
1818

1919
assert(binding.promise.promiseReturnsCorrectEnv());
20+
21+
const onFulfilled = (value) => value * 2;
22+
const onRejected = (reason) => reason + '!';
23+
24+
const thenOnFulfilled = binding.promise.thenMethodOnFulfilled(onFulfilled);
25+
assert.strictEqual(thenOnFulfilled.isPromise, true);
26+
const onFulfilledValue = await thenOnFulfilled.promise;
27+
assert.strictEqual(onFulfilledValue, 84);
28+
29+
const thenResolve = binding.promise.thenMethodOnFulfilledOnRejectedResolve(onFulfilled, onRejected);
30+
assert.strictEqual(thenResolve.isPromise, true);
31+
const thenResolveValue = await thenResolve.promise;
32+
assert.strictEqual(thenResolveValue, 84);
33+
34+
const thenRejected = binding.promise.thenMethodOnFulfilledOnRejectedReject(onFulfilled, onRejected);
35+
assert.strictEqual(thenRejected.isPromise, true);
36+
const rejectedValue = await thenRejected.promise;
37+
assert.strictEqual(rejectedValue, 'Rejected!');
38+
39+
const catchMethod = binding.promise.catchMethod(onRejected);
40+
assert.strictEqual(catchMethod.isPromise, true);
41+
const catchValue = await catchMethod.promise;
42+
assert.strictEqual(catchValue, 'Rejected!');
2043
}

0 commit comments

Comments
 (0)