What is the JavaScript rule that determines the output of this code?:
Promise.resolve()
.then(() => console.log("Microtask 1"))
.then(() => console.log("Microtask 11"));
Promise.resolve()
.then(() => console.log("Microtask 2"))
.then(() => console.log("Microtask 22"))
This is the output:
Microtask 1
Microtask 2
Microtask 11
Microtask 22
But why isn't this the output?:
Microtask 1
Microtask 11
Microtask 2
Microtask 22
You see the output you see because the microtask for Microtask 11
isn't queued until the microtask for Microtask 1
runs, at which point the microtask for Microtask 2
is already in the queue waiting. So the order is (skipping some details; more below):
Microtask 1
Microtask 2
Microtask 1
Microtask 11
Microtask 2
Microtask 21
Microtask 11
Microtask 21
Thus,
Microtask 1 Microtask 2 Microtask 11 Microtask 21
But: Writing code that relies on the order of execution of disconnected promise chains / microtask sequences is asking for trouble. :-) You can only reason about it in relation to promises you know are already settled, and of course in the normal case, you don't know when (or even if) a promise will settle. Instead, if you need a specific order, connect the chains to ensure order.
Re skipped details: I sort of glossed over things in the main explanation to keep it short and clear, but for accuracy, let's look at:
Promise.resolve()
.then(() => console.log("Microtask 1"))
.then(() => console.log("Microtask 11"));
...just on its own, ignoring the second promise chain to keep things simple.
Here's how that code is executed (my blog post on promise terminology may be helpful while reading the below):
Promise.resolve()
, creating a promise that in this particular case is fulfilled with the value undefined
..then(() => console.log("Microtask 1"))
, creating a new function and calling then
with it on the promise from Step 1. This creates and returns a new promise, and in this particular case, queues a microtask to call the function because the promise is already fulfilled..then(() => console.log("Microtask 11"))
, creating a new function and calling then
with it on the promise from Step 2. That creates and returns a new promise (which we throw away in that code) and, since the promise from Step 2 is still pending, adds the function to it as a fulfillment handler.Microtask 1
.undefined
, which isn't a promise or other thenable, the engine fulfills the promise from Step 2 with undefined.
(If the function has returned a promise or other thenable, the promise would instead be resolved to the promise that was returned.)
Microtask 11
when called.Microtask 11
that was just added to the queue, not the one logging Microtask 2
):
Microtask 11
.undefined
, which isn't a promise or other thenable, the engine fulfills the promise from Step 3 with undefined.