JavaScript - Microtasks
JavaScript 中的 microtasks 是小型函数,在创建它们的函数或程序代码完成后执行,并且在 JavaScript 执行栈为空时执行。Microtasks 在任何 macrotasks(如 setImmediate() 和 setTimeout())之前执行。Microtasks 用于实现诸如 promises 等功能。
JavaScript 是一种单线程编程语言。但是,你可以使用 promises、callbacks 和异步函数来并行运行 JavaScript 代码。
JavaScript 基于事件循环运行代码。事件循环负责执行代码、处理它、收集事件数据并执行子任务。
让我们先了解 JavaScript 事件循环。
JavaScript 事件循环
事件循环逐行执行 JavaScript 代码。它将代码添加到调用栈(一个用于执行的队列)中。
JavaScript 包含两种类型的队列来执行任务。
- Microtasks 队列
- Macrotasks 队列
当调用栈队列为空时,事件循环执行 microtask 队列中的所有任务。之后,它执行 Macro task 队列中的所有函数和代码。
在了解 microtasks 和 macrotasks 之后,我们将更深入地理解 JavaScript 代码执行。
JavaScript 中的 Microtasks 是什么?
在 JavaScript 中,microtask 是由 promise 或异步函数产生的较短函数,并在稍后被消费。
以下是 Microtasks 的列表。
- Promise callback
- queueMicrotask
无论你将什么 callback function 作为 then()、catch() 或 finally() 方法的参数传递,在消费 promise 代码时,它都会被添加到 microtask 队列中。
首先,JavaScript 运行引擎执行整个脚本,将主线程的代码添加到调用栈,并将 microtasks 添加到 microtask 队列中。当调用栈的所有任务执行完成后,它完成 microtask 队列中所有任务的执行。
让我们通过下面的示例来理解它。
示例
在下面的代码中,我们在脚本开始时打印开始消息,在脚本结束时打印结束消息。
在中间,我们定义了一个立即 resolved 的 promise。之后,我们使用 then() 方法消费 promise 并打印 promise 返回的消息。
<html>
<body>
<div id = "output"> </div>
<script>
const output = document.getElementById("output");
output.innerHTML += "The start of the code execution. <br>";
// 创建 promise
let promise = new Promise(function (resolve, reject) {
resolve("The promise is resolved. <br>");
});
// 消费 promise 代码
promise.then(function (result) {
output.innerHTML += result;
});
output.innerHTML += "The end of the code execution. <br>";
</script>
</body>
</html>
输出
The start of the code execution. The end of the code execution. The promise is resolved.
上面的代码输出中发生了一些有趣的事情。
在输出中,你可以看到它首先打印开始、结束消息,最后打印 promise 消息。
现在,问题是为什么会这样。答案是 then() 方法的 callback function 被添加到 microtask 队列中,只有在调用栈为空时才执行。
什么是 Macrotasks?
现在,让我们了解一下 Macrotasks 是什么。
Macrotasks 也是一种短函数,它会在调用栈和 microtask queue 中所有代码执行完成后被执行。
JavaScript 运行时引擎将 macro tasks 添加到 microtask queue 中。
以下方法的回调函数会被添加到 Macrotask queue 中。
- setTimeout
- setInterval
- setImmediate
让我们通过下面的示例来理解 Macrotasks。
示例
在下面的代码中,我们添加了开始消息、setTimeout() 方法和结束消息。
在 setTimeout() 方法中,我们将回调函数作为第一个参数传递,它会在输出中打印消息,并设置 0 秒延迟。
<html>
<body>
<div id = "demo"> </div>
<script>
let output = document.getElementById("demo");
output.innerHTML += "The start of the code execution.<br>";
setTimeout(function () {
output.innerHTML += "The code execution is being delayed for 0 seconds. <br>";
}, 0);
output.innerHTML += "The end of the code execution.<br>";
</script>
</body>
</html>
输出
The start of the code execution. The end of the code execution. The code execution is being delayed for 0 seconds.
上述代码的输出也很有趣。
它首先打印开始消息,然后是结束消息,最后是来自 setTimeout() 方法的消息。
在这里,我们为 setTimeout() 方法设置了 0 延迟,但它仍然在最后执行,因为 JavaScript 运行引擎将回调函数添加到 macro task queue 中。
让我们通过下面的示例一起理解 microtask 和 macro tasks。
示例
在下面的代码中,我们添加了带有 0 延迟的 setTimeout() 方法,其回调函数打印消息。
之后,我们使用 Promise() 构造函数定义了一个 promise,并使用 then() 方法消费 promise 代码。
最后,我们打印了结束消息。
<html>
<body>
<div id = "output"> </div>
<script>
let output = document.getElementById("output");
output.innerHTML += "Start <br>";
setTimeout(function () { // Macro task (宏任务)
output.innerHTML += "In setTimeOut() method. <br>";
}, 0);
let promise = new Promise(function (resolve, reject) {
resolve("In Promise constructor. <br>");
});
promise.then(function (value) { // Micro tasks (微任务)
output.innerHTML += value;
});
output.innerHTML += "End <br>";
</script>
</body>
</html>
输出
Start End In Promise constructor. In setTimeOut() method.
让我们理解上述示例的输出。
首先,由于 JavaScript 调用栈,它打印开始消息。
之后,它将 setTimeout() 方法的回调函数添加到 Macrotask queue 中。
接下来,它将 then() 方法的回调函数添加到 Microtask queue 中。
接下来,它执行代码的最后一行并打印 End 消息。
现在,调用栈为空。因此,它执行 Microtask queue 中的所有任务,完成 then() 方法回调函数的执行。
现在,调用栈和 Microtask queue 都为空。因此,它执行 Macrotask queue 中的所有任务,并完成 setTimeout() 方法回调函数的执行。
本章演示了 JavaScript 运行引擎如何执行代码。如果您想改变代码的执行顺序,可以注意使用 micro 和 macro tasks。