BFE.dev solution for JavaScript Quiz
3. Promise then callbacks
TL;DR
- if function is passed as callback, the return value of the function is used as the fulfillment value
- for non-function values, previous fulfillment value is used.
Promise.resolve(1) // fulfilled : 1
.then(() => 2) // function is passed, return value is 2 => fulfilled : 2
.then(3) // non-function is passed, previous fulfillment value 2 is used => fulfilled : 2
.then((value) => value * 3) // function is passed, value is 2, return value is 6 => fulfilled: 6
.then(Promise.resolve(4)) // Promise object is not function, previous fulfillment value 6 is used => fulfilled : 6
.then(console.log) // 6 gets logged
Below is longer version of explanation.
Promise.prototype.then()
From ECMAScript Spec, it does below things.
- Let promise be the this value.
- If IsPromise(promise) is false, throw a TypeError exception.
- Let C be ? SpeciesConstructor(promise, %Promise%).
- Let resultCapability be ? NewPromiseCapability(C).
- Return PerformPromiseThen(promise, onFulfilled, onRejected, resultCapability).
Basically it says it creates a new Promise object, and more steps are in below function.
PerformPromiseThen()
According to ECMAScript Spec,
Promise.prototype.then()
works as below.
- Assert: IsPromise(promise) is true.
- If resultCapability is not present, then
- Set resultCapability to undefined.
- If IsCallable(onFulfilled) is false, then
- Let onFulfilledJobCallback be empty.
- Else,
- Let onFulfilledJobCallback be HostMakeJobCallback(onFulfilled).
- If IsCallable(onRejected) is false, then
- Let onRejectedJobCallback be empty.
- Else,
- Let onRejectedJobCallback be HostMakeJobCallback(onRejected).
- Let fulfillReaction be the PromiseReaction { [[Capability]]: resultCapability, [[Type]]: Fulfill, [[Handler]]: onFulfilledJobCallback }.
- Let rejectReaction be the PromiseReaction { [[Capability]]: resultCapability, [[Type]]: Reject, [[Handler]]: onRejectedJobCallback }.
- If promise.[[PromiseState]] is pending, then
- Append fulfillReaction to promise.[[PromiseFulfillReactions]].
- Append rejectReaction to promise.[[PromiseRejectReactions]].
- Else if promise.[[PromiseState]] is fulfilled, then
- Let value be promise.[[PromiseResult]].
- Let fulfillJob be NewPromiseReactionJob(fulfillReaction, value).
- Perform HostEnqueuePromiseJob(fulfillJob.[[Job]], fulfillJob.[[Realm]]).
- Else,
- Assert: The value of promise.[[PromiseState]] is rejected.
- Let reason be promise.[[PromiseResult]].
- If promise.[[PromiseIsHandled]] is false, perform HostPromiseRejectionTracker(promise, "handle").
- Let rejectJob be NewPromiseReactionJob(rejectReaction, reason).
- Perform HostEnqueuePromiseJob(rejectJob.[[Job]], rejectJob.[[Realm]]).
- Set promise.[[PromiseIsHandled]] to true.
- If resultCapability is undefined, then
- Return undefined.
- Else,
- Return resultCapability.[[Promise]].
Basically it says
- create callbacks(fulfillReaction, rejectReaction) for fulfilment and rejection (7 and 8)
- register callbacks if the promise is pending (9)
- trigger the callbacks if promise is fulfilled or rejected (10 and 11)
Notice that in step 3 and 5, it is checking if argument is function or not (IsCallable). If not function, the internal of fulfillReaction/rejectReaction is empty
What happens in _empty_
is defined in NewPromiseReactionJob(), which is how the fulfillReaction and rejectReaction are executed.
NewPromiseReactionJob()
According to ECMAScript Spec,
- Let _job be a new Job Abstract Closure with no parameters that captures reaction and argument and performs the following steps when called:
- Let promiseCapability be reaction.[[Capability]].
- Let type be reaction.[[Type]].
- Let handler be reaction.[[Handler]].
- If handler is empty, then
- If type is Fulfill, let handlerResult be NormalCompletion(argument).
- Else,
- Assert: type is Reject.
- Let handlerResult be ThrowCompletion(argument).
- Else, let handlerResult be Completion(HostCallJobCallback(handler, undefined, « argument »)).
- If promiseCapability is undefined, then
- Assert: handlerResult is not an abrupt completion.
- Return empty.
- Assert: promiseCapability is a PromiseCapability Record.
- If handlerResult is an abrupt completion, then
- Return ? Call(promiseCapability.[[Reject]], undefined, « handlerResult.[[Value]] »).
- Else,
- Return ? Call(promiseCapability.[[Resolve]], undefined, « handlerResult.[[Value]] »).
- Let handlerRealm be null.
- If reaction.[[Handler]] is not empty, then
- Let getHandlerRealmResult be Completion(GetFunctionRealm(reaction.[[Handler]].[[Callback]])).
- If getHandlerRealmResult is a normal completion, set handlerRealm to getHandlerRealmResult.[[Value]].
- Else, set handlerRealm to the current Realm Record.
- NOTE: handlerRealm is never null unless the handler is undefined. When the handler is a revoked Proxy and no ECMAScript code runs, handlerRealm is used to create error objects.
- Return the Record { [[Job]]: job, [[Realm]]: _handlerRealm }.
Let's focus on step 4 & 5, it says
- step 4: if callback (handler) is empty, then the existing fulfillment value (argument) is used
- step 5: otherwise, the return value of callback (handler) is used.
After this the value is used to resolve() or reject() the promise object.
You might also be able to find a solution fromcommunity posts or fromAI solution.