JavaScript - Asynchronous
Asynchronous
异步编程
适用于IO 密集型应用
异步编程的传统实现方式:
Callback
1 | console.log("main start"); |
依次
执行多个异步操作:Callback Hell
1 | setTimeout(() => { |
为了解决
Callback Hell
,提高代码的可读性,Promise
应运而生:通过链式
操作将多个异步任务
串联起来
常见的
try...catch...finally
为同步
编程范式
1 | fetch("https://www.bilibili.com/") |
Promise
一个
JavaScript 引擎
会常驻于内存
中,等待宿主
把 JavaScript 代码或者函数传递给它去执行
- 在
ES3
及更早的版本中,JavaScript 引擎本身没有异步
执行代码的能力 - 在
ES5
之后,JavaScript 引入了Promise
,从此,不需要浏览器的安排,JavaScript 引擎本身也可以发起任务
宏观任务 vs 微观任务
依据
JSC
引擎的术语,把宿主
发起的任务称为宏观任务
,把JavaScript 引擎
发起的任务称为微观任务
Event Loop
:JavaScript 引擎等宿主
环境分配宏观任务
在底层的C/C++
代码中,Event Loop
是跑在一个独立线程
的循环中
1 | // 宏观任务的队列相当于 Event Loop |
在宏观任务中,JavaScript 的
Promise
还会产生异步代码
JavaScript 必须保证这些异步代码在一个宏观任务中完成
,因此,每个宏观任务
中又包含了一个微观任务队列
基于
宏观任务
和微观任务
的机制,可以实现宿主级
和JavaScript 引擎级
的任务
setTimeout
(宿主 API):添加宏观任务
Promise
:在队列尾部
添加微观任务
Promise
Promise 是
JavaScript 语言
提供的一种标准化
的异步管理方式
总体思想:需要进行 IO、等待或者其它异步操作的函数,
不返回真实结果
,而是返回一个『承诺』
函数的调用方可以在合适的时机,选择等待 Promise 实现(通过 Promise 的then
方法回调
)
Promise 的
then
回调是一个异步
的执行过程(微观任务队列尾部
)
1 | console.log("main start") |
Promise + setTimeout,
微观任务始终先于宏观任务
1 | // setTimeout 是浏览器 API,产生的是宏观任务 |
微观任务始终先于宏观任务
1 | setTimeout(() => console.log("macro"), 0); |
执行顺序
- 分析有多少
宏观任务
- 在每个宏观任务中,分析有多少
微观任务
- 根据
调用次序
,确定宏观任务中的微观任务
的执行次序 - 根据宏观任务的
触发规则
和调用次序
,确定宏观任务
的执行次序
1 | function sleep(duration) { |
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 | async function httpFetch() { |
1 | // 异步函数:返回 Promise 的函数 |
async
函数是可以嵌套
的
1 | function sleep(duration) { |
串行 vs 并行
1 | // 任务串行:r1 -> r2 |
1 | // 任务并行:Promise.all |