1

Is there a spec on the order of execution of branches of a JavaScript Promise?

Consider this code:

 // Create a promise that will be resolved when we call the function `r` let r; const promise = new Promise((resolve, reject) => { r = resolve; // Store the resolver in `r`, so we can call it later. }); // When `promise` resolves, print "I am the first branch". promise.then(() => console.log("I am the first branch")); // When `promise` resolves, also print "I am the second branch". promise.then(() => console.log("I am the second branch")); // Resolve `promise` r(); 

The Promise promise is branched out into two branches.

From my testing, the first branch is executed before the second.

I am the first branch I am the second branch 

My question: Is this guaranteed by the JavaScript spec, or am I relying on an undefined behavior?

Note: I could, of course, chain the promises instead. Like this:

promise.then(() => console.log("I am the first branch")).then(() => console.log("I am the second branch")); 

But I would prefer to branch them if possible.

10
  • 2
    You probably shouldn't rely on this extensively. If you need "second branch" to happen after "first branch" then chain the two. Your code works here but might not in other cases. Commented Jul 13, 2021 at 9:50
  • 2
    "But I would prefer to branch them if possible." if they depend on each other, then they should not branch. It's as simple as that. Commented Jul 13, 2021 at 9:52
  • 3
    27.2.5.4 Promise.prototype.then -> 5. Return PerformPromiseThen -> 27.2.5.4.1 PerformPromiseThen -> 9.a: "If promise.[[PromiseState]] is pending, then Append fulfillReaction as the last element of the List that is promise [[PromiseFulfillReactions]]" Commented Jul 13, 2021 at 9:55
  • 1
    @Bravo in this example, there are no competing tasks to resolve. So I'd expect this to always resolve in the same order. But add some more tasks and it becomes unpredictable. Commented Jul 13, 2021 at 9:55
  • 1
    @Andreas would you mind putting that into an answer Commented Jul 13, 2021 at 9:55

1 Answer 1

2

In this case (-> calling .then() multiple times on the same promise)...

const p = new Promise((resolve, reject) => { setTimeout(resolve, 1000) }); p.then(() => console.log("first")); p.then(() => console.log("second"));

...the order is guaranteed because .then() acts like a .push() with arrays. The relevant path from the specification is:

Step 5 in Promise.prototype.then + Step 9.a in PerformPromiseThen

27.2.5.4 Promise.prototype.then ( onFulfilled, onRejected )

When the then method is called with arguments onFulfilled and onRejected, the following steps are taken:

1. Let promise be the this value. 2. If IsPromise(promise) is false, throw a TypeError exception. 3. Let C be ? SpeciesConstructor(promise, %Promise%). 4. Let resultCapability be ? NewPromiseCapability(C). 5. Return PerformPromiseThen(promise, onFulfilled, onRejected, resultCapability). 

and then

27.2.5.4.1 PerformPromiseThen ( promise, onFulfilled, onRejected [ , resultCapability ] )

The abstract operation PerformPromiseThen takes arguments promise, onFulfilled, and onRejected and optional argument resultCapability (a PromiseCapability Record). It performs the “then” operation on promise using onFulfilled and onRejected as its settlement actions. If resultCapability is passed, the result is stored by updating resultCapability's promise. If it is not passed, then PerformPromiseThen is being called by a specification-internal operation where the result does not matter. It performs the following steps when called:

1. Assert: IsPromise(promise) is true. 2. If resultCapability is not present, then a. Set resultCapability to undefined. 3. If IsCallable(onFulfilled) is false, then a. Let onFulfilledJobCallback be empty. 4. Else, a. Let onFulfilledJobCallback be HostMakeJobCallback(onFulfilled). 5. If IsCallable(onRejected) is false, then a. Let onRejectedJobCallback be empty. 6. Else, a. Let onRejectedJobCallback be HostMakeJobCallback(onRejected). 7. Let fulfillReaction be the PromiseReaction { [[Capability]]: resultCapability, [[Type]]: Fulfill, [[Handler]]: onFulfilledJobCallback }. 8. Let rejectReaction be the PromiseReaction { [[Capability]]: resultCapability, [[Type]]: Reject, [[Handler]]: onRejectedJobCallback }. 9. If promise.[[PromiseState]] is pending, then a. Append fulfillReaction as the last element of the List that is promise.[[PromiseFulfillReactions]]. b. Append rejectReaction as the last element of the List that is promise.[[PromiseRejectReactions]]. 10. Else if promise.[[PromiseState]] is fulfilled, then a. Let value be promise.[[PromiseResult]]. b. Let fulfillJob be NewPromiseReactionJob(fulfillReaction, value). c. Perform HostEnqueuePromiseJob(fulfillJob.[[Job]], fulfillJob.[[Realm]]). 11. Else, a. Assert: The value of promise.[[PromiseState]] is rejected. b. Let reason be promise.[[PromiseResult]]. c. If promise.[[PromiseIsHandled]] is false, perform HostPromiseRejectionTracker(promise, "handle"). d. Let rejectJob be NewPromiseReactionJob(rejectReaction, reason). e. Perform HostEnqueuePromiseJob(rejectJob.[[Job]], rejectJob.[[Realm]]). 12. Set promise.[[PromiseIsHandled]] to true. 13. If resultCapability is undefined, then a. Return undefined. 14. Else, a. Return resultCapability.[[Promise]]. 
Sign up to request clarification or add additional context in comments.

3 Comments

I think the important part here is In this case
@Liam Added a short explanation for "this case" so the answer should work without the question
I don't really dispute this answer. But I do think relying on order or promises (in general) is a bad idea that shouldn't really be promoted.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.