【问题标题】:Why does an async function finishes executing after a promise started later?为什么一个异步函数在一个承诺稍后开始之后才完成执行?
【发布时间】:2022-04-20 19:06:17
【问题描述】:

这是我不明白的事件循环。

代码如下:

async function async1() {
  console.log('async1 start');
  await async2();
  console.log('async1 end');
}
 
async function async2() {
  console.log('async2 start');
  return new Promise((resolve, reject) => {
    resolve();
    console.log('async2 promise');
  })
}
 
console.log('script start');
 
setTimeout(function() {
  console.log('setTimeout');
}, 0);
 
async1();
 
new Promise(function(resolve) {
  console.log('promise1');
  resolve();
}).then(function() {
  console.log('promise2');
}).then(function() {
  console.log('promise3');
});
 
console.log('script end');

结果是:

script start
async1 start
async2 start
async2 promise
promise1
script end
promise2
promise3
async1 end
setTimeout

我不明白为什么在promise 2promise 3 之后打印async1 end

在解释这一点的事件循环中发生了什么?这些微任务在队列中按什么顺序推送和弹出?

【问题讨论】:

  • 你已经回答了你自己的问题:“push and pop in queue”。
  • 除非这些是通过then()await 的依赖关系,否则事件循环可以在任何await 中断时在它们之间切换。
  • 虽然是一个很好的问题,但最终答案对您没有任何帮助。解决承诺和执行回调的顺序可能取决于实现。如果你想强制正确的顺序,不要使用执行细节,使用承诺机制并在“async1 end”之后强制“promise 2”和“promise 3”日志。 example

标签: javascript async-await es6-promise event-loop


【解决方案1】:

您很惊讶为什么async1 end 出现在promise2promise3 之后,尽管它是在它们之前调用的,并且微任务是按照它们入队的顺序执行的。

但是,这实际上归结为async 函数需要多少微任务才能解决。

看看这个(它是相同的代码,但有 4 个原始承诺):

async function async1() {
  console.log('async1 start');
  await async2();
  console.log('async1 end');
}

async function async2() {
  console.log('async2 start');
  return new Promise((resolve, reject) => {
    resolve();
    console.log('async2 promise');
  })
}

console.log('script start');

setTimeout(function() {
  console.log('setTimeout');
}, 0);

async1();

new Promise(function(resolve) {
  console.log('promise1');
  resolve();
}).then(function() {
  console.log('promise2');
}).then(function() {
  console.log('promise3');
}).then(function() {
  console.log('promise4');
});

console.log('script end');
/* Just to make the console fill the available space */
.as-console-wrapper {
  max-height: 100% !important;
}

哎呀,async1 end 不再是结尾了:它出现在之前 promise4

那么这告诉我们什么?这意味着async1 end 在 3 个微任务之后被记录(不包括由promiseN 引起的那些)。

这 3 个微任务需要什么?让我们检查一下:

最后一个很明显:async1 中的 await 运算符消耗了一个。

我们还剩两个。

为了看到这一点,在async2 中,不是通过Promise 构造函数创建一个promise,而是创建一个thenable(一个带有.then() 方法的对象,又名。promise-like 对象),其作用相同(好吧,一个真正的承诺要复杂得多,但为了这个例子,它是这样工作的)。它看起来像这样:

async function async1() {
  console.log('async1 start');
  await async2();
  console.log('async1 end');
}
 
async function async2() {
  console.log('async2 start');
  
  console.log('async2 promise');
  return {
    then(resolve, reject){
      queueMicrotask(() => {
        resolve();
        console.log('async2 resolve');
      });
      return Promise.resolve()
    }
  };
}
 
console.log('script start');
 
setTimeout(function() {
  console.log('setTimeout');
}, 0);
 
async1();
 
new Promise(function(resolve) {
  console.log('promise1');
  resolve();
}).then(function() {
  console.log('promise2');
}).then(function() {
  console.log('promise3');
}).then(function() {
  console.log('promise4');
});
 
console.log('script end');
/* Just to make the console fill the available space */
.as-console-wrapper {
  max-height: 100% !important;
}

但是,您可能会发现仍有问题。 promise2 仍然在 async2 resolve 之前调用。

async 函数返回一个承诺之前他们的return 语句到达。这是有道理的,但这也意味着,他们不能返回传递给returnsame 承诺对象。他们也必须等待返回的承诺,以使返回的承诺反映其状态。

那么,让我们看看我们的自定义 then 函数何时被调用:

async function async1() {
  console.log('async1 start');
  await async2();
  console.log('async1 end');
}
 
async function async2() {
  console.log('async2 start');
  
  console.log('async2 promise');
  return {
    then(resolve, reject){
      console.log('async2 then awaited')
      queueMicrotask(() => {
        resolve();
        console.log('async2 resolve');
      });
    }
  };
}
 
console.log('script start');
 
setTimeout(function() {
  console.log('setTimeout');
}, 0);
 
async1();
 
new Promise(function(resolve) {
  console.log('promise1');
  resolve();
}).then(function() {
  console.log('promise2');
}).then(function() {
  console.log('promise3');
}).then(function() {
  console.log('promise4');
});
 
console.log('script end');
/* Just to make the console fill the available space */
.as-console-wrapper {
  max-height: 100% !important;
}

啊哈,在一个新的微任务中!


我们找到了所有的漏洞,所以现在我们可以看到async 事物是如何与promises 交错执行的(这里--> 表示enqueues<-- 表示日志;微任务标有μt):

MACROTASK #0

<-- script start

    setTimeout enqueued, but it creates a MACROTASK, so it always comes at last --> MT#1

    async1 called
<-- async1 start
    async2 called
<-- async2 start
    promise executor called synchronously
<-- async2 promise
    resolved promise returned to async2
    async2 execution halted --> μt#1
    async1 execution halted at await
    
    promise executor called synchronously
<-- promise1
    promise1 resolved --> μt#2
    `then` chain built
                                 
<-- script end            

microtask #1
    async2 continues, calls `then` of the returned promise
<-- async2 `then` awaited
    promise's `then` enqueues microtask for calling callback of async2 --> μt#3

microtask #2
    promise2 `then` called
<-- promise2
    promise2 resolved --> μt#4

microtask #3
    called queued callback of promise
<-- async2 resolve
    async2 completes
    promise returned by async2 resolves --> μt#5

microtask #4
    promise3 `then` called
<-- promise3
    promise3 resolved --> μt#6

microtask #5
    async1 continues
<-- async1 end
    async1 completes

microtask #6
    promise4 `then` called
<-- promise4
    promise4 resolved

MACROTASK #1

    timer callback called
<-- setTimeout

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-03-19
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多