async函数简介
async函数简单来说就是Generator函数的语法糖,可以看成多个异步操作包装成的Promise对象。相比较于Generator函数,async函数多了几个改进的地方
1.内置执行器
在使用Generator函数时,我们需要自己设置一个执行器,但是async自带执行器,在使用时与普通函数一样只需要函数名和括号来传入参数。
2.更好的语义
相对于Generator函数的声明,async的声明其实很像,但更符合语义,看看下面两个函数
//Generator函数
function* gen(){
yield 1;
}
//async函数
async function gen(){
await 1;
}
async函数只是将*改成async,把yield改成await。
3.返回值是Promise对象
Generator函数返回的是Iterator对象,而async函数返回的是Promise对象,使用then方法来指定下一步操作,比Iterator对象使用next方便。
async函数的使用
async函数在使用时,会直接执行函数体的内容,遇到await语句时,会先返回Promise对象,执行完该await语句后再继续执行后面的内容。
async function asy(){
console.timeline();
await new Promise(resolve=>setTimeout(()=>{
resolve();
console.log(1);
},3000));
setTimeout(()=>{
console.log(2);
console.timelineEnd();
},1000);
}
asy()
// 1
// 2
// Timeline 'default': 4003.81005859375ms
上面代码中,1是3秒后才打印,2是1秒后打印,在一般函数中,2会先打印,但因为在async函数中,需要等到await语句后面的内容执行完后才会执行下面的内容,所以这里先要等到await后面的Promise对象转换为resolve状态后才能继续执行下面的内容,所以这里2其实是4秒后才打印。从使用console.timelineEnd打印出的时间也能看出。
async函数的语法
返回Promise对象
async函数返回的是一个Promise对象,即最后return的内容会变为then方法的resolve方法的参数,而抛出的错误会被catch方法捕获。
async function asy(){
return 'i am promise';
}
asy().then(val=>console.log(val))
// i am promise
async function asy(){
throw new Error('i throw error');
}
asy().then().catch(e=>console.log(e))
// Error: i throw error
async函数返回的Promise对象状态的改变需要等到async函数中所有await语句后面的Promise对象转换完才会改变,除非遇到return语句或者抛出错误。
async function asy(){
await new Promise((resolve)=>setTimeout(resolve,2000));
}
console.timeline();
asy().then(()=>{
console.log('complete');
console.timelineEnd();
})
//complete
// Timeline 'default': 2001.083251953125ms
从打印出来的时间可以看出,complete是在两秒后才打印出来的,即是等到里面的Promise对象转换为resolve状态才转换的。
await命令
正常情况下,await命令后面是一个Promise对象,若不是Promise对象,则直接返回该值。
async function asy(){
return await 'not promise';
}
asy().then(val=>console.log(val))
// not promise
如上面代码,后面的字符串不是Promise对象,所以直接将该值return出来,作为resolve方法的参数。
当然,thenable对象一样会被当成Promise对象处理(带then方法的对象)。
var obj={
then(resolve){
setTimeout(()=>{
resolve();
console.log('thenable');
},1000)
}
}
async function asy(){
await obj;
console.log('async');
}
asy().then(()=>console.log('promise'))
Promise {<pending>}
// thenable
// async
// promise
上面代码可以看到,虽然obj对象里面的then方法的console语句是在1秒后执行的,但’thenale’字符串还是在’promise’字符串之前打印出来,说明await将obj对象当成Promise对象处理。
要注意的是,如果async函数中有一个Promise对象转换为rejected状态,整个函数会终止执行。
async function asy(){
await Promise.reject('error');
await Promise.resolve('continue');
console.log('complete');
}
asy().then(val=>console.log(val)).catch(e=>console.log(e))
// error
上面代码中,因为第一个Promise对象变为rejected状态,所以后面的内容都没有执行了。为了避免这种情况,一般将可能会出错或者变为rejected状态的Promise对象放在try...catch语句块中,或者在其后面调用catch方法。
async function f() {
try {
await Promise.reject('出错了');
} catch(e) {
}
return await Promise.resolve('hello world');}
f().then(v => console.log(v))
// hello world
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
如上面代码,在转为rejected状态的Promise对象后面的语句依然能执行。
使用注意点
1.使用async函数时,因为其中的await语句后面的Promise对象可能会变为rejected状态,所以最好将其放在try...catch语句中。
2.多个异步操作执行时,若其执行的顺序与最终结果无关,最好让它们同时触发。
// 按顺序执行的写法
let foo = await getFoo();
let bar = await getBar();
// 同时触发
// 写法一
let [foo, bar] = await Promise.all([getFoo(), getBar()]);
// 写法二
let fooPromise = getFoo();
let barPromise = getBar();
let foo = await fooPromise;
let bar = await barPromise;
3.await命令只能在async函数中使用,若在其他地方使用会报错。
function normal(){
await 1;
}
// Uncaught SyntaxError: await is only valid in async function
4.async函数可以保留运行堆栈
const a = () => {
b().then(() => c());
};
上面代码中,a内部执行了一个异步方法b,但是在b开始执行时,a并不会停止执行,等到c执行时,a可能已经停止了。b所在的上下文环境已经不在了,若b()或c()报错,错误堆栈将不包括a()。
执行原理
async函数的执行原理,其实就是将Generator函数和自动执行器包装在一个函数里。
async function fn(args) {
// ...
}
// 等同于
function fn(args) {
return spawn(function* () {
// ...
});}
参考自阮一峰的《ECMAScript6入门》
ES6学习笔记目录