JavaScript事件循环:分解谜团
#javascript #编程 #node #softwareengineering

当Node.js运行以下代码时会发生什么?

setTimeout(() => console.log(1), 10)
Promise.resolve().then(() => console.log(2))
console.log(3)

如果您的答案与:
不同

3
2
1

也许您不完全了解JavaScript的执行顺序和事件循环的操作。

不用担心,我会尝试解释。

首先,如果您对什么有疑问:

  • javascript
  • ecmascript
  • javascript Runtime

我建议您在继续之前阅读glossary

现在,让我们解释一下此JavaScript代码执行的每个阶段发生的情况。

主线程

节点在单个线程中从上到下解释JavaScript文件。

运行settimeout()

主线程将解释第一个指令,将其添加到呼叫堆栈中,并将其从 call stack stack 。。

删除。

Visualization of the Main Thread executing the first function call: setTimeout(() => console.log(1), 10)

setTimeout指令用于安排某些毫秒后的函数执行。

此功能是koude1库的一部分,该函数使用该函数创建计时器而无需阻止主 thread

The main thread executes the setTimeout function, which starts a timer in a new thread, through a library called libuv. At the end of the timer, the callback will be added to the macro-task queue

启动计时器,主 thread 将从 call call stack stack 。。。

Main Thread pops from call stack

在间隔结束时,计时器将添加 settimeout 函数的回调 函数P>

运行Promise.resolve()。然后()

计时器 libuv 库等待10ms时, main thread 将解释文件的下一行。

Main thread consumes the next instruction from the call stack

这次的说明是

Promise.resolve().then(() => console.log(2))

thread 将执行函数Promise.resolve()。然后()

Promise 是代表异步操作的完成或失败的对象。

通过无需任何参数调用resolve()函数,我们正在声明 promise , 都可以返回任何值,但这还可以。

Executing the function Promise.resolve()

目前,我们对的行为更感兴趣。

通过将() => console.log(2)作为我们的 Promise 的回调,我们告诉节点,一旦成功完成 Promise ,请立即执行此代码。

换句话说,我们说的是,一旦执行了 promise resolve()方法,节点应执行我们的 console.log(2)指令。

但是,这并不是它的工作方式。

每个 promise 回调立即发送到一个特殊队列,称为 Micro Tasks Queue queue

Pushes Promise callback to MicroTasks queue

回顾

这是脚本执行的当前状态:

Current state of the script execution

到目前为止发生的一切肯定花了不到10毫秒的时间,这就是为什么计时器尚未将console.log(1)的指示添加到宏观任务队列的原因。

但是,通过使用 libuv 主线程可以以非阻滞方式继续正常工作。

好吧,您可能想知道:

事件循环

在整个过程中,通过文件中的每行解释,事件循环执行了一个非常重要的,尽管重复的功能。

  • 检查 call stack stack 是否为空。

Event Loop asking if the Call Stack is empty

您可以看到,答案总是: no!

在执行此脚本期间,任何时间都没有 call stack 空,所以我们的朋友事件循环将继续等待。

清空调用堆栈

现在,主线程解释文件的最后指令。

Main Thread consumes the last call from the call stack

这是一个简单的指令,在 console 上显示一个值,其结果是:

3

,第一次, call stack 是空!

事件循环

现在,事件循环的最期待的时刻,它有能力行动的时刻!

call stack stack 为空!

时,它只会验证其他队列

在每个循环中,它将:

  • 处理 micro任务中的所有任务队列
    • 将它们添加到 call stack
  • 宏观任务队列的过程1个任务
    • 将其添加到调用堆栈
  • 等待调用stack 为空
  • 重复

主线程在主要上下文中执行所有指令。

现在,继续执行示例代码:

微型任务

调用stack 变为空时,这意味着主线程没有执行任何内容。

然后,事件循环消耗从 micro任务队列中的所有任务,并将它们添加到 call stack stack
Event Loop consuming function from the micro tasks queue

接下来,主线程消耗调用stack 并执行的指令。
Main Thread consuming call stack

console.log(2) // Writes 2 to the console

现在,调用stack 再次变为空。

然后,事件循环 Micro Tasks中寻找更多任务队列。

Current state of the application

当它是空的,它在 Micro Task Queue 中完成工作,并开始消耗宏观任务队列Queue

宏任务

现在,假设10毫秒间隔已经传递,并且 timer 已将控制台插入了台。 >事件循环将从宏任务队列将1个指令转移到调用stack

Event Loop consuming Macro Tasks queue

然后,主线程消耗 call stack stack 的最后一个指令并执行它。
Main Thread consuming the Call Stack

console.log(1) // Writes 1 to the console

重要的一点:如果微观任务队列中仍然有指示,则将处理这些指示。但是,由于所有内容都是空的,因此程序执行将朝着末尾。

这就是为什么代码:

setTimeout(() => console.log(1), 10)
Promise.resolve().then(() => console.log(2))
console.log(3)

将导致:

3
2
1

我们已经到达末尾-Arlindo Cruz

现在您了解了JavaScript场景后面会发生什么。 事件循环管理 micro macro 任务的队列,因此,确保在主要背景下和谐地执行异步指令线程。

了解其工作原理有助于我们编写更有效的代码并更好地预测应用程序的行为。

下次您编写JavaScript代码时,希望您还记得 event loop 的一切发生的一切

稍后再见!

词汇表

JavaScript

这是一种支持多个编程范式的高级,动态,解释的编程语言(功能,命令,面向对象)。

这是您想做的事情与计算机执行的操作之间的“媒介”。

Ecmascript

这是一个set of rules,定义了JavaScript的工作方式,它定义了语言标准(语法,数据类型,控制结构和操作员),而JavaScript是这些标准的实现。

如果您想更好地了解,请阅读this article

JavaScript运行时

引擎执行JavaScript代码。

编写JavaScript代码时,您可以编写指令(遵循由ecmascript定义的规则),但是要执行这些说明,您需要 Run> Runtime

好像JavaScript是食谱,运行时是执行食谱的厨师。

NodeV8SpiderMonkey是世界上最著名的Javascript Runtimes