简体   繁体   English

为什么在稍后启动 promise 之后异步 function 完成执行?

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

This is something about the event loop I don't understand.这是关于事件循环的事情,我不明白。

Here's the code:这是代码:

 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');

the result is:结果是:

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

I can't understand why async1 end is printed after promise 2 and promise 3 .我不明白为什么在promise 2promise 3之后打印async1 end

What happens in the event loop that explains this?在解释这一点的事件循环中会发生什么? In what order are these microtasks pushed and popped in the queue?这些微任务在队列中按什么顺序推送和弹出?

You're surprised why async1 end comes after promise2 and promise3 , although it was called before them, and microtasks are executed in the order they are enqueued.你很惊讶为什么async1 end出现在promise2promise3之后,尽管它是在它们之前调用的,并且微任务是按照它们入队的顺序执行的。

However, it really boils down to how many microtasks does it take for the async function to resolve.但是,它实际上归结为async function 需要多少微任务才能解决。

Take a look at this (it's the same code but with 4 raw promises):看看这个(它是相同的代码,但有 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; }

Whoops, async1 end is no longer at the end: it comes before promise4 !哎呀, async1 end不再是最后:它出现promise4之前!

So what does this tell us?那么这告诉我们什么呢? It means that async1 end is logged after 3 microtasks (not counting the ones caused by promiseN 's).这意味着async1 end在 3 个微任务之后被记录(不包括由promiseN引起的那些)。

What takes those 3 microtasks?什么需要这 3 个微任务? Let's inspect:让我们检查一下:

The last one is obvious: the await operator in async1 consumes one.最后一个很明显: async1中的await运算符消耗了一个。

We have two left.我们还剩两个。

To see that, in async2 , instead of creating a promise through the Promise constructor, create a thenable (an object with a .then() method, aka. promise-like object), that acts the same (well, a real promise is much more complex, but for the sake of this example, it works this way). To see that, in async2 , instead of creating a promise through the Promise constructor, create a thenable (an object with a .then .then() method, aka. promise-like object), that acts the same (well, a real promise is复杂得多,但为了这个例子,它是这样工作的)。 I'd look like this:我看起来像这样:

 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; }

However, you may see that something's still wrong.但是,您可能会发现仍然有问题。 promise2 is still called before async2 resolve . promise2async2 resolve之前仍然被调用。

async functions return a promise before their return statement is reached. async函数在到达return语句之前返回 promise。 That makes sense, but that also means, that they can't return the same promise object that is passed return .这是有道理的,但这也意味着,它们不能返回通过return相同promise object 。 They have to await the returned promise too!他们也必须等待返回的 promise!

So, let's see when is our custom then function called:那么,让我们看看我们的自定义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; }

Ah ha, in a new microtask!啊哈,在一个新的微任务中!


We found all the holes, so now we can see how the async thingies execute interlaced with the promise s (here --> means enqueues , <-- means logs ; microtasks are marked with μt ):我们找到了所有的漏洞,所以现在我们可以看到async事物是如何与promise交错执行的(这里-->表示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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM