Skip to content

Commit 7252040

Browse files
committed
sync with upstream: 8365ea7
1 parent 9fd548d commit 7252040

3 files changed

Lines changed: 23 additions & 102 deletions

File tree

Lines changed: 23 additions & 102 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11

2-
# Microtasks 和事件循环
2+
# 微任务(Microtasks
33

44
Promise 的处理程序(handlers)`.then``.catch``.finally` 都是异步的。
55

6-
即便一个 promise 立即被 resolve,`.then``.catch``.finally` **下面**的代码也会在这些处理程序之前被执行。
6+
即便一个 promise 立即被 resolve,`.then``.catch``.finally` *下面*的代码也会在这些处理程序之前被执行。
77

88
示例代码如下:
99

@@ -21,9 +21,9 @@ alert("code finished"); // 该警告框会首先弹出
2121

2222
为什么 `.then` 会在之后被触发?这是怎么回事?
2323

24-
## Microtasks(微任务
24+
## 微任务队列(Microtasks queue
2525

26-
异步任务需要适当的管理。为此,JavaScript 标准指定了一个内部队列 `PromiseJobs`,通常被称为 “microtask 队列”(v8 术语)。
26+
异步任务需要适当的管理。为此,JavaScript 标准规定了一个内部队列 `PromiseJobs`,通常被称为 “微任务队列”(v8 术语)。
2727

