Asynchronous

异步编程适用于 IO 密集型应用

异步编程的传统实现方式:Callback

1
2
3
4
5
6
7
console.log("main start");
setTimeout(() => console.log("sync start"), 1000);
console.log("main end");

// main start
// main end
// sync start

依次执行多个异步操作: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");
})
})
})
})

// Action 1
// Action 2
// Action 3
// Action 4

为了解决 Callback Hell,提高代码的可读性,Promise 应运而生:通过链式操作将多个异步任务串联起来

image-20230429154107305

常见的 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 代码或者函数传递给它去执行

  1. ES3 及更早的版本中,JavaScript 引擎本身没有异步执行代码的能力
  2. ES5 之后,JavaScript 引入了 Promise,从此,不需要浏览器的安排,JavaScript 引擎本身也可以发起任务

宏观任务 vs 微观任务

依据 JSC 引擎的术语,把宿主发起的任务称为宏观任务,把 JavaScript 引擎发起的任务称为微观任务

Event Loop:JavaScript 引擎等宿主环境分配宏观任务
在底层的 C/C++ 代码中,Event Loop 是跑在一个独立线程的循环中

1
2
3
4
5
// 宏观任务的队列相当于 Event Loop
while (true) {
r = wait();
execute(r);
}

在宏观任务中,JavaScript 的 Promise 还会产生异步代码
JavaScript 必须保证这些异步代码在一个宏观任务中完成,因此,每个宏观任务中又包含了一个微观任务队列

基于宏观任务微观任务的机制,可以实现 宿主级JavaScript 引擎级 的任务

  1. setTimeout(宿主 API):添加宏观任务
  2. 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")

// main start
// Promise start
// Promise end
// main end
// resolve

Promise + setTimeout,微观任务始终先于宏观任务

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// setTimeout 是浏览器 API,产生的是宏观任务
setTimeout(() => console.log("d"), 0);

// Promise 产生的是 JavaScript 引擎内部的微观任务
let p = new Promise((resolve, reject) => {
console.log("a");
resolve();
})
p.then(() => console.log("c"));

console.log("b")

// a
// b
// c
// d

微观任务始终先于宏观任务

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"));
})

// micro 1
// micro 2
// macro

执行顺序

  1. 分析有多少宏观任务
  2. 在每个宏观任务中,分析有多少微观任务
  3. 根据调用次序,确定宏观任务中的微观任务的执行次序
  4. 根据宏观任务的触发规则调用次序,确定宏观任务的执行次序
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")); // resolve

// a
// b
// c
  1. setTimeout 把整个代码分割成 2 个宏观任务
  2. 第 1 个宏观任务中,包含先后同步执行console.log("a");console.log("b");
  3. 第 2 个宏观任务中,调用了 resolve,然后 then 中的代码异步得到执行

ES6 开始,有了 async/await + Promise,能够有效地改善代码结构

async / await

async/await提供了用forif等代码结构来编写异步的方式,运行时基础是 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"))

// httpFetch...
// 200
// done
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 异步函数:返回 Promise 的函数
function sleep(duration) {
return new Promise((resolve, reject) => {
setTimeout(resolve, duration);
})
}

async function foo() {
console.log("a");
await sleep(2000); // 使用 await 等待一个 Promise
console.log("b");
}

foo().then(() => console.log("c"));

// a
// b
// 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"))

// A
// B
// C

串行 vs 并行

1
2
3
4
5
// 任务串行:r1 -> r2
async function f() {
let r1 = await fetch("");
let r2 = await fetch("");
}
1
2
3
4
5
6
7
// 任务并行:Promise.all
async function f() {
let r1 = fetch("");
let r2 = fetch("");
let [p1, p2] = await Promise.all([r1, r2]);
return [p1, p2];
}