# 异步编程、PromiseA+规范、Event loop 机制

作者:万峥嵘 时间:2020-09-16

# 1. 异步编程

# 1.1、异步的概念

异步(Asynchronous, async)是与同步(Synchronous, sync)相对的概念。

在我们学习的传统单线程编程中,程序的运行是同步的(同步不意味着所有步骤同时运行,而是指步骤在一个控制流序列中按顺序执行)。而异步的概念则是不保证同步的概念,也就是说,一个异步过程的执行将不再与原有的序列有顺序关系。 简单来理解就是:同步按你的代码顺序执行,异步不按照代码顺序执行,异步的执行效果更高。

以上是关于异步的概念的解释,接下来我们通俗地解释一下异步:断续执行。 如果有异步方法会跳过,在等待回调结果后,在适当时机执行 同步异步

# 1.2、什么时候用异步编程

在编程中,我们在处理一些简短、快速的操作时,例如计算 1 + 1 的结果,往往在主线程中就可以完成。主线程作为一个线程,不能够同时接受多方面的请求。所以,当一个事件没有结束时,界面将无法处理其他请求。

为了避免这种情况的发生,我们常常用子线程来完成一些可能消耗时间足够长以至于被用户察觉的事情,比如读取一个大文件或者发出一个网络请求。因为子线程独立于主线程,所以即使出现阻塞也不会影响主线程的运行。但是子线程有一个局限:一旦发射了以后就会与主线程失去同步,我们无法确定它的结束,如果结束之后需要处理一些事情,比如处理来自服务器的信息,我们是无法将它合并到主线程中去的。 为了解决这个问题,JavaScript 中的异步操作函数往往通过回调函数来实现异步任务的结果处理。

# 1.3、回调函数

回调函数就是一个函数,它是在我们启动一个异步任务的时候就告诉它:等你完成了这个任务之后要干什么。这样一来主线程几乎不用关心异步任务的状态了,他自己会善始善终。

setTimeout(function () {
    console.log("蜡笔小新!");
}, 1000);
console.log("你真帅!");
1
2
3
4

# 2. Promise

# 2.1、引子

# 2.1.1 背景

由于 JavaScript 单线程的“缺陷”,导致 JavaScript 的所有网络操作,浏览器事件,都必须是异步执行。异步执行可以用回调函数实现,并在将来的某个时间点触发一个函数调用。AJAX 就是典型的异步操作,如下:

function success(text) {
  console.log(txt);
}

function fail(code) {
  console.log(txt);
}

var request = new XMLHttpRequest();
request.onreadystatechange = function () {
  // 状态发生变化时,函数被回调
  if (request.readyState === 4) {
    // 成功完成
    if (request.status === 200) {
      return success(request.responseText);
    } else {
      return fail(request.status);
    }
  } else {
    // HTTP请求还在继续...
  }
};
request.open("GET", "http://...");
request.send();
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

把回调函数success(request.responseText)fail(request.status)写到一个 AJAX 操作里很正常,但是不好看,而且不利于代码复用。

更好的写法:统一执行 AJAX 逻辑,不关心如何处理结果,然后根据结果是成果还是失败在将来的某个时候在调用响应的处理函数。实现执行代码和处理结果的分离。

var ajax = ajaxGet("http://...");
ajax.ifSuccess(success).ifFail(fail);
1
2

回调地狱:多层回调函数嵌套

