BFE.dev solution for JavaScript Quiz
2. Promise executor
TL;DR
Once Promise is fulfilled or rejected, it cannot be fulfilled or rejected again.
So for the code in the question:
new Promise((resolve, reject) => { resolve(1) resolve(2) reject('error')}).then( (value) => { console.log(value) }, (error) => { console.log('error') })
It is the same as below:
new Promise((resolve, reject) => { resolve(1)}).then( (value) => { console.log(value) }, (error) => { console.log('error') })
So 1
is the result.
Below is detailed explanation based on ECMAScript spec
Promise(executor)
According to ECMAScript spec, what happens in new Promise(...)
is defined as:
- If NewTarget is undefined, throw a TypeError exception.
- If IsCallable(executor) is false, throw a TypeError exception.
- Let promise be ? OrdinaryCreateFromConstructor(NewTarget, "%Promise.prototype%", « [[PromiseState]], [[PromiseResult]], [[PromiseFulfillReactions]], [[PromiseRejectReactions]], [[PromiseIsHandled]] »).
- Set promise.[[PromiseState]] to pending.
- Set promise.[[PromiseFulfillReactions]] to a new empty List.
- Set promise.[[PromiseRejectReactions]] to a new empty List.
- Set promise.[[PromiseIsHandled]] to false.
- Let resolvingFunctions be CreateResolvingFunctions(promise).
- Let completion be Completion(Call(executor, undefined, « resolvingFunctions.[[Resolve]], resolvingFunctions.[[Reject]] »)).
- If completion is an abrupt completion, then
- a. Perform ? Call(resolvingFunctions.[[Reject]], undefined, « completion.[[Value]] »).
- Return promise.
We can see that
- it creates an empty promise object
- creates the
resolve()
andreject()
by CreateResolvingFunctions - the executor is called with above 2 functions passed.
Properties of Promise Instances
According to ECMAScript spec, a promise have following internal data slot.
Internal Slot Type Description [[PromiseState]] pending, fulfilled, or rejected Governs how a promise will react to incoming calls to its then method. [[PromiseResult]] an ECMAScript language value The value with which the promise has been fulfilled or rejected, if any. Only meaningful if [[PromiseState]] is not pending. [[PromiseFulfillReactions]] a List of PromiseReaction Records Records to be processed when/if the promise transitions from the pending state to the fulfilled state. [[PromiseRejectReactions]] a List of PromiseReaction Records Records to be processed when/if the promise transitions from the pending state to the rejected state. [[PromiseIsHandled]] a Boolean Indicates whether the promise has ever had a fulfillment or rejection handler; used in unhandled rejection tracking.
[[PromiseState]] is very important, it is internal state that we often refer to whether a Promise is rejected or fulfilled.
[[PromiseResult]] is the final value that it settles to
[[PromiseFulfillReactions]] and [[PromiseRejectReactions]] are the callbacks
[[PromiseIsHandled]] is why we often see unhandle rejection error.
CreateResolvingFunctions ( promise )
According to ECMAScript spec, resolve()
and reject()
are created as below
- Let alreadyResolved be the Record { [[Value]]: false }.
- Let stepsResolve be the algorithm steps defined in Promise Resolve Functions.
- Let lengthResolve be the number of non-optional parameters of the function definition in Promise Resolve Functions.
- Let resolve be CreateBuiltinFunction(stepsResolve, lengthResolve, "", « [[Promise]], [[AlreadyResolved]] »).
- Set resolve.[[Promise]] to promise.
- Set resolve.[[AlreadyResolved]] to alreadyResolved.
- Let stepsReject be the algorithm steps defined in Promise Reject Functions.
- Let lengthReject be the number of non-optional parameters of the function definition in Promise Reject Functions.
- Let reject be CreateBuiltinFunction(stepsReject, lengthReject, "", « [[Promise]], [[AlreadyResolved]] »).
- Set reject.[[Promise]] to promise.
- Set reject.[[AlreadyResolved]] to alreadyResolved.
- Return the Record { [[Resolve]]: resolve, [[Reject]]: reject }.
We can see that
- both
resolve()
andreject()
holds internal reference to the Promise by[[Promise]]
- both
resolve()
andreject()
has a property[[AlreadyResolved]]
, which points to a single state alreadyResolved, meaningresolve()
andreject()
are checking the same value
resolve()
According to ECMAScript spec,
- Let F be the active function object.
- Assert: F has a [[Promise]] internal slot whose value is an Object.
- Let promise be F.[[Promise]].
- Let alreadyResolved be F.[[AlreadyResolved]].
- If alreadyResolved.[[Value]] is true, return undefined.
- Set alreadyResolved.[[Value]] to true.
- If SameValue(resolution, promise) is true, then
- Let selfResolutionError be a newly created TypeError object.
- Perform RejectPromise(promise, selfResolutionError).
- Return undefined.
- If resolution is not an Object, then
- Perform FulfillPromise(promise, resolution).
- Return undefined.
- Let then be Completion(Get(resolution, "then")).
- If then is an abrupt completion, then
- Perform RejectPromise(promise, then.[[Value]]).
- Return undefined.
- Let thenAction be then.[[Value]].
- If IsCallable(thenAction) is false, then
- Perform FulfillPromise(promise, resolution).
- Return undefined.
- Let thenJobCallback be HostMakeJobCallback(thenAction).
- Let job be NewPromiseResolveThenableJob(promise, resolution,thenJobCallback).
- Perform HostEnqueuePromiseJob(job.[[Job]], job.[[Realm]]).
- Return undefined.
There are quite some steps
- it checks if promise is already resolved, return if so
- it forbids
resolve()
to be called with promise itself, it creates infinite chaining - resolves to the value if the argument is not Object
- if the argument is thenable, chain them
The step 5 clearly tells us that resolve()
does nothing if promise is already resolved.
reject()
According to ECMASCript spec, reject()
internals are defined as:
- Let F be the active function object.
- Assert: F has a [[Promise]] internal slot whose value is an Object.
- Let promise be F.[[Promise]].
- Let alreadyResolved be F.[[AlreadyResolved]].
- If alreadyResolved.[[Value]] is true, return undefined.
- Set alreadyResolved.[[Value]] to true.
- Perform RejectPromise(promise, reason).
- Return undefined.
The step 5 clearly tells us that reject()
does nothing if promise is already resolved.