async
函数是Generator函数的语法糖。
async
函数就是将Generator函数的星号(*
)替换成async
,将yield
替换成await
。
依次读取两个文件,Generator函数写法:
const fs = require('fs');
const readFile = function (fileName) {
return new Promise(function (resolve, reject) {
fs.readFile(fileName, function(error, data) {
if (error) return reject(error);
resolve(data);
}
});
}
const gen = function * () {
const f1 = yield readFile('/etc/fstab');
const f2 = yield readFile('/etc/shells');
console.log(f1.toString());
console.log(f2.toString());
}
依次读取两个文件,async
函数写法:
const asyncReadFile = async function () {
const f1 = await readFile('/etc/fstab');
const f2 = await readFile('/etc/shells');
console.log(f1.toString());
console.log(f2.toString());
}
async
函数对Generator函数的改进内置执行器
Generator函数的执行必须依靠执行器(例如co
模块),而async
函数自带执行器,即async
函数的执行,和普通函数一样,只需要一行代码。
asyncReadFile();
更好的语义
async
和await
,比起星号和yield
,语义更清楚了。async
表示函数里有异步操作,await
表示紧跟在后面的表达式需要等待结果。
更广的适用性
co
模块约定:yield
命令后面只能是Thunk函数或Promise对象。
async
函数的await
:后面可以是Promise对象和原始类型的值(数值、字符串和布尔值,但这是会自动转成立即resolved的Promise对象)。
返回值是Promise
Generator函数返回值是Iterator对象,async
函数返回的是Promise对象,可以用then
方法指定下一步的操作。
async
函数可以看作是多个异步操作包装成的一个Promise对象,而await
命令就是内部then
命令的语法糖。
async
函数返回一个Promise对象,可以使用then
方法添加回调函数。当函数执行的时候,一旦遇到await
就会先返回(就是直接跳出整个函数了),等到异步操作完成,再接着执行函数体内后面的语句。
async
函数有多种使用形式。
async
函数返回的是Promise对象,可以作为await
命令的参数。
// 函数声明
async function foo() {}
// 函数表达式
const foo = async function () {};
// 对象的方法
let obj = { async foo() {} };
obj.foo().then(...)
// Class 的方法
class Storage {
constructor() {
this.cachePromise = caches.open('avatars');
}
async getAvatar(name) {
const cache = await this.cachePromise;
return cache.match(`/avatars/${name}.jpg`);
}
}
const storage = new Storage();
storage.getAvatar('jake').then(…);
// 箭头函数
const foo = async () => {};
async
函数的难点是错误处理机制。
async
函数返回一个Promise对象,函数内部return
语句返回的值,会成为then
方法回调函数的参数。
async
函数内部抛出错误,会导致返回的Promise对象变为reject
状态。抛出的错误对象会被catch
方法回调函数接收到。
async function f() {
throw new Error('出错了');
}
f().then(
v => console.log('resolve', v),
e => console.log('reject', e)
)
//reject Error: 出错了
async
函数返回的Promise对象,必须等到内部所有await
命令后面的Promise对象执行完,状态才会发生变化(除非遇到return
或者抛出错误)。也就是说,只有async
函数内部的异步操作执行完,才会执行then
方法指定的回调函数。
await
返回:如果await
命令后面是一个Promise对象(或thenable
对象),返回该对象的结果,如果不是,就直接返回对应的值。
返回reject
状态:await
命令后面的Promise对象如果变成reject
状态,则reject
的参数会被catch
方法的回调函数接收到。任何一个await
语句后面的Promise对象变为reject
状态,那么整个async
函数都会中断执行。
处理reject
状态:即使某一异步操作失败,也不中断后续异步操作。1.将可能失败的异步操作放到try...catch
结构里面。2.在可能失败的异步操作后再加一个catch
方法,处理可能出现的错误。
如果await
后面的异步操作出错,那么等同于async
函数返回的Promise对象被reject
。
处理方法:将可能出错的await
命令放入try...catch
结构中,可以放多个await
命令。
await
命令放在try...catch
代码块中(或后加catch
方法),防止await
变成reject
状态中断程序。await
命令,如果不存在继发关系,最好让它们同时触发。// 写法一
let [foo, bar] = await Promise.all([getFoo(), getBar()]);
// 写法二
let fooPromise = getFoo();
let barPromise = getBar();
let foo = await fooPromise;
let bar = await barPromise;
await
命令只能用在async
函数中,用在普通函数中会报错。async
函数可以保留运行堆栈。const a = async () => {
await b();
c();
};
上面代码中,b()运行的时候,a()是暂停执行,上下文环境都保存着。一旦b()或()报错,错误堆栈讲包括a()。
async函数的实现原理,就是将Generator函数和自动执行器,包装在一个函数里。