2828
[规范](https://tc39.github.io/ecma262/#sec-jobs-and-job-queues)中所述:
2929

@@ -52,78 +52,6 @@ Promise.resolve()
5252

5353
现在代码就是按照预期执行的。
5454

55-
## 事件循环
56-
57-
浏览器内的 JavaScript 以及 Node.js 的执行流程都是基于**事件循环**的。
58-
59-
“事件循环”是引擎休眠并等待事件的过程。只有当事件发生时才会处理它们,然后重新进入休眠状态。
60-
61-
事件可能来自外部,例如用户操作,或者也可能来自于内部任务的结束信号。
62-
63-
例如下面的事件:
64-
- `mousemove`,用户移动了他们的鼠标。
65-
- `setTimeout` 处理程序将被调用。
66-
- 一个外部的 `<script src="...">` 被加载完成,准备执行。
67-
- 网络操作,例如 `fetch` 被完成。
68-
- 等等。
69-
70-
事情发生了 —— 引擎处理它们 —— 并等待更多的事情发生(在休眠时消耗接近零 CPU)。
71-
72-
![](eventLoop.png)
73-
74-
如你所见,这里也有一个队列。所谓的 “macrotask(宏任务)队列”(v8 术语)。
75-
76-
当一个事件发生时,如果引擎正忙,它的处理程序就会进入这个队列排队等待执行。
77-
78-
例如,当引擎忙于处理网络 `fetch` 请求时,用户可能会移动他们的鼠标,这会触发 `mousemove`,或者相应的 `setTimeout` 等等,正如上图所示。
79-
80-
来自 macrotask 队列的事件基于“先进 —— 先处理”的原则被处理。当浏览器引擎完成 `fetch` 时,它会接着处理 `mousemove` 事件,然后是 `setTimeout` 处理程序,等等。
81-
82-
到目前为止,很简单,对吧?引擎正忙,所以其他任务需要排队。
83-
84-
现在重要的东西来了。
85-
86-
**Microtask 队列的优先级高于 macrotask 队列。**
87-
88-
换句话说,引擎会首先执行所有的 microtask,然后执行 macrotask。
89-
90-
例如,下面的代码:
91-
92-
```js run
93-
setTimeout(() => alert("timeout"));
94-
95-
Promise.resolve()
96-
.then(() => alert("promise"));
97-
98-
alert("code");
99-
```
100-
101-
顺序是什么?
102-
103-
1. `code` 第一个出现,因为它是一个常规的同步调用。
104-
2. `promise` 第二个出现,因为 `.then` 通过 microtask 队列被执行,并在当前代码之后运行。
105-
3. `timeout` 最后出现,因为它来自于 macrotask 队列。
106-
107-
可能会发生这样的情况,在处理 macrotask 时,新的 promise 被创建。
108-
109-
或者,反过来说,一个 microtask 调度了一个 macrotask(例如 `setTimeout`)。
110-
111-
例如,这里的 `.then` 调度了一个 `setTimeout`
112-
113-
```js run
114-
Promise.resolve()
115-
.then(() => {
116-
setTimeout(() => alert("timeout"), 0);
117-
})
118-
.then(() => {
119-
alert("promise");
120-
});
121-
```
122-
123-
当然,`promise` 会首先出现,因为 `setTimeout` 宏任务在一个低优先级的 macrotask 队列中进行等待。
124-
125-
作为一个合乎逻辑的结果,只有当 promise 给引擎一个“空闲时间”时才处理 macrotask。因此,如果我们有一系列不等待任何事情的 promise 处理程序,它们会被一个接一个地执行,`setTimeout`(或者用户操作处理程序)永远不能在它们之间运行。
126-
12755
## 未处理的 rejection
12856

12957
还记得 <info:promise-error-handling> 一章中“未处理的 rejection”事件吗?
@@ -132,60 +60,53 @@ Promise.resolve()
13260

13361
**“未处理的 rejection”是指在 microtask 队列结束时未处理的 promise 错误。**
13462

135-
例如,考虑以下的代码
63+
正常来说,如果我们期望一个错误,我们会添加 `.catch` 到 promise 链上去处理它
13664

13765
```js run
13866
let promise = Promise.reject(new Error("Promise Failed!"));
67+
*!*
68+
promise.catch(err => alert('caught'));
69+
*/!*
13970

140-
window.addEventListener('unhandledrejection', event => {
141-
alert(event.reason); // Promise Failed!
142-
});
71+
// 没有错误
72+
window.addEventListener('unhandledrejection', event => alert(event.reason));
14373
```
14474

145-
我们创建一个 rejected 的 `promise` 并且没有处理错误。所以我们有了一个 “未处理的 rejection”(也会在控制台打印出来)。
146-
147-
如果我们添加了 `.catch`,我们就不会见到这个“未处理的 rejection”,如下所示:
75+
......但是如果我们忘记添加 `.catch`,那么微任务队列清空后,JavaScript 引擎会触发以下事件:
14876

14977
```js run
15078
let promise = Promise.reject(new Error("Promise Failed!"));
151-
*!*
152-
promise.catch(err => alert('caught'));
153-
*/!*
15479

155-
// 没有错误
80+
// Promise Failed!
15681
window.addEventListener('unhandledrejection', event => alert(event.reason));
15782
```
15883

159-
如下所示,现在我们假设会在 `setTimeout` 之后抓住这个错误
84+
如果我们迟点在处理这个错误会怎样?比如
16085

16186
```js run
16287
let promise = Promise.reject(new Error("Promise Failed!"));
16388
*!*
16489
setTimeout(() => promise.catch(err => alert('caught')));
16590
*/!*
16691

167-
// 错误:Promise Failed!
92+
// Error: Promise Failed!
16893
window.addEventListener('unhandledrejection', event => alert(event.reason));
16994
```
17095

171-
现在再次出现“未处理的 rejection”。为什么?因为 `unhandledrejection` 在 microtask 队列完成时才会被生成。而引擎会检查 promise,如果其中的任何一个出现 “rejected” 状态,`unhandledrejection` 事件就会被触发。
172-
173-
在这个例子中,`.catch` 当然被 `setTimeout` 触发器添加了,只是会在 `unhandledrejection` 出现之后被执行。
96+
现在,如果你运行以上的代码,我们先会看到 `Promise Failed!` 的消息,然后才是 `caught`
17497

175-
## 总结
98+
如果我们并不了解微任务队列,我们可能想知道:“为什么 `unhandledrejection` 的处理程序会运行?我们已经去捕捉(catch)这个错误了!”
17699

177-
- Promise 处理始终是异步的,因为所有 promise 操作都被放入内部的 “promise jobs” 队列执行,也被称为 “microtask 队列”(v8 术语)
100+
但是现在我们知道 `unhandledrejection` 在 microtask 队列完成时才会被生成:引擎会检查 promise,如果其中的任何一个出现 “rejected” 状态,`unhandledrejection` 事件就会被触发
178101

179-
**因此,`.then/catch/finally` 处理程序总是在当前代码完成后才被调用。**
102+
在这个例子中,被添加到 `setTimeout``.catch` 也会执行,只是会在 `unhandledrejection` 事件出现之后才执行,所以并没有改变什么(没有发挥作用)。
180103

181-
如果我们需要确保一段代码在 `.then/catch/finally` 之后被执行,最好将它添加到 `.then` 的链式调用中。
182-
183-
- 还有一个 “macrotask 队列”,用于保存各种事件,网络操作结果,`setTimeout` —— 调度的方法,等等。这些也被称为 “macrotasks(宏任务)”(v8 术语)。
104+
## 总结
184105

185-
引擎使用 macrotask 队列按出现顺序处理它们
106+
Promise 处理始终是异步的,因为所有 promise 操作都被放入内部的 “promise jobs” 队列执行,也被称为 “微任务队列”(v8 术语)
186107

187-
**Macrotasks 在当前代码执行完成并且 microtask 队列为空时执行**
108+
**因此,`.then/catch/finally` 处理程序总是在当前代码完成后才被调用**
188109

189-
换句话说,它们的优先级较低
110+
如果我们需要确保一段代码在 `.then/catch/finally` 之后被执行,最好将它添加到 `.then` 的链式调用中
190111

191-
所以顺序是:常规代码,然后是 promise 处理程序,然后是其他的一切,比如事件等等
112+
在大部分 JavaScript 引擎中(包括浏览器和 Node.js),微任务的概念与“事件循环”和“宏任务”紧密联系。由于这些概览跟 promises 没有直接关系,它们被涵盖在本教程另外的章节 <info:event-loop>
739 Bytes
Loading
1.82 KB
Loading

0 commit comments

Comments
 (0)