简单谈谈
async
是什么?一句话,它就是 Generator
函数的语法糖。
既然是语法糖,那么先来对比下 async
函数和 Generator
函数的区别。以下是分别用 async
和 Generator
写的异步操作:
const fs = require('fs');
const readFile = function (fileName) {
return new Promise((resolve, reject) => {
fs.readFile(fileName, (error, data) => {
if (error) return reject(error);
resolve(data);
});
});
};
// Generator 的写法 & 执行
const gen = function* () {
const f1 = yield readFile('/etc/fstab');
const f2 = yield readFile('/etc/shells');
console.log(f1.toString());
console.log(f2.toString());
}
autoGen(gen())
// 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());
}
asyncReadFile();
**注:**不清楚 autoGen
是什么的可以查看上一节。
上诉代码,主要有以下几点区别:
类型Generatorasync函数标识*async暂停语句yieldawait执行方法调用自动执行方法(autoGen)直接调用
如果能理解 Generator
函数在异步中的使用的话,那么我相信,async
函数对于我们来说,简直简单的不能在简单了。那么既然用 Generator
函数能解决的东西,为什么又要多一个 async
?
和大多数语法糖出现一样,async
主要是为了更好的语义和更广的适用性。主要表现在以下几点:
async
和await
,比起*
和yield
,语义更清楚了。async
表示函数里有异步操作,await
表示紧跟在后面的表达式需要等待结果。yield
命令后面只能是Thunk
函数或Promise
对象,而async
函数的await
命令后面,可以是Promise
对象或原始类型的值(数值、字符串和布尔值,等同于同步操作)。async
函数的返回值是Promise
对象,这比Generator
函数的返回值是Iterator
对象方便多了。可以用then
方法指定下一步的操作。
基本用法
async
async
函数返回一个 Promise
对象,可以使用 then
方法添加回调函数。
async function asy(){
return 'this is async fun';
};
asy().then(res => console.log(res));
// this is async fun
上述例子可见 async
函数最终返回 Promise
其结果为函数的返回值。
async
函数有多种形式(与基本函数的表现形式一致):
// 函数声明
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
内部有未捕捉的异常时, async
函数返回的 Promise
将会是 reject
状态。
async function f() {
throw new Error('出错了');
}
f().then(
v => console.log(v),
e => console.log(e)
);
// Error: 出错了
await
正常情况下,await
命令后面是一个 Promise
对象。如果不是,会被转成一个立即 resolve
的 Promise
对象。
async function f() {
let a = await 123;
return a;
}
// 等同于
async function f() {
let a = await Promise.resolve(123);
return a;
}
f().then(v => console.log(v));
// 123
当 await
命令后跟的 Promise
状态变更为 fulfilled(resolve)
时,a
会被赋值,a
的值即为异步处理的结果。
当然如果 await
命令后的 Promise
的状态变更为 reject
并且没有对该 Promise
进行 catch
处理,那么 async
函数结束执行返回的 Promise
也将会变成 reject
状态。
async function f() {
await Promise.reject('出错了');
await Promise.resolve('hello world'); // 不会执行
}
f();
.then(v => console.log(v));
.catch(e => console.log(e));
// 出错了
错误处理
有两种方式可以在不影响 async
函数执行的情况下进行捕捉异常:
try...catch
async function f() {
try {
await Promise.reject('出错了');
} catch(e) {
}
return await Promise.resolve('hello world');
}
f();
.then(v => console.log(v));
// hello world
Promise.catch
async function f() {
await Promise.reject('出错了').catch(e => console.log(e));
return await Promise.resolve('hello world');
}
f();
.then(v => console.log(v));
// 出错了
// hello world
使用注意点
await
命令后面的Promise
对象,运行结果可能是rejected
,所以最好把await
命令放在try...catch
代码块中,或直接后面跟一个catch
方法进行异常捕捉。- 多个
await
命令后面的异步操作,如果不存在继发关系,最好让它们同时触发。 await
命令只能用在async
函数之中,如果用在普通函数,就会报错。
关于第二点,由于 async
函数的设计就是以一种同步的写法写异步函数,所以 await
是一个一个执行的,有顺序的关系,所以当一个函数体内有两个互相无关的异步请求的话,直接写 await
是达不成同时进行异步操作的效果的,所以得先使用 Promise.all
进行一层包装。
let [foo, bar] = await Promise.all([getFoo(), getBar()]);