es6之async函数

136 阅读3分钟

什么是async函数

async函数就是Generator函数的语法糖。async函数的实现原理就是Generator函数 + 自动执行器包装在一个函数里。

asycn函数 Generator函数
自带执行器 执行必须靠执行器
async/await语义清晰 星号和yield语义不明确
await命令后Promise对象和原型类型值 Thunk函数或Promise
返回值Promise,可以使用then命令 返回值Iterator对象

怎么使用

aysnc函数返回一个Promise对象,aysnc函数内部return语句返回值,作为then方法回调函数的参数。当函数执行时候,一旦遇到await就会先返回,等到异步操作完成,再接着执行函数体内后面的语句。

function sleep(ms) {
    return new Promise(resolve => {
        setTimeout(() => {
            resolve();
        }, ms);
    });
}

async function foo(ms){
    await sleep(ms);
    return 300;
}

foo(2000).then((data)=> {
    console.log(data)
});
//300

调用foo会立即返回一个promise,2s后await后面的promise状态变成resolve,才执行return,执行then,打印300。

由于async返回是promise,程序可以改为

async function sleep(ms) {
    await new Promise(resolve => {
        setTimeout(() => {
            resolve();
        }, ms);
    });
}
//...其他代码同上

await命令后是一个Promise对象。如果不是,会被转成一个立即resolve的Promise。如果await命令后面是一个thenable对象(即定义then方法的对象),那么await会将其等同于 Promise 对象。可以参照es6之Promise深入理解

let thenable = {
    then: (resolve) => {
        console.log('thenable');
        resolve('xxx');
    }
}
async function foo(){
    return await thenable;
}

foo().then((data)=> {
    console.log(data)
}).catch((e)=>{
    console.log(e.toString())
});

//thenable
//xxx

错误处理机制

async函数内部抛出错误,外部catch捕获

内部抛出错误,后面语句不执行,直接外部catch捕获错误

async function foo(){
    throw new Error('出错了');
    await sleep(); //不执行
}

foo().then((data)=> {
    console.log(data)
}).catch((e)=>{
    console.log(e.toString())
});

//Error: 出错了

这种情况会导致返回的Promise对象状态为reject状态,错误被catch方法回调函数接收到。

async函数返回的Promise对象必须等到内部所有await命名后面的Promise对象执行完才会发生状态改变,除非遇到return语句或者抛出错误。

任何一个await语句后面的Promise对象变为reject状态,那么整个async函数都会中断执行。

async函数内部抛出错误,内部catch捕获

内部自己捕获,程序继续往下执行return语句

async function foo(){
    await Promise.reject('error').catch((e)=>{
        console.log('in:', e.toString())
    });
    return 300;
}

foo().then((data)=> {
    console.log(data)
}).catch((e)=>{
    console.log('out:',e.toString())
});
//in: error
//300

try...catch 实现多次重复尝试

const NUM_RETRIES = 3;
async function multiRetries(url, count){
    let c = count || NUM_RETRIES;
    for(let i = 0; i < c; i++){
        try {
            await ajax.get(url);
            break; //请求成功就跳出循环
        } catch (error) {
            console.error(error);
        }
    }
}

multiRetries('http://xxxx').then((data)=> {
    console.log(data)
}).catch((e)=>{
    console.log(e.toString())
});

注意点

多个await命令后面的异步操作,如果不存在继发关系,最好让他们同时触发

let [foo, bar] = await Promise.all([ajax.get(), ajax.post()])

//或
let fooPromise = ajax.get();
let barPromise = ajax.get();

let foo = await fooPromise;
let bar = await fooPromise;

forEach中使用await中注意,如下是并发的。因为只有async函数内部是继发执行的,外部不受影响。

urls.forEach(async function(url){
    await ajax.get(url);
});

异步遍历器

Iterator 接口是一种数据遍历的协议,只要调用遍历器对象的 next 方法就会得到一个对象。特别注意 next 方法必须是同步的,只要调用就必须立刻返回值。

Generator函数里面的异步操作返回一个Thunk函数或者Promise对象,等待以后返回真正的值,done属性还是同步产生的。

for await ... of

async function foo(){
    try {
        for await (let value of createAsyncIterable()){
            console.log(value);
        }
    } catch (e) {
        console.log(e.toString());
    }
}

异步Generator函数

await后面操作的应该返回Promise对象。使用yield关键字的地方,就是next方法停下来的地方。

async function* gen(){
    yield await Promise.resolve('xxx');
}

注:读阮一峰老师的《ES6入门标准》做的笔记