ES2017 标准引入 async 函数,使得一步操作变得更加方便
函数async
对Generator 函数进行了改进,主要体现在以下方面
Generator 函数的执行必须要有执行器,而async
函数自带执行器。async
函数的执行,与普通函数一样,只要一行。
函数async
和await
比起*
和yield
,语义更加清楚。async
表示函数里有异步操作,await
表示其后的表达式需要更待结果。
函数async
函数的await
后面,可以是Promise
对象和原始类型的值(数值、字符串和布尔值但会自动转换为resolved 的Promise 对象)
函数async
的返回值是Promise对象,比Generator函数返回的值是Iterator对象方便多了。可以直接使用then
方法执行下一步动作
函数async
函数返回一个Promise对象,可以使用then
方法添加回调函数。当函数执行的时候,一旦遇到await
就会先返回,等待异步操作完成,再接着执行函数体内后面的语句。
例子:指定多少秒后输出一个值
function timeout(ms) {
return new Promise( (resolve) => {
setTimeout(resolve, ms);
});
}
async function asyncPrint(value, ms){
await timeout(ms);
console.log(value);
}
asyncPrint('hello async',1000);
由于 async
函数返回的是Promise 对象,可以作为await
命令的参数。所以,上面的代码也可以改写为:
async function timeout(ms){
await new Promise( (resolve) => {
setTimeout(resolve, ms);
});
}
async function asyncPrint(value, ms){
await timeout(ms);
console.log(value);
}
asyncPrint('hello async',1000);
函数async
有多种使用方式
// 函数声明
async function abc() {}
// 函数表达式
const foo = async function (){};
// 对象的方法
let obj = { async abc (){}};
// 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
返回一个Promise对象,async
函数内部return
语句返回的值,会成为then
方法回调函数的参数
async function f() {
return 'hello async';
}
f().then(value=>console.log(value)); // 'hello async'
函数async
函数内抛出错误,会导致返回的Promise 对象变为 reject
状态。抛出的错误对象会被catch
方法回调函数接收到。
async function f() {
throw new Error('Error');
}
f().then(
value => console.log(value),
error => console.log(error),
)
// 'Error'
函数async
返回的Promise对象,必须要等到内部所有await
命令后面的Promise
对象完成才会发生状态改变,或者遇到return
语句或者抛出错误。只有async
函数内部的异步操作执行完,才会执行then
方法指定的回调函数。
async function getTitle(url) {
let response = await fetch(url);
let html = await response.test();
return html.match(/([\s\S]+)<\/title>/i )[1];
}
getTitle('https://tc39.github.io/ecma262/').then(console.log)
await
命令后面是一个Promise对象,返回Promise对象的结果。如果不是Promise对象,就直接返回对应的值。async function f() {
return await 123;
// 等同于 return 123;
}
f().then(value => console.log(value));
当await
命令后面是一个thenable
对象(即:定义then
方法的对象),此时await
会将其等同于Promise对象。
命令await
后面的Promise对象如果变为reject
状态,则reject
的参数被catch
方法的回调函数接收到。
任何一个await
语句后面的Promise对象变为 reject
状态,那么整个async
函数都会中断执行
async function f() {
await Promise.reject('Error');
await Promise.resolve('Hello async');
}
如果希望即使第一步出错了,也不要中断后面的异步操作,有两种方法解决:
try…catch
代码块中,这样不管这个异步操作是否成功,后面的await
都会执行。async function f(){
try {
await Promise.reject('Error');
} catch(e) {
}
return await Promise.resolve('Hello async');
}
f().then(value => console.log(value));
await
后面的Promise 对象再跟一个catch
方法,处理前面可能会出现的错误async function f(){
await Promise.reject('Error').catch( e => console.log(e));
return await Promise.resolve('Hello async');
}
f().then( value=>console.log(value)); // 'Hello async'
await
后面的异步操作出错,那么等同于async
函数返回的Promise对象被reject
.try…catch
代码块中,有多个await
的时候,可以统一放在try…catch
代码块中.注意
await
后面的Promise对象, 运行结果可能是reject
,所以最好把await
命令放在try…catch
代码块中await
命令后面是异步操作,如果不存在继发关系,最好让他们同时触发let foo = await getFoo();
let bar = await getBar();
上面的代码中,getFoo
和getBar
是两个独立的异步操作(互不依赖),被写成继发关系,这样比较耗时。
同时触发的写法
// 写法一
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 = () => {
b().then( () => c());
};
上面代码中,函数a
内部运行了一个异步操作b()
。当b()
运行的时候,函数a()
不会中断,而是继续执行。等到b()
运行结束,可能a()
早就结束了,b()
所在的上下文环境已经消失了,如果此时的b()
或c()
报错,错误对战将不包括a()
.
改为async
函数
const a = async () =>{
await b();
c();
}
此时,b()
运行的时候,a()
是暂停执行的,上下文环境都是保存着的,一旦b()
或者c()
报错,错误堆栈将包括a()
.
函数async
实现原理就是将Generator 函数和自动执行器封装在一个函数里。
async function fn(args){
// ...
}
// 等同于
function fn(args){
return spawn(function* (){
// ...
});
}
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(value){
step(function() {return gen.next(v)});
},function(e){
step(function() {return gen.throw(e)});
})
}
step(function() {return gen.next(undefined);});
});
}
备注:本文是自己学习阮一峰老师的《ECMAScript 6 入门》所做的笔记,大部分例子来源于此书。