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
CodePudding user response:
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):
- Queue microtask for
Microtask 1
- Queue microtask for
Microtask 2
- Task is compelete, start processing microtasks:
- Execute microtask queued in #1
- Output
Microtask 1
- Queue microtask for
Microtask 11
- Output
- Execute microtask queued in #2
- Output
Microtask 2
- Queue microtask for
Microtask 21
- Output
- Execute microtask queued in #3.1
- Output
Microtask 11
- Output
- Execute microtask queued in #3.2
- Output
Microtask 21
- Output
- Execute microtask queued in #1
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):
- Execute
Promise.resolve()
, creating a promise that in this particular case is fulfilled with the valueundefined
. - Execute
.then(() => console.log("Microtask 1"))
, creating a new function and callingthen
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. - Execute
.then(() => console.log("Microtask 11"))
, creating a new function and callingthen
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. - The task running this code ends, so the JavaScript engine processes the microtask queue:
- Execute the first microtask in the queue:
- Call the function logging
Microtask 1
. - Since the function returns
undefined
, which isn't a promise or other thenable, the engine fulfills the promise from Step 2 withundefined.
(If the function has returned a promise or other thenable, the promise would instead be resolved to the promise that was returned.)- That queues a microtask to call the fulfillment handler that was registered on that promise in Step 3, the one that will log
Microtask 11
when called.
- That queues a microtask to call the fulfillment handler that was registered on that promise in Step 3, the one that will log
- Call the function logging
- Execute the next microtask in the queue (remember that we're ignoring the other promise chain in this description, so in this case the next microtask is the one logging
Microtask 11
that was just added to the queue, not the one loggingMicrotask 2
):- Call the function logging
Microtask 11
. - Since the function returns
undefined
, which isn't a promise or other thenable, the engine fulfills the promise from Step 3 withundefined.
- That doesn't do anything, because that promise doesn't have any fulfillment handlers registered on it.
- Call the function logging
- Execute the first microtask in the queue:
CodePudding user response:
"Microtask 1"
and "Microtask 11"
are sequential under one Promise
& the then
callbacks await
for the response in the first then once it is resolved then it moves on to the next then
in the promise chain
in the sequence.
Hence, the output you see in the order Microtask 1, Microtask 2, Microtask 11, Microtask 22
.
But if you do you chain all the promises you will see the expected output i.e Microtask 1, Microtask 11, Microtask 2, Microtask 22
.:
Promise.resolve()
.then(() => console.log("Microtask 1"))
.then(() => console.log("Microtask 11"))
.then(() => console.log("Microtask 2"))
.then(() => console.log("Microtask 22"))
NOTE: then callbacks
(and awaits
) are executed after the synchronous JavaScript code and in order.