含义
ES2017 标准引入了 async 函数,使得异步操作变得更加方便。
在异步处理上,async 函数就是 Generator 函数的语法糖。
举个例子:
1 2 3 4 5 6 7 8 9 10 11
| 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
| 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"); }
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"); }
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) { 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
| 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"; }
|
参考文献