十一、ES6系列之Async

含义

ES2017 标准引入了 async 函数,使得异步操作变得更加方便。

在异步处理上,async 函数就是 Generator 函数的语法糖。

举个例子:

1
2
3
4
5
6
7
8
9
10
11
// 使用 generator
var fetch = require("node-fetch");
var co = require("co");

function* gen() {
var r1 = yield fetch("https://api.github.com/users/github");
var json1 = yield r1.json();
console.log(json1.bio);
}

co(gen);

当你使用 async 时:

1
2
3
4
5
6
7
8
9
10
// 使用 async
var fetch = require("node-fetch");

var fetchData = async function () {
var r1 = await fetch("https://api.github.com/users/github");
var json1 = await r1.json();
console.log(json1.bio);
};

fetchData();

一比较就会发现,其实 async 函数的实现原理,就是将 Generator 函数和自动执行器,包装在一个函数里。

1
2
3
4
5
6
7
8
9
10
11
async function fn(args) {
// ...
}

// 等同于

function fn(args) {
return spawn(function* () {
// ...
});
}

spawn 函数指的是自动执行器,就比如说 co。

再加上 async 函数返回一个 Promise 对象,你也可以理解为 async 函数是基于 Promise 和 Generator 的一层封装。
下面给出 spawn 函数的实现,基本就是前文自动执行器的翻版。

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
function spawn(genF) {
return new Promise(function (resolve, reject) {
const gen = genF();
function step(nextF) {
let next;
try {
next = nextF();
} catch (e) {
return reject(e);
}
if (next.done) {
return resolve(next.value);
}
Promise.resolve(next.value).then(
function (v) {
step(function () {
return gen.next(v);
});
},
function (e) {
step(function () {
return gen.throw(e);
});
}
);
}
step(function () {
return gen.next(undefined);
});
});
}

基本用法

async 函数返回一个 Promise 对象,可以使用 then 方法添加回调函数。当函数执行的时候,一旦遇到 await 就会先返回,等到异步操作完成,再接着执行函数体内后面的语句。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function resolveAfter2Seconds(ms) {
return new Promise((resolve) => {
setTimeout(resolve, ms);
});
}

async function asyncCall() {
console.log("calling");
await resolveAfter2Seconds(2000);
console.log("ending");
// expected output: "ending"
}

asyncCall();

由于 async 函数返回的是 Promise 对象,可以作为 await 命令的参数。所以,上面的例子也可以写成下面的形式。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
async function resolveAfter2Seconds(ms) {
await new Promise((resolve) => {
setTimeout(resolve, ms);
});
}

async function asyncCall() {
console.log("calling");
const result = await resolveAfter2Seconds(2000);
console.log("ending");
// expected output: "ending"
}

asyncCall();

继发与并发

问题:给定一个 URL 数组,如何实现接口的继发和并发?

async 继发实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 继发一
async function loadData() {
var res = await fetch(url1);
var res2 = await fetch(url2);
var res3 = await fetch(url3);
return "whew all done";
}
// 继发二
async function loadData(urls) {
for (const url of urls) {
const response = await fetch(url);
console.log(await response.text());
}
}

async 并发实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 并发一
async function loadData() {
var res = await Promise.all([fetch(url1), fetch(url2), fetch(url3)]);
return "whew all done";
}

// 并发二
async function loadData(urls) {
// 并发读取 url
const textPromises = urls.map(async (url) => {
const response = await fetch(url);
return response.text();
});

// 按次序输出
for (const textPromise of textPromises) {
console.log(await textPromise);
}
}

错误捕获

尽管我们可以使用 try catch 捕获错误,但是当我们需要捕获多个错误并做不同的处理时,很快 try catch 就会导致代码杂乱,就比如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
async function f() {
try {
await new Promise(function (resolve, reject) {
throw new Error("出错了");
});
} catch (e) {
console.log(e);
}
try {
await new Promise(function (resolve, reject) {
throw new Error("出错了2");
});
} catch (e) {
console.log(e);
}
try {
await new Promise(function (resolve, reject) {
throw new Error("出错了3");
});
} catch (e) {
console.log(e);
}
return await "hello world";
}

为了简化这种错误的捕获,我们可以给 await 后的 promise 对象添加 catch 函数,为此我们需要写一个 helper:

1
2
3
4
5
6
7
8
// to.js
export default function to(promise) {
return promise
.then((data) => {
return [null, data];
})
.catch((err) => [err]);
}

简化后的代码:

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
import to from "./to.js";

async function f() {
[err, user] = await to(
new Promise(function (resolve, reject) {
throw new Error("出错了");
})
);
if (!user) console.log(err);

[err, user] = await to(
new Promise(function (resolve, reject) {
throw new Error("出错了2");
})
);
if (err) console.log(err);

[err, user] = await to(
new Promise(function (resolve, reject) {
throw new Error("出错了3");
})
);
if (!user) console.log(err);
return await "hello world";
}

参考文献