To illustrate how the microtask queue evolves through the execution of your script, I would first suggest some changes to your script, so we can more easily identify the promises and the callbacks that are involved:
// Assign all used callbacks to distinct variables (for easy reference)
const p1a_then = console.log;
const p1b_catch = console.log;
const p2a_then = console.log;
const p2b_catch = console.log;
// Use available short-cuts for immediately settling promises
// and assign each created promise to a separate variable (for easy reference)
const p1a = Promise.reject(1);
const p1b = p1a.then(p1a_then);
const p1 = p1b.catch(p1b_catch);
const p2a = Promise.resolve(10);
const p2b = p2a.then(p2a_then);
const p2 = p2b.catch(p2b_catch);
Note how the promise chains were split over several statements/assignments, but this does not affect the chain itself: it still represents the same flow of execution, but with more variable references which we can use in analysing this flow.
With some simplifications, we could represent that execution flow like this:
| step |
call stack |
action |
p1a |
p1b |
p1 |
p2a |
p2b |
p2 |
microtask queue |
| 1 |
script |
p1a = Promise.reject(1) |
R |
|
|
|
|
|
|
| 2 |
script |
p1b = p1a.then(p1a_then) |
R |
P |
|
|
|
|
p1a_catch/p1b |
| 3 |
script |
p1 = p1b.catch(p1b_catch) |
R |
P |
P |
|
|
|
p1a_catch/p1b |
| 4 |
script |
p2a = Promise.resolve(10) |
R |
|
|
F |
|
|
|
| 5 |
script |
p2b = p2a.then(p2a_then) |
R |
P |
|
F |
P |
|
p1a_catch/p1b, p2a_then/p2b |
| 6 |
script |
p2 = p2b.catch(p2b_catch) |
R |
P |
P |
F |
P |
P |
p1a_catch/p1b, p2a_then/p2b |
| 7 |
- |
process microtask queue |
R |
P |
P |
F |
P |
P |
p1a_catch/p1b, p2a_then/p2b |
| 8 |
p1a_catch |
(pass through) reject p1b |
R |
R |
P |
F |
P |
P |
p2a_then/p2b, p1b_catch/p1 |
| 9 |
- |
process microtask queue |
R |
R |
P |
F |
P |
P |
p2a_then/p2b, p1b_catch/p1 |
| 10 |
p2a_then |
log(10), fulfill p2b |
R |
R |
P |
F |
F |
P |
p1b_catch/p1, p2b_then/p2 |
| 11 |
- |
process microtask queue |
R |
R |
P |
F |
F |
P |
p1b_catch/p1, p2b_then/p2 |
| 12 |
p1b_catch |
log(1), fulfill p1 |
R |
R |
F |
F |
F |
P |
p2b_then/p2 |
| 13 |
- |
process microtask queue |
R |
R |
F |
F |
F |
P |
p2b_then/p2 |
| 14 |
p2b_then |
(pass through) fulfill p2 |
R |
R |
F |
F |
F |
F |
|
| 15 |
- |
process microtask queue |
R |
R |
F |
F |
F |
F |
|
Some comments on this table:
The last column lists the entries in the microtask queue, with "function/promise" notation: when that microtask entry is consumed, the function will be called and its return value will be used to resolve/reject the promise mentioned after the slash.
That column sometimes lists functions that don't exist, like p1a_catch. This is to indicate that although this hander was not provided in the code, there is still an action to perform: the default action ("pass through") will reject promise p1b.
The columns near the right give the state of each promise: [P]ending, [F]ulfilled, [R]ejected.
Be aware that a callback is only queued in the microtask queue when the promise, on which this callback was attached, has settled. So for example, in step 3 the callback p1b_catch is not added to the microtask queue yet because p1b is not yet settled. This may be a surprise, but for p1b to settle, we first need p1a_then to execute, as that determines how p1b will resolve, and that execution will only happen asynchronously, i.e. later. In general, a then/catch method call will always return a pending promise, no matter what the state is of the promise on which you call that then/catch method.
I hope this clarifies it.
thenmethod returns a promise, and that only when that promise settles, thecatchcallback becomes relevant?.then(…, …)and.then(…).catch(…). Notice that in your code, you are calling.then()and.catch()on different promises -.then()returns a new promise