Asynchronous
异步编程适用于 IO 密集型应用
异步编程的传统实现方式:Callback
1 2 3 4 5 6 7
| console.log("main start"); setTimeout(() => console.log("sync start"), 1000); console.log("main end");
|
依次执行多个异步操作:Callback Hell
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| setTimeout(() => { console.log("Action 1"); setTimeout(() => { console.log("Action 2"); setTimeout(() => { console.log("Action 3"); setTimeout(() => { console.log("Action 4"); }) }) }) })
|
为了解决 Callback Hell,提高代码的可读性,Promise 应运而生:通过链式操作将多个异步任务串联起来

常见的 try...catch...finally 为同步编程范式
1 2 3 4 5
| fetch("https://www.bilibili.com/") .then(response => response.json()) .then(json => console.log(json)) .catch(error => console.log(error)) .finally(() => console.log("close"));
|
Promise
一个 JavaScript 引擎会常驻于内存中,等待宿主把 JavaScript 代码或者函数传递给它去执行
- 在
ES3 及更早的版本中,JavaScript 引擎本身没有异步执行代码的能力
- 在
ES5 之后,JavaScript 引入了 Promise,从此,不需要浏览器的安排,JavaScript 引擎本身也可以发起任务
宏观任务 vs 微观任务
依据 JSC 引擎的术语,把宿主发起的任务称为宏观任务,把 JavaScript 引擎发起的任务称为微观任务
Event Loop:JavaScript 引擎等宿主环境分配宏观任务
在底层的 C/C++ 代码中,Event Loop 是跑在一个独立线程的循环中
1 2 3 4 5
| while (true) { r = wait(); execute(r); }
|
在宏观任务中,JavaScript 的 Promise 还会产生异步代码
JavaScript 必须保证这些异步代码在一个宏观任务中完成,因此,每个宏观任务中又包含了一个微观任务队列
基于宏观任务和微观任务的机制,可以实现 宿主级 和 JavaScript 引擎级 的任务
setTimeout(宿主 API):添加宏观任务
Promise:在队列尾部添加微观任务
Promise
Promise 是 JavaScript 语言提供的一种标准化的异步管理方式
总体思想:需要进行 IO、等待或者其它异步操作的函数,不返回真实结果,而是返回一个『承诺』
函数的调用方可以在合适的时机,选择等待 Promise 实现(通过 Promise 的 then 方法回调)
Promise 的 then 回调是一个异步的执行过程(微观任务队列尾部)
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| console.log("main start") let p = new Promise((resolve, reject) => { console.log("Promise start") resolve(); console.log("Promise end") }) p.then(() => console.log("resolve")); console.log("main end")
|
Promise + setTimeout,微观任务始终先于宏观任务
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| setTimeout(() => console.log("d"), 0);
let p = new Promise((resolve, reject) => { console.log("a"); resolve(); }) p.then(() => console.log("c"));
console.log("b")
|
微观任务始终先于宏观任务
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| setTimeout(() => console.log("macro"), 0);
new Promise((resolve, reject) => { resolve(); }).then(() => { let begin = Date.now(); while (Date.now() - begin < 2000) { }
console.log("micro 1"); new Promise((resolve, reject) => { resolve(); }).then(() => console.log("micro 2")); })
|
执行顺序
- 分析有多少
宏观任务
- 在每个宏观任务中,分析有多少
微观任务
- 根据
调用次序,确定宏观任务中的微观任务的执行次序
- 根据宏观任务的
触发规则和调用次序,确定宏观任务的执行次序
1 2 3 4 5 6 7 8 9 10 11 12 13
| function sleep(duration) { return new Promise((resolve, reject) => { console.log("b"); setTimeout(resolve, duration); }) }
console.log("a"); sleep(1000).then(() => console.log("c"));
|
setTimeout 把整个代码分割成 2 个宏观任务
- 第 1 个
宏观任务中,包含先后同步执行的 console.log("a"); 和 console.log("b");
- 第 2 个
宏观任务中,调用了 resolve,然后 then 中的代码异步得到执行
从 ES6 开始,有了 async/await + Promise,能够有效地改善代码结构
async / await
async/await提供了用for、if等代码结构来编写异步的方式,运行时基础是 Promise(本质上为语法糖)
异步函数:会返回 Promise 的函数
可以通过 async 关键字将一个函数声明为异步函数,而 await 关键字也只能在 async 声明的异步函数内使用
1 2 3 4 5 6 7 8 9 10 11 12 13
| async function httpFetch() { let response = await fetch(""); console.log("httpFetch...") return response; }
httpFetch().then(response => response.status) .then(code => console.log(code)) .finally(() => console.log("done"))
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| function sleep(duration) { return new Promise((resolve, reject) => { setTimeout(resolve, duration); }) }
async function foo() { console.log("a"); await sleep(2000); console.log("b"); }
foo().then(() => console.log("c"));
|
async 函数是可以嵌套的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| function sleep(duration) { return new Promise((resolve, reject) => { setTimeout(resolve, duration); }) }
async function foo(name) { await sleep(2000); console.log(name); }
async function bar() { await foo("A"); await foo("B"); }
bar().then(() => console.log("C"))
|
串行 vs 并行
1 2 3 4 5
| async function f() { let r1 = await fetch(""); let r2 = await fetch(""); }
|
1 2 3 4 5 6 7
| async function f() { let r1 = fetch(""); let r2 = fetch(""); let [p1, p2] = await Promise.all([r1, r2]); return [p1, p2]; }
|