diff --git a/doc/promises.md b/doc/promises.md index 21594c6b8..ddac2a500 100644 --- a/doc/promises.md +++ b/doc/promises.md @@ -75,5 +75,47 @@ Rejects the Promise object held by the `Napi::Promise::Deferred` object. * `[in] value`: The Node-API primitive value with which to reject the `Napi::Promise`. +## Promise Methods + +### Then + +```cpp + Napi::Promise::Then(napi_value onFulfilled, napi_value onRejected) const; +``` + +Attaches fulfillment and/or rejection handlers to the promise and returns a new promise. + +**Parameters:** +* `[in] onFulfilled`: A function to be called when the promise is fulfilled +* `[in] onRejected`: A function to be called when the promise is rejected (optional) + +**Returns:** A new `` that resolves or rejects based on the handler's result. + +**Example:** +```cpp +// Single fulfillment handler +Promise newPromise = existingPromise.Then(fulfillmentHandler); + +// Both fulfillment and rejection handlers +Promise chainedPromise = existingPromise.Then(onFulfilled, onRejected); +``` + +### Catch + +```cpp + Napi::Promise::Catch(napi_value onRejected) const; +``` + +Attaches a rejection handler to the promise and returns a new promise. + +**Parameters:** +* `[in] onRejected`: A function to be called when the promise is rejected + +**Returns:** A new `` that handles rejection cases. + +**Example:** +```cpp +Promise handledPromise = existingPromise.Catch(rejectionHandler); +``` [`Napi::Object`]: ./object.md diff --git a/napi-inl.h b/napi-inl.h index 88a6cd4a7..c71fa9c03 100644 --- a/napi-inl.h +++ b/napi-inl.h @@ -2813,6 +2813,84 @@ inline void Promise::CheckCast(napi_env env, napi_value value) { inline Promise::Promise(napi_env env, napi_value value) : Object(env, value) {} +inline MaybeOrValue Promise::Then(napi_value onFulfilled) const { + EscapableHandleScope scope(_env); +#ifdef NODE_ADDON_API_ENABLE_MAYBE + Value thenMethod; + if (!Get("then").UnwrapTo(&thenMethod)) { + return Nothing(); + } + MaybeOrValue result = thenMethod.As().Call(*this, {onFulfilled}); + if (result.IsJust()) { + return Just(scope.Escape(result.Unwrap()).As()); + } + return Nothing(); +#else + Function thenMethod = Get("then").As(); + MaybeOrValue result = thenMethod.Call(*this, {onFulfilled}); + if (scope.Env().IsExceptionPending()) { + return Promise(); + } + return scope.Escape(result).As(); +#endif +} + +inline MaybeOrValue Promise::Then(napi_value onFulfilled, napi_value onRejected) const { + EscapableHandleScope scope(_env); +#ifdef NODE_ADDON_API_ENABLE_MAYBE + Value thenMethod; + if (!Get("then").UnwrapTo(&thenMethod)) { + return Nothing(); + } + MaybeOrValue result = thenMethod.As().Call(*this, {onFulfilled, onRejected}); + if (result.IsJust()) { + return Just(scope.Escape(result.Unwrap()).As()); + } + return Nothing(); +#else + Function thenMethod = Get("then").As(); + MaybeOrValue result = thenMethod.Call(*this, {onFulfilled, onRejected}); + if (scope.Env().IsExceptionPending()) { + return Promise(); + } + return scope.Escape(result).As(); +#endif +} + +inline MaybeOrValue Promise::Catch(napi_value onRejected) const { + EscapableHandleScope scope(_env); +#ifdef NODE_ADDON_API_ENABLE_MAYBE + Value catchMethod; + if (!Get("catch").UnwrapTo(&catchMethod)) { + return Nothing(); + } + MaybeOrValue result = catchMethod.As().Call(*this, {onRejected}); + if (result.IsJust()) { + return Just(scope.Escape(result.Unwrap()).As()); + } + return Nothing(); +#else + Function catchMethod = Get("catch").As(); + MaybeOrValue result = catchMethod.Call(*this, {onRejected}); + if (scope.Env().IsExceptionPending()) { + return Promise(); + } + return scope.Escape(result).As(); +#endif +} + +inline MaybeOrValue Promise::Then(const Function& onFulfilled) const { + return Then(static_cast(onFulfilled)); +} + +inline MaybeOrValue Promise::Then(const Function& onFulfilled, const Function& onRejected) const { + return Then(static_cast(onFulfilled), static_cast(onRejected)); +} + +inline MaybeOrValue Promise::Catch(const Function& onRejected) const { + return Catch(static_cast(onRejected)); +} + //////////////////////////////////////////////////////////////////////////////// // Buffer class //////////////////////////////////////////////////////////////////////////////// diff --git a/napi.h b/napi.h index bd604c6fd..98500c53a 100644 --- a/napi.h +++ b/napi.h @@ -1574,7 +1574,16 @@ class Promise : public Object { static void CheckCast(napi_env env, napi_value value); + Promise(); Promise(napi_env env, napi_value value); + + MaybeOrValue Then(napi_value onFulfilled) const; + MaybeOrValue Then(napi_value onFulfilled, napi_value onRejected) const; + MaybeOrValue Catch(napi_value onRejected) const; + + MaybeOrValue Then(const Function& onFulfilled) const; + MaybeOrValue Then(const Function& onFulfilled, const Function& onRejected) const; + MaybeOrValue Catch(const Function& onRejected) const; }; template diff --git a/test/promise.cc b/test/promise.cc index f25600283..c2ef6df53 100644 --- a/test/promise.cc +++ b/test/promise.cc @@ -1,4 +1,5 @@ #include "napi.h" +#include "test_helper.h" using namespace Napi; @@ -23,6 +24,72 @@ Value PromiseReturnsCorrectEnv(const CallbackInfo& info) { return Boolean::New(info.Env(), deferred.Env() == info.Env()); } +Value ThenMethodOnFulfilled(const CallbackInfo& info) { + auto deferred = Promise::Deferred::New(info.Env()); + Function onFulfilled = info[0].As(); + + Promise resultPromise = MaybeUnwrap(deferred.Promise().Then(onFulfilled)); + + bool isPromise = resultPromise.IsPromise(); + deferred.Resolve(Number::New(info.Env(), 42)); + + Object result = Object::New(info.Env()); + result["isPromise"] = Boolean::New(info.Env(), isPromise); + result["promise"] = resultPromise; + + return result; +} + +Value ThenMethodOnFulfilledOnRejectedResolve(const CallbackInfo& info) { + auto deferred = Promise::Deferred::New(info.Env()); + Function onFulfilled = info[0].As(); + Function onRejected = info[1].As(); + + Promise resultPromise = MaybeUnwrap(deferred.Promise().Then(onFulfilled, onRejected)); + + bool isPromise = resultPromise.IsPromise(); + deferred.Resolve(Number::New(info.Env(), 42)); + + Object result = Object::New(info.Env()); + result["isPromise"] = Boolean::New(info.Env(), isPromise); + result["promise"] = resultPromise; + + return result; +} + +Value ThenMethodOnFulfilledOnRejectedReject(const CallbackInfo& info) { + auto deferred = Promise::Deferred::New(info.Env()); + Function onFulfilled = info[0].As(); + Function onRejected = info[1].As(); + + Promise resultPromise = MaybeUnwrap(deferred.Promise().Then(onFulfilled, onRejected)); + + bool isPromise = resultPromise.IsPromise(); + deferred.Reject(String::New(info.Env(), "Rejected")); + + Object result = Object::New(info.Env()); + result["isPromise"] = Boolean::New(info.Env(), isPromise); + result["promise"] = resultPromise; + + return result; +} + +Value CatchMethod(const CallbackInfo& info) { + auto deferred = Promise::Deferred::New(info.Env()); + Function onRejected = info[0].As(); + + Promise resultPromise = MaybeUnwrap(deferred.Promise().Catch(onRejected)); + + bool isPromise = resultPromise.IsPromise(); + deferred.Reject(String::New(info.Env(), "Rejected")); + + Object result = Object::New(info.Env()); + result["isPromise"] = Boolean::New(info.Env(), isPromise); + result["promise"] = resultPromise; + + return result; +} + Object InitPromise(Env env) { Object exports = Object::New(env); @@ -31,6 +98,12 @@ Object InitPromise(Env env) { exports["rejectPromise"] = Function::New(env, RejectPromise); exports["promiseReturnsCorrectEnv"] = Function::New(env, PromiseReturnsCorrectEnv); + exports["thenMethodOnFulfilled"] = Function::New(env, ThenMethodOnFulfilled); + exports["thenMethodOnFulfilledOnRejectedResolve"] = + Function::New(env, ThenMethodOnFulfilledOnRejectedResolve); + exports["thenMethodOnFulfilledOnRejectedReject"] = + Function::New(env, ThenMethodOnFulfilledOnRejectedReject); + exports["catchMethod"] = Function::New(env, CatchMethod); return exports; } diff --git a/test/promise.js b/test/promise.js index 63b20cef8..e61a783ce 100644 --- a/test/promise.js +++ b/test/promise.js @@ -17,4 +17,27 @@ async function test (binding) { rejecting.then(common.mustNotCall()).catch(common.mustCall()); assert(binding.promise.promiseReturnsCorrectEnv()); + + const onFulfilled = (value) => value * 2; + const onRejected = (reason) => reason + '!'; + + const thenOnFulfilled = binding.promise.thenMethodOnFulfilled(onFulfilled); + assert.strictEqual(thenOnFulfilled.isPromise, true); + const onFulfilledValue = await thenOnFulfilled.promise; + assert.strictEqual(onFulfilledValue, 84); + + const thenResolve = binding.promise.thenMethodOnFulfilledOnRejectedResolve(onFulfilled, onRejected); + assert.strictEqual(thenResolve.isPromise, true); + const thenResolveValue = await thenResolve.promise; + assert.strictEqual(thenResolveValue, 84); + + const thenRejected = binding.promise.thenMethodOnFulfilledOnRejectedReject(onFulfilled, onRejected); + assert.strictEqual(thenRejected.isPromise, true); + const rejectedValue = await thenRejected.promise; + assert.strictEqual(rejectedValue, 'Rejected!'); + + const catchMethod = binding.promise.catchMethod(onRejected); + assert.strictEqual(catchMethod.isPromise, true); + const catchValue = await catchMethod.promise; + assert.strictEqual(catchValue, 'Rejected!'); }