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.
2
Answers
You can see this chain-call:
This is a chained invocation, where
a().then(...)
returns aPromise
, it will outputA
after the returnedPromise
is fulfilled, not aftera()
is fulfilled. Soresolve(2)
will enqueueb().then
before fulfillinga().then
and enqueuinga().finally
.To make it clearer, I rewrited the code:
The execution unfolds like this:
f
a
b
is immediately fulfilled, causingcb1
to be enqueued into the microtask queue (now queue =[cb1]
)c
's executor enqueuesf
returnscb1
from the queue (now queue =[]
)1
, resolvesa
, causingcb2
to be enqueued (now queue =[cb2]
)cb1
returns, causingc
to be resolved, thencbA
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.
To agree with typed-sigterm‘s answer, this is because
resolve(2)
will instantly resolve thenew Promise(...)
returned byb()
. That "instantly" doesn’t mean synchronously, but rather that nothing is ensuring that the Promise will wait for thefinally
block.Unlike the
finally
block in atry
/catch
statement,Promise.finally
can be thought of as athen(x, y)
call with the same handler function forx
andy
(but with the returned and thrown handler result discarded). It does not happen before the call toresolve()
.If you do want this kind of behavior, where some possibly-asynchronous behavior happens before the call to
resolve
, you can return a Promise fromthen
or callresolve
withinnew Promise
with a Promise. These don’t even have to be real Promises, they just have to have to be "Promise-like" or "thenable" by having a callablethen
method.Of course, at that point you can rewrite to avoid the
new Promise
entirely: