事件循环排队顺序

时间:2021-06-06 21:22:56

标签: javascript node.js event-loop

我正在尝试了解以下代码的顺序(我使用 node 在本地运行)。

async function bfunc() {
  console.log("2 - b func");
}

async function afunc() {
  await bfunc();                           // rest of function pushed to event queue
  console.log('4 - a func');
}

const prom = new Promise((resolve, reject) => {
  console.log("1 - inside promise");
  resolve('5 - foo');
  afunc();
}).then((value) => {                       // this is also pushed to the event queue
  console.log(value);
});

console.log("3 - last thing");

打印出来:

1 - inside promise
2 - b func
3 - last thing
4 - a func
5 - foo

我相信我的大部分困惑源于关于何时将事件推送到事件队列的不确定性。

  1. 上面评论的 2 个地方是否正确推送了辨别事件?

  2. 在await调用的情况下,是异步函数本身(包括其推送到事件队列的堆栈环境?

  3. 为什么 '5 - foo' 不在 '4 - a func' 之前打印?我读到here

<块引用>

挂起的承诺可以用一个值来实现,也可以用一个原因(错误)来拒绝。当这些选项中的任何一个发生时,由 promise 的 then 方法排队的相关处理程序将被调用

所以我认为这会使队列中的第一个事件成为 then 函数的处理程序。

1 个答案:

答案 0 :(得分:1)

这里的妙语是:Promise 仅在当前 Javascript 序列完成执行并将控制权返回给事件系统后,才会通知侦听器解析/拒绝。请继续阅读以了解这意味着什么以及它如何影响您的代码的更多详细信息。

让我们在您的代码中添加行号,以便我们更轻松地讨论:

1     async function bfunc() {
2       console.log("2 - b func");
3     }
4    
5     async function afunc() {
6       await bfunc();         // rest of function pushed to event queue
7       console.log('4 - a func');
8     }
9    
10    const prom = new Promise((resolve, reject) => {
11      console.log("1 - inside promise");
12      resolve('5 - foo');
13      afunc();
14    }).then((value) => {     // this is also pushed to the event queue
15      console.log(value);
16    });
17    
18    console.log("3 - last thing");

事件顺序

  1. 第 10 行执行。这会导致 const promise = new Promise(...).then(...) 开始执行
  2. 作为 new Promise(...) 构造函数的一部分,它调用 promise 的执行器函数(您传递给构造函数的回调)。立即同步执行。
  3. 由于同步调用承诺执行器,您会看到执行第 11 行的输出 1 - inside promise 的第一行。
  4. 然后执行第 12 行,promise 的状态更改为已完成,值为 '5 - foo'
  5. 然后第 13 行执行并调用 afunc()
  6. 即使 afunc() 被声明为 async,它仍然开始同步执行其函数体,因此它立即在第 6 行调用 await bfunc()。它执行 bfunc() 并等待它返回来解决的承诺。
  7. 作为执行 bfunc() 的一部分,您会在控制台的第 2 行看到 2 - b func
  8. bfunc() 返回一个promise(因为它被声明为async)并且由于它里面没有await 并且没有从它返回promise,这个promise 的状态被提前到了fulfilled。这在 Promise 作业队列中插入一个作业,以在将来使用此 Promise 上的 .then() 调用任何已注册的 await 处理程序或代码。
  9. 由于在 await 内部的 await bfunc() 中有一个 afunc(),所以 afunc() 的执行被暂停,直到它收到通知,通过调用 {{1 }} 已经解决了。该通知尚未到来,因为它正在 Promise 作业队列中等待。同时,bfunc() 立即返回一个处于待定状态的承诺。
  10. promise executor 返回,新创建的 promise 可用。
  11. 由于这个新创建的 Promise 现已完成,它在 Promise 作业队列中插入一个作业以调用任何注册的 afunc() 处理程序或代码,在此承诺上使用 .then()未来的时间。
  12. 然后,第 14 行对新创建的承诺执行 await。这会为之前创建的承诺注册一个 .then(...) 处理程序,然后返回一个新的承诺。
  13. 该新承诺被分配给变量 .then()
  14. 然后执行第 18 行,您会看到 prom 作为您的第三行输出。
  15. 那么当前的 Javascript 链就完成了,不再需要同步执行,因此它将控制权返回给事件系统。
  16. 事件系统在获得控制权后首先要做的事情之一是检查 Promise 作业队列中是否有任何挂起的任务。 Promise 作业队列中的第一项是在第 6 行等待的调用 3 - last thing 的已解决承诺。由于承诺已解决,bfunc() 的执行被恢复,您会看到 {{1}在输出中。 afunc() 返回它解决了它的承诺,但没有人在听它的承诺,所以在这个 Javascript 链中没有什么可做的,控制权回到事件系统。
  17. 再一次,它在检查其他事情之前检查 Promise 作业队列,并且再次有一个挂起的 Promise 任务。这会从 4 - a func 承诺调用 afunc() 处理程序,您会在输出中看到 .then()

主要要点

    prom 函数中的
  1. 5 - foo 暂停该函数的执行,并立即将函数的承诺返回给调用者。
  2. 当 promise 被解决或拒绝时,它们总是通过 Promise 作业队列从 awaitasyncawait.then() 通知完成或错误,该队列仅在以下情况下为作业提供服务当前的 Javascript 链已将控制权返回给事件系统。因此,连续的代码行会一直运行,直到它们将控制权返回给事件系统。这就是为什么当你在上面的描述中看到一个 promise 被解析时,它会将一个作业插入到 Promise 作业队列中,并且该作业不会立即运行。人们常说,promise 总是“在事件循环的下一个滴答上异步地解决或拒绝”。虽然有些人不同意“事件循环的下一个滴答声”的确切含义,但这个概念是正确的,承诺仅在当前 Javascript 线程完成执行并将控制权返回给事件系统后才通知解决/拒绝强>。然后,事件系统可以查看它的各种队列以确定下一步要做什么,它查看的第一个队列之一是 Promise 作业队列,其中 promise 解决和拒绝事件正在等待通知正在侦听它们的人(使用 { {1}} 或 try/catch.catch()
  3. await 承诺链完全独立于此代码中的其他承诺链。这就是我所说的“即发即弃”承诺链,因为没有代码观察它是否成功完成或有错误。这几乎总是一个编程错误,因为如果它拒绝,则没有针对该拒绝的处理程序,您将收到系统未处理的拒绝错误(类似于同步代码的未捕获异常)。
  4. 此外,当您必须拥有多个独立的 Promise 链(其中包含具有不确定时序的真正异步操作)时,一个 Promise 链相对于另一个 Promise 链的完成方式是不可预测的。此特定代码将始终以相同方式运行,因为您的 promise 或 .then() 函数背后没有真正的异步操作,因此所有这些代码都有确定的时间,但在实际代码中并非如此。因此,如果您需要代码以特定顺序运行,您可以连接两条链并使一个链依赖另一个链,以便您可以控制顺序。

您的问题

<块引用>

上面评论的 2 个地方是否正确?

有点。首先,请记住已完成的 Promise 通过 Promise 作业队列通知 .catch()afunc()。并且,请记住 async 函数中的 await 会暂停该函数的执行并立即返回一个新的 Promise。因此,第 6 行的第一个注释更准确地说是“afunc() 执行暂停,等待 .then() 返回的承诺的通知”。第 14 行的第二条注释是“.then() 处理程序注册并等待来自第 10 行创建的承诺的通知”。

<块引用>

在await调用的情况下,是异步函数本身(包括其推送到事件队列的堆栈环境?

不是真的。使用与生成器相同的过程,await 函数可以在某个执行点暂停,保留其所有当前状态。在这种情况下,该函数已通过 async 返回的承诺注册对已履行或拒绝状态链感兴趣,并且当它收到该通知时,它将继续执行该函数(如果承诺已履行)或将拒绝它自己的承诺(如果承诺被拒绝并且没有针对该拒绝的本地处理程序)。

因此,函数状态(它是 JS 解释器中的一个对象)只是坐在那里等待 bfunc() 返回的承诺通知。它本身并没有在事件队列中放入任何东西。它在 async 承诺上注册了对状态更改的兴趣,然后当 bfunc() 承诺解决或拒绝时,将向 Promise 作业队列中插入一些内容以通知注册的侦听器。

<块引用>

为什么 '5 - foo' 不在 '4 - a func' 之前打印?

正如我之前在外卖 #3 和 #4 中所说的,这里有两个独立的 promise 链,因为没有监控 bfunc() 的完成情况。最后两行之间的顺序与承诺状态更改被添加到每个承诺链的承诺作业队列的确切时间有关。如果您关心这个顺序并且这些是真正的异步操作,您就必须以不同的方式对其进行编程,这样您就不会拥有两个独立且不协调的承诺链,每个链都有自己不可预测的时间。

您可以在我对上述步骤的描述中找到确切的操作顺序,但这实际上并不是那么重要,因为如果没有真正的异步操作和真正的异步操作,您不会使用这样的代码,这些的顺序两个独立的承诺链是不可预测的,因为它们解决或拒绝彼此承诺的时间是不可预测的。因此,尝试理解这种详细程度并不值得,因为无论如何您都不应该在异步编程中依赖它。如果您关心排序,则将两条链链接起来,以便您的代码决定排序,而不是时间的细节。