In this example:
const foo = async () => {
const a = await (Promise.resolve(1).then(v => console.log(v)));
console.log('foo');
return 1;
}
const value = foo();
value
.then(a => a + 1)
.then(a => console.log('then 1', a));
value
.finally(() => 'finally')
.then(a => console.log('then after finally'));
value
.then(a => a + 2)
.then(a => console.log('then 2', a));
In this example:
const foo = async () => {
const a = await (Promise.resolve(1).then(v => console.log(v)));
console.log('foo');
return 1;
}
const value = foo();
value
.then(a => a + 1)
.then(a => console.log('then 1', a));
value
.finally(() => 'finally')
.then(a => console.log('then after finally'));
value
.then(a => a + 2)
.then(a => console.log('then 2', a));
The output is:
1
foo
then 1 2
then 2 3
then after finally
What I don't get is why does "then after finally" runs after "then 2 3"? And not after "then 1 2"? Why is it places at the very end of a microtask queue? As far as I understand all tree "branches" of value are independent, but it seems like the "finally" waits until all "then"s (1st and 2nd branch) are executed.
But if I add some more "then"s like this:
const foo = async () => {
const a = await (Promise.resolve(1).then(v => console.log(v)));
console.log('foo');
return 1;
}
const value = foo();
value
.then(a => a + 1)
.then(a => console.log('then 1', a))
.then(a => console.log('then 3', a))
.then(a => console.log('then 5', a))
.then(a => console.log('then 7', a));
value
.finally(() => 'finally')
.then(a => console.log('then after finally'));
value
.then(a => a + 2)
.then(a => console.log('then 2', a))
.then(a => console.log('then 4', a))
.then(a => console.log('then 6', a))
.then(a => console.log('then 8', a));
it doesn't seem like "finally" waits for anything:
1
foo
then 1 2
then 2 3
then 3 undefined
then 4 undefined
then 5 undefined
then after finally
then 6 undefined
then 7 undefined
then 8 undefined
Can someone please explain how it all works in this example?
Share Improve this question edited Mar 26 at 17:43 Barmar 784k57 gold badges548 silver badges660 bronze badges asked Mar 26 at 7:51 b3rryb3rry 951 silver badge9 bronze badges 3 |1 Answer
Reset to default 0What I don't get is why does "then after finally" runs after "then 2 3"? And not after "then 1 2"? Why is it places at the very end of a microtask queue?
It takes extra steps to resolve the promise returned by the finally
method. As a result, the callback of the then
method called on the promise returned by the finally
method gets called at the end, after all the other fulfillment handlers have executed.
Following points explain the 1st code example from the point where the promise returned by the foo
function is fulfilled with the value of 1
:
After the
value
promise is fulfilled with the value of1
, its fulfillment handlers are added to the microtask queue to execute them:mictotask queue: [ job(a => a + 1), // <-- front of the queue job(() => 'finally'), job(a => a + 2) ] output: 1 foo
Processing the 1st microtask queue fulfills the promise returned by
value.then(...)
with the fulfillment value of2
. This also results in a new microtask to execute the fulfillment handler ofvalue.then(...).then(a => console.log('then 1', a))
:mictotask queue: [ job(() => 'finally'), job(a => a + 2), job(a => console.log('then 1', a)) ] output: 1 foo
The next microtask is the callback of the
finally
method. Its callback returns the string"finally"
which is wrapped in a new promise and returned. This results in the promise returned by thefinally
method getting resolved to the promise returned by its callback function.Following code is a simplified version of how the
finally
method is implemented:// Simulating Promise.finally() internal behavior function promiseFinally(promiseOnWhichFinallyCalled, finallyCb) { return promiseOnWhichFinallyCalled.then( (value) => Promise.resolve(finallyCb()).then(() => value), (reason) => Promise.resolve(finallyCb()).then(() => { throw reason }) ); }
Note that the promise returned by the
finally
method is the one returned bypromiseOnWhichFinallyCalled.then(...)
(let's call itfinallyPromise
). And its callback also returns a promise, i.e.,Promise.resolve(...).then(...)
(let's call itfinallyCallbackPromise
). This is where thefinallyPromise
is resolved to thefinallyCallbackPromise
, adding extra steps to the fulfillment of thefinallyPromise
. This allows other fulfillment handlers to run before'then after finally'
is logged to the console.Coming back to the microtask queue, processing the
finally
callback queues another microtask to resolve the promise returned by thefinally
method to the promise returned by its callback:mictotask queue: [ job(a => a + 2), job(a => console.log('then 1', a)), job(resolve(finallyPromise, finallyCallbackPromise)) ] output: 1 foo
Processing the next microtask fulfills the promise returned by the
then
method with the value of3
. This results in a new microtask to execute its fulfillment handler:mictotask queue: [ job(a => console.log('then 1', a)), job(resolve(finallyPromise, finallyCallbackPromise)), job(a => console.log('then 2', a)) ] output: 1 foo
Processing the next microtask logs
then 1 2
to the console:mictotask queue: [ job(resolve(finallyPromise, finallyCallbackPromise)), job(a => console.log('then 2', a)) ] output: 1 foo then 1 2
Processing the next microtask enqueues a microtask to resolve the promise returned by the
finally
method with the same fulfillment value as the original promise, i.e.1
:mictotask queue: [ job(a => console.log('then 2', a)), job(resolve(finallyPromise)) ] output: 1 foo then 1 2
Processing the next microtask logs
then 2 3
to the console:mictotask queue: [ job(resolve(finallyPromise)) ] output: 1 foo then 1 2 then 2 3
Finally, after the fulfillment of the promise returned by the
finally
method, its fulfillment handler is enqueued in the microtask queue to logthen after finally
to the console:mictotask queue: [ job(a => console.log('then after finally')) ] output: 1 foo then 1 2 then 2 3
The final output after the last microtask is processed:
1 foo then 1 2 then 2 3 then after finally
Following the same steps for the 2nd code example to understand its output. The key thing to understand is how the promise returned by the finally
method is resolved.
Also keep in mind that the real-world code shouldn't rely on the execution order of the promises in separate promise chains.
a
is, nor to print it. Also in your second snippet, the callbacks that print 7 and 8 don't seem relevant to your question. Finally, can you focus the question on one version of your code? Otherwise it becomes too broad. – trincot Commented Mar 26 at 8:31finally
implementation in terms ofthen
like this one and you see where extra microtasks might come from. – Bergi Commented Mar 26 at 22:45