最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - Why does "finally" delays the execution of "then" attached to it? - Stack Overf

programmeradmin4浏览0评论

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
  • Please simplify the code: it doesn't seem relevant what the value of 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:31
  • "As far as I understand all tree "branches" of value are independent" - yes. So you should not care about the order in between them – Bergi Commented Mar 26 at 9:45
  • Consider a finally implementation in terms of then like this one and you see where extra microtasks might come from. – Bergi Commented Mar 26 at 22:45
Add a comment  | 

1 Answer 1

Reset to default 0

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?

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:

  1. After the value promise is fulfilled with the value of 1, 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
    
  2. Processing the 1st microtask queue fulfills the promise returned by value.then(...) with the fulfillment value of 2. This also results in a new microtask to execute the fulfillment handler of value.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
    
  3. 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 the finally 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 by promiseOnWhichFinallyCalled.then(...) (let's call it finallyPromise). And its callback also returns a promise, i.e., Promise.resolve(...).then(...) (let's call it finallyCallbackPromise). This is where the finallyPromise is resolved to the finallyCallbackPromise, adding extra steps to the fulfillment of the finallyPromise. 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 the finally 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
    
  4. Processing the next microtask fulfills the promise returned by the then method with the value of 3. 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
    
  5. 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
    
  6. 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
    
  7. 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
    
  8. Finally, after the fulfillment of the promise returned by the finally method, its fulfillment handler is enqueued in the microtask queue to log then 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.

发布评论

评论列表(0)

  1. 暂无评论