为什么 async 函数在 promise 稍后启动后完成执行?

Why does an async function finishes executing after a promise started later?

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

代码如下:

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

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

你很惊讶为什么 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 构造函数创建承诺,而是创建一个 thenable(一个带有 .then() 方法,又名类似 promise 的对象),其行为相同(好吧,真正的 promise 要复杂得多,但为了这个例子,它以这种方式工作)。它看起来像这样:

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 承诺 他们的 return 声明达成之前。这是有道理的,但这也意味着,他们不能 return 传递给 return 相同 承诺对象。他们也必须等待 returned 承诺,以便使他们的 returned 承诺反映其状态。

那么,让我们看看我们的自定义 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 东西是如何与 promise 交错执行的(这里 --> 意味着 enqueues, <-- 表示 logs; microtasks 标记为 μ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