Consider this:
const a = () => {
return new Promise((resolve) => {
resolve(1);
});
};
const b = () => {
return new Promise((resolve) => {
a()
.then((aR) => {
console.log(aR);
resolve(2);
})
.finally(() => {
console.log("A");
});
});
};
const c = () => {
b().then((bR) => {
console.log(bR);
});
};
c();
Why it prints 1 2 A
, not 1 A 2
? If rewriting as synchronous logic, it should look like this:
const a = () => 1
const b = () => {
try {
const aR = a()
console.log(aR)
return 2
} finally {
console.log("A")
}
};
const c = () => {
try {
const bR = b()
console.log(bR)
} catch {}
};
c();
It prints 1 A 2
The problem is originally discovered by @rebuilding127 on Segmentfault, a StackOverflow-like Chinese forum. I think it's worth translating & publishing here.
You can see this chain-call:
a()
.then((aR) => {
console.log(aR);
resolve(2);
})
.finally(() => {
console.log("A");
});
This is a chained invocation, where a().then(...)
returns a Promise
, it will output A
after the returned Promise
is fulfilled, not after a()
is fulfilled. So resolve(2)
will enqueue b().then
before fulfilling a().then
and enqueuing a().finally
.
To make it clearer, I rewrited the code:
const f = () => {
const a = new Promise((resolve) => {
const cb1 = (result1) => {
console.log(result1);
resolve(2);
};
const cbA = () => console.log('A');
const b = Promise.resolve(1);
const c = b.then(cb1);
c.finally(cbA);
});
return a;
};
const cb2 = (result2) => console.log(result2);
f().then(cb2);
The execution unfolds like this:
f
a
b
is immediately fulfilled, causing cb1
to be enqueued into the microtask queue (now queue = [cb1]
)c
's executor enqueuesf
returnscb1
from the queue (now queue = []
)1
, resolves a
, causing cb2
to be enqueued (now queue = [cb2]
)cb1
returns, causing c
to be resolved, then cbA
to be enqueued (now queue = [cb2, cbA]
)cb2
from the queue (now queue = [cbA]
)2
cbA
from the queue; now queue = []
.A
, tick endsAlso published under the original problem on SegmentFault.