method1(function (err, result) {
  if (err) {
    throw err;
  }
  method2(function (err, result) {
    if (err) {
      throw err;
    }
    method3(function (err, result) {
      if (err) {
        throw err;
      }
      method4(function (err, result) {
        if (err) {
          throw err;
        }
        method5(result);
      });
    });
  });
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

# 2.1.2 问题

  • 难以理解和调试,无法对回调结果进行选择性的操作
  • 信任问题:回调函数不能保证什么时候去调用回调,以及使用什么方式去调用回调(控制反转)

# 2.1.3 解决方案

Promise 是对回调地狱的思考,或者说是改良方案。它是在 async 函数普及之前唯一的通用性规范,最早是在 CommonJs 社区被提出来,当时比较被接受的是 Promise/A 规范。后来在此基础上提出了 Promise/A+规范,也就是现在业内推行的规范,ES6 也采用这种规范。

promise 这个词意味着”承诺“一个暂时还没有完成但将来会完成的事,与 Promise 进行交互的最主要的方法是,通过将函数传入它的 then 函数从而获得 Promise 的最终结果,告诉下一个 then 函数如何操作。

Promise 最大的好处是在异步执行的流程中,把执行代码处理结果的代码清晰地分离了。

# 2.2 介绍

Promise 是一个 ECMAScript 6 提供的类,目的是更加优雅地书写复杂的异步任务。 由于 Promise 是 ES6 新增加的,所以一些旧的浏览器并不支持,苹果的 Safari 10 和 Windows 的 Edge 14 版本以上浏览器才开始支持 ES6 特性。

以下是 Promise 浏览器支持的情况: promise浏览器支持情况

# 2.3 构造 Promise

new Promise(function (resolve, reject) {
  // do somesthing...
});
1
2
3

示例比较:

//正常书写
setTimeout(function () {
  console.log("我");
  setTimeout(function () {
    console.log("很");
    setTimeout(function () {
      console.log("帅");
    }, 3000);
  }, 4000);
}, 1000);
1
2
3
4
5
6
7
8
9
10
new Promise(function (resolve, reject) {
  setTimeout(function () {
    console.log("我");
    resolve();
  }, 1000);
})
  .then(function () {
    return new Promise(function (resolve, reject) {
      setTimeout(function () {
        console.log("很");
        resolve();
      }, 4000);
    });
  })
  .then(function () {
    setTimeout(function () {
      console.log("帅");
    }, 3000);
  });
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

# 2.4 剖析

Promise 构造函数只有一个参数,是一个函数,这个函数在构造之后会直接执行,所以我们称之为起始函数。起始函数包含两个参数 resolve 和 reject。 当 Promise 被构造时,起始函数会被异步执行。 resolve 和 reject 都是函数,其中调用 resolve 代表一切正常,reject 是出现异常时所调用的:

new Promise(function (resolve, reject) {
  var a = 0;
  var b = 1;
  if (b == 0) reject("Diveide zero");
  else resolve(a / b);
})
  .then(function (value) {
    console.log("a / b = " + value);
  })
  .catch(function (err) {
    console.log(err);
  })
  .finally(function () {
    console.log("End");
  });
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

Promise 类有 .then() .catch() 和 .finally() 三个方法,这三个方法的参数都是一个函数,.then() 可以将参数中的函数添加到当前 Promise 的正常执行序列,.catch() 则是设定 Promise 的异常处理序列,.finally() 是在 Promise 执行的最后一定会执行的序列。 .then() 传入的函数会按顺序依次执行,有任何异常都会直接跳到 catch 序列。

new Promise(function (resolve, reject) {
  console.log(1111);
  resolve(2222);
})
  .then(function (value) {
    console.log(value);
    return 3333;
  })
  .then(function (value) {
    console.log(value);
    throw "An error";
  })
  .catch(function (err) {
    console.log(err);
  });
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

resolve() 中可以放置一个参数用于向下一个 then 传递一个值,then 中的函数也可以返回一个值传递给 then。但是,如果 then 中返回的是一个 Promise 对象,那么下一个 then 将相当于对这个返回的 Promise 进行操作,这一点从刚才的计时器的例子中可以看出来。

reject() 参数中一般会传递一个异常给之后的 catch 函数用于处理异常。

但是请注意以下两点:

  • resolve 和 reject 的作用域只有起始函数,不包括 then 以及其他序列;
  • resolve 和 reject 并不能够使起始函数停止运行,别忘了 return。

# 2.5 Promise 函数

上述的 "计时器" 程序看上去比函数瀑布还要长,所以我们可以将它的核心部分写成一个 Promise 函数:

function print(delay, message) {
  return new Promise(function (resolve, reject) {
    setTimeout(function () {
      console.log(message);
      resolve();
    }, delay);
  });
}
1
2
3
4
5
6
7
8

然后实现功能:

print(1000, "First")
  .then(function () {
    return print(4000, "Second");
  })
  .then(function () {
    print(3000, "Third");
  });
//异步函数(async function)是 ECMAScript 2017 (ECMA-262) 标准的规范,几乎被所有浏览器所支持,除了 Internet Explorer。
async function asyncFunc() {
  await print(1000, "First");
  await print(4000, "Second");
  await print(3000, "Third");
}
asyncFunc();
1
2
3
4
5
6
7
8
9
10
11
12
13
14

这种返回值为一个 Promise 对象的函数称作 Promise 函数,它常常用于开发基于异步操作的库。

# 2.6 解惑

  • then、catch 和 finally 序列顺序可以颠倒,效果完全一样。但不建议这样做,最好按 then-catch-finally 的顺序编写程序。
  • then 块如何中断:then 块默认会向下顺序执行,return 是不能中断的,可以通过 throw 来跳转至 catch 实现中断。
  • Promise 不是一种将异步转换为同步的方法,Promise 是一种更良好的编程风格。
  • Promise 可以用来防范第三方请求多次回调结果,避免不必要的错误产生(例如第三方收费)(控制反转)

# 2.7 Promise/A+规范

其实 Promise 规范有很多,如 Promise/A,Promise/B,Promise/D 以及 Promise/A 的升级版 Promise/A+。ES6 中采用了 Promise/A+ 规范。

# 2.7.1 解读

  • 一个 promise 的当前状态只能是 pending、fulfilled 和 rejected 三种之一。状态改变只能是 pending 到 fulfilled 或者 pending 到 rejected。状态改变不可逆。
  • promise 的 then 方法接收两个可选参数,表示该 promise 状态改变时的回调(promise.then(onFulfilled, onRejected))。then 方法返回一个 promise,then 方法可以被同一个 promise 调用多次。
  • Promise/A+并未规范 race、all、catch 方法,这些是 ES6 自己规范的。

参考:Promises/A+规范-翻译

# 2.7.2 代码案例

// 先定义三个常量表示状态
var PENDING = "pending";
var FULFILLED = "fulfilled";
var REJECTED = "rejected";

function MyPromise(fn) {
  this.status = PENDING; // 初始状态为pending
  this.value = null; // 初始化value
  this.reason = null; // 初始化reason

  // 构造函数里面添加两个数组存储成功和失败的回调
  this.onFulfilledCallbacks = [];
  this.onRejectedCallbacks = [];

  // 存一下this,以便resolve和reject里面访问
  var that = this;
  // resolve方法参数是value
  function resolve(value) {
    if (that.status === PENDING) {
      that.status = FULFILLED;
      that.value = value;

      // resolve里面将所有成功的回调拿出来执行
      that.onFulfilledCallbacks.forEach((callback) => {
        callback(that.value);
      });
    }
  }

  // reject方法参数是reason
  function reject(reason) {
    if (that.status === PENDING) {
      that.status = REJECTED;
      that.reason = reason;

      // resolve里面将所有失败的回调拿出来执行
      that.onRejectedCallbacks.forEach((callback) => {
        callback(that.reason);
      });
    }
  }

  try {
    fn(resolve, reject);
  } catch (error) {
    reject(error);
  }
}

function resolvePromise(promise, x, resolve, reject) {
  // 如果 promise 和 x 指向同一对象,以 TypeError 为据因拒绝执行 promise
  // 这是为了防止死循环
  if (promise === x) {
    return reject(
      new TypeError("The promise and the return value are the same")
    );
  }

  if (x instanceof MyPromise) {
    // 如果 x 为 Promise ,则使 promise 接受 x 的状态
    // 也就是继续执行x,如果执行的时候拿到一个y,还要继续解析y
    // 这个if跟下面判断then然后拿到执行其实重复了,可有可无
    x.then(function (y) {
      resolvePromise(promise, y, resolve, reject);
    }, reject);
  }
  // 如果 x 为对象或者函数
  else if (typeof x === "object" || typeof x === "function") {
    // 这个坑是跑测试的时候发现的,如果x是null,应该直接resolve
    if (x === null) {
      return resolve(x);
    }

    try {
      // 把 x.then 赋值给 then
      var then = x.then;
    } catch (error) {
      // 如果取 x.then 的值时抛出错误 e ,则以 e 为据因拒绝 promise
      return reject(error);
    }

    // 如果 then 是函数
    if (typeof then === "function") {
      var called = false;
      // 将 x 作为函数的作用域 this 调用之
      // 传递两个回调函数作为参数,第一个参数叫做 resolvePromise ,第二个参数叫做 rejectPromise
      // 名字重名了,我直接用匿名函数了
      try {
        then.call(
          x,
          // 如果 resolvePromise 以值 y 为参数被调用,则运行 [[Resolve]](promise, y)
          function (y) {
            // 如果 resolvePromise 和 rejectPromise 均被调用,
            // 或者被同一参数调用了多次,则优先采用首次调用并忽略剩下的调用
            // 实现这条需要前面加一个变量called
            if (called) return;
            called = true;
            resolvePromise(promise, y, resolve, reject);
          },
          // 如果 rejectPromise 以据因 r 为参数被调用,则以据因 r 拒绝 promise
          function (r) {
            if (called) return;
            called = true;
            reject(r);
          }
        );
      } catch (error) {
        // 如果调用 then 方法抛出了异常 e:
        // 如果 resolvePromise 或 rejectPromise 已经被调用,则忽略之
        if (called) return;

        // 否则以 e 为据因拒绝 promise
        reject(error);
      }
    } else {
      // 如果 then 不是函数,以 x 为参数执行 promise
      resolve(x);
    }
  } else {
    // 如果 x 不为对象或者函数,以 x 为参数执行 promise
    resolve(x);
  }
}

MyPromise.prototype.then = function (onFulfilled, onRejected) {
  // 如果onFulfilled不是函数,给一个默认函数,返回value
  // 后面返回新promise的时候也做了onFulfilled的参数检查,这里可以删除,暂时保留是为了跟规范一一对应,看得更直观
  var realOnFulfilled = onFulfilled;
  if (typeof realOnFulfilled !== "function") {
    realOnFulfilled = function (value) {
      return value;
    };
  }

  // 如果onRejected不是函数,给一个默认函数,返回reason的Error
  // 后面返回新promise的时候也做了onRejected的参数检查,这里可以删除,暂时保留是为了跟规范一一对应,看得更直观
  var realOnRejected = onRejected;
  if (typeof realOnRejected !== "function") {
    realOnRejected = function (reason) {
      throw reason;
    };
  }

  var that = this; // 保存一下this

  if (this.status === FULFILLED) {
    var promise2 = new MyPromise(function (resolve, reject) {
      setTimeout(function () {
        try {
          if (typeof onFulfilled !== "function") {
            resolve(that.value);
          } else {
            var x = realOnFulfilled(that.value);
            resolvePromise(promise2, x, resolve, reject);
          }
        } catch (error) {
          reject(error);
        }
      }, 0);
    });

    return promise2;
  }

  if (this.status === REJECTED) {
    var promise2 = new MyPromise(function (resolve, reject) {
      setTimeout(function () {
        try {
          if (typeof onRejected !== "function") {
            reject(that.reason);
          } else {
            var x = realOnRejected(that.reason);
            resolvePromise(promise2, x, resolve, reject);
          }
        } catch (error) {
          reject(error);
        }
      }, 0);
    });

    return promise2;
  }

  // 如果还是PENDING状态,将回调保存下来
  if (this.status === PENDING) {
    var promise2 = new MyPromise(function (resolve, reject) {
      that.onFulfilledCallbacks.push(function () {
        setTimeout(function () {
          try {
            if (typeof onFulfilled !== "function") {
              resolve(that.value);
            } else {
              var x = realOnFulfilled(that.value);
              resolvePromise(promise2, x, resolve, reject);
            }
          } catch (error) {
            reject(error);
          }
        }, 0);
      });
      that.onRejectedCallbacks.push(function () {
        setTimeout(function () {
          try {
            if (typeof onRejected !== "function") {
              reject(that.reason);
            } else {
              var x = realOnRejected(that.reason);
              resolvePromise(promise2, x, resolve, reject);
            }
          } catch (error) {
            reject(error);
          }
        }, 0);
      });
    });

    return promise2;
  }
};

MyPromise.deferred = function () {
  var result = {};
  result.promise = new MyPromise(function (resolve, reject) {
    result.resolve = resolve;
    result.reject = reject;
  });

  return result;
};

MyPromise.resolve = function (parameter) {
  if (parameter instanceof MyPromise) {
    return parameter;
  }

  return new MyPromise(function (resolve) {
    resolve(parameter);
  });
};

MyPromise.reject = function (reason) {
  return new MyPromise(function (resolve, reject) {
    reject(reason);
  });
};

MyPromise.all = function (promiseList) {
  var resPromise = new MyPromise(function (resolve, reject) {
    var count = 0;
    var result = [];
    var length = promiseList.length;

    if (length === 0) {
      return resolve(result);
    }

    promiseList.forEach(function (promise, index) {
      MyPromise.resolve(promise).then(
        function (value) {
          count++;
          result[index] = value;
          if (count === length) {
            resolve(result);
          }
        },
        function (reason) {
          reject(reason);
        }
      );
    });
  });

  return resPromise;
};

MyPromise.race = function (promiseList) {
  var resPromise = new MyPromise(function (resolve, reject) {
    var length = promiseList.length;

    if (length === 0) {
      return resolve();
    } else {
      for (var i = 0; i < length; i++) {
        MyPromise.resolve(promiseList[i]).then(
          function (value) {
            return resolve(value);
          },
          function (reason) {
            return reject(reason);
          }
        );
      }
    }
  });

  return resPromise;
};

MyPromise.prototype.catch = function (onRejected) {
  this.then(null, onRejected);
};

MyPromise.prototype.finally = function (fn) {
  return this.then(
    function (value) {
      return MyPromise.resolve(fn()).then(function () {
        return value;
      });
    },
    function (error) {
      return MyPromise.resolve(fn()).then(function () {
        throw error;
      });
    }
  );
};

MyPromise.allSettled = function (promiseList) {
  return new MyPromise(function (resolve) {
    var length = promiseList.length;
    var result = [];
    var count = 0;

    if (length === 0) {
      return resolve(result);
    } else {
      for (var i = 0; i < length; i++) {
        (function (i) {
          var currentPromise = MyPromise.resolve(promiseList[i]);

          currentPromise.then(
            function (value) {
              count++;
              result[i] = {
                status: "fulfilled",
                value: value,
              };
              if (count === length) {
                return resolve(result);
              }
            },
            function (reason) {
              count++;
              result[i] = {
                status: "rejected",
                reason: reason,
              };
              if (count === length) {
                return resolve(result);
              }
            }
          );
        })(i);
      }
    }
  });
};

module.exports = MyPromise;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359

# 3 Event loop

# 3.1 定义

# 3.1.1

event loop 是一个执行模型,在不同的地方有不同的实现。浏览器和 NodeJS 基于不同的技术实现了各自的 Event Loop。

  • 浏览器的 Event Loop 是在html5 的规范中明确定义。
  • NodeJS 的 Event Loop 是基于 libuv 实现的。可以参考 Node 的官方文档以及 libuv 的官方文档
  • libuv 已经对 Event Loop 做出了实现,而 HTML5 规范中只是定义了浏览器中 Event Loop 的模型,具体的实现留给了浏览器厂商。

# 3.1.2

JavaScript 语言就采用 event loop,来解决单线程运行带来的一些问题。如图:

简单说,就是在程序中设置两个线程:一个负责程序本身的运行,称为"主线程";另一个负责主线程与其他进程(主要是各种 I/O 操作)的通信,被称为"Event Loop 线程"(可以译为"消息线程")。

每当遇到 I/O 的时候,主线程就让 Event Loop 线程去通知相应的 I/O 程序,然后接着往后运行,所以不存在红色的等待时间。等到 I/O 程序完成操作,Event Loop 线程再把结果返回主线程。主线程就调用事先设定的回调函数,完成整个任务。

可以看到,由于多出了橙色的空闲时间,所以主线程得以运行更多的任务,这就提高了效率。这种运行方式称为"异步模式"(asynchronous I/O)。

这种机制与 android 的 handler 机制类似,如图(仅供参考):

# 3.2 宏队列和微队列

宏队列,microtask,也叫 tasks。 一些异步任务的回调会依次进入 macro task queue,等待后续被调用,这些异步任务包括

  • setTimeout
  • setInterval
  • setImmediate (Node 独有)
  • requestAnimationFrame (浏览器独有)
  • I/O
  • UI rendering (浏览器独有) 微队列,microtask,也叫 jobs。 另一些异步任务的回调会依次进入 micro task queue,等待后续被调用,这些异步任务包括:
  • process.nextTick (Node 独有)
  • Promise
  • Object.observe
  • MutationObserver (注:这里只针对浏览器和 NodeJS)

# 3.3 浏览器的 Event Loop

# 3.3.1 图解:

浏览器的Eventloop

# 3.3.2 执行一个 JavaScript 代码的具体流程:

  1. 执行全局 Script 同步代码,这些同步代码有一些是同步语句,有一些是异步语句(比如 setTimeout 等);
  2. 全局 Script 代码执行完毕后,调用栈 Stack 会清空;
  3. 从微队列 microtask queue 中取出位于队首的回调任务,放入调用栈 Stack 中执行,执行完后 microtask queue 长度减 1;
  4. 继续取出位于队首的任务,放入调用栈 Stack 中执行,以此类推,直到直到把 microtask queue 中的所有任务都执行完毕。注意,如果在执行 microtask 的过程中,又产生了 microtask,那么会加入到队列的末尾,也会在这个周期被调用执行;
  5. microtask queue 中的所有任务都执行完毕,此时 microtask queue 为空队列,调用栈 Stack 也为空;
  6. 取出宏队列 macrotask queue 中位于队首的任务,放入 Stack 中执行;
  7. 执行完毕后,调用栈 Stack 为空;
  8. 重复第 3-7 个步骤;
  9. 重复第 3-7 个步骤; ......

# 3.3.3 归纳 3 个重点:

  1. 宏队列 macrotask 一次只从队列中取一个任务执行,执行完后就去执行微任务队列中的任务;
  2. 微任务队列中所有的任务都会被依次取出来执行,直到 microtask queue 为空;
  3. 图中没有画 UI rendering 的节点,因为这个是由浏览器自行判断决定的,但是只要执行 UI rendering,它的节点是在执行完所有的 microtask 之后,下一个 macrotask 之前,紧跟着执行 UI render。

# 3.3.4 示例代码

console.log(1);

setTimeout(() => {
  console.log(2);
  Promise.resolve().then(() => {
    console.log(3);
  });
});

new Promise((resolve, reject) => {
  console.log(4);
  resolve(5);
}).then((data) => {
  console.log(data);
});

setTimeout(() => {
  console.log(6);
});

console.log(7);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

结果输出:

// 正确答案
1;
4;
7;
5;
2;
3;
6;
1
2
3
4
5
6
7
8

# 3.3.5 练习(巩固)

console.log(1);

setTimeout(() => {
  console.log(2);
  Promise.resolve().then(() => {
    console.log(3);
  });
});

new Promise((resolve, reject) => {
  console.log(4);
  resolve(5);
}).then((data) => {
  console.log(data);

  Promise.resolve()
    .then(() => {
      console.log(6);
    })
    .then(() => {
      console.log(7);

      setTimeout(() => {
        console.log(8);
      }, 0);
    });
});

setTimeout(() => {
  console.log(9);
});

console.log(10);

// 正确答案
1;
4;
10;
5;
6;
7;
2;
3;
9;
8;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45

# 4 参考链接