async/await用法详解

ES2017 标准引入了 async 函数,使得异步操作变得更加方便。

async函数的定义方式

// 函数声明
async function fn() {}

// 函数表达式
const fn = async function () {}

// 箭头函数
const fn = async () => {}

// 对象的方法
let obj = { async fn() {} }
obj.fn().then(...)

核心用法

  • await命令只能用在async函数之中,如果用在普通函数中会报错。

  • async函数体内,await命令之前的代码以及await命令所在语句中await后的代码都为正常的同步代码,在async函数调用时正常执行。

  • async函数会返回一个Promise对象。若返回的不是一个显式的Promise对象,则会使用Promise.resolve()包装成Promise对象。

  • await命令后跟一条返回Promise对象的语句。若语句返回的不是Promise对象,则会使用Promise.resolve()包装成Promise对象。

  • await命令相当于其后语句返回的Promise对象调用的只有一个回调函数的then方法。async函数体内await命令所在语句后的语句相当于then方法第一个回调函数中的语句,await关键字左侧返回的值相当于要传入then方法第一个回调函数的参数。

  • await后语句返回的Promise对象的执行结果可能是rejected,所以最好把await命令放在try...catch代码块中。

示例代码:

async function fn() {
    return 100 // 相当于 return Promise.resolve(100)
}
console.log(fn()) // Promise {<fulfilled>: 100}
(async function () {
    const a = await new Promise((resolve, reject) => {
        console.log('start')
        resolve('hello')
        console.log('middle')
    }).then((val) => {
        console.log(val)
        return 'world'
    })
    console.log(a)
})()
console.log('end')

// start
// middle
// end
// hello
// world

错误处理

因为async函数体内await命令所在语句后的语句相当于then方法第一个回调函数中的语句,所以如果任何一个await语句后面的 Promise 对象变为reject状态,那么整个async函数都会中断执行。

(async function () {
    await Promise.reject('出错了')
    await Promise.resolve('hello world'); // 不会执行
})()
console.log('hello') // 仍然可以执行

常见错误方式

1. await后的Promise变为reject状态

await命令后面的 Promise 对象如果变为rejected状态,则reject的参数会被catch方法的回调函数接收到。

(async function () {
    await Promise.reject('出错了')
    console.log('hello'); // 不会执行
})()
    .catch(e => console.log('err~', e))
// err~ 出错了

注意,上面代码中,await语句前面没有return,但是reject方法的参数依然传入了catch方法的回调函数。这里如果在await前面加上return,效果是一样的。

2. await后的异步操作出错

Promise内部直接抛出错误(如以下的throw new Error('出错了')),则Promise状态会变为rejectedreject的参数即为抛出的错误对象(如以下的Error('出错了')

(async function () {
    await new Promise(() => {
        throw new Error('出错了')
        console.log('hello') // 不会执行
    })
})()
    .catch(e => console.log('err~', e))
// err~ Error: 出错了

3. async函数内部抛出错误

async函数内部直接抛出错误,会导致async函数返回的 Promise 对象变为rejected状态。抛出的错误对象会被catch方法回调函数接收到。

(async function () {
    throw new Error('出错了');
    console.log('hello'); // 不会执行
})()
    .catch(e => console.log('err~', e))
// err~ Error: 出错了

// 类似于如下
new Promise(() => {
    throw new Error('出错了')
    console.log('hello'); // 不会执行
})
    .catch(e => console.log('err~', e))

错误处理方式

如果希望即使一个异步操作失败,也不要中断后面的异步操作,可以将可能出错的await放在try...catch结构里面,这样不管这个异步操作是否成功,后面的await都会执行。

async function f() {
    try {
        await Promise.reject('出错了')
    } catch (e) {}
    return await Promise.resolve('hello world')
}

f().then(v => console.log(v))
// hello world

如果await后跟的是Promise对象,则也可以后面的 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命令,可以统一放在try...catch结构中。

async function main() {
    try {
        const val1 = await firstStep();
        const val2 = await secondStep(val1);
        const val3 = await thirdStep(val1, val2);

        console.log('Final: ', val3);
    } catch (err) {
        console.error(err);
    }
}

Promise对象的状态变化

async函数返回的 Promise 对象,必须等到async函数内部所有await命令后面的 Promise 对象执行完,才会发生状态改变,除非遇到return语句或者抛出错误。也就是说,只有async函数内部的异步操作执行完,才会执行then方法指定的回调函数。

示例代码:

function getSquare(num) {
    return new Promise((resolve) => {
        setTimeout(() => {
            resolve(num ** 2)
        }, 1000)
    })
}
async function fn(num) {
    const val1 = await getSquare(num)
    const val2 = await getSquare(val1)
    return val2
}
fn(3).then(console.log)
// 延迟两秒后,打印81

上面代码中,函数fn内部有两个操作:将输入平方、将得到的结果再次平方。只有这两个操作全部完成,才会执行then方法里面的console.log

继发与并发

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

let foo = await getFoo();
let bar = await getBar();

上面代码中,getFoogetBar是两个独立的异步操作(即互不依赖),被写成继发关系。这样比较耗时,因为只有getFoo完成以后,才会执行getBar,完全可以让它们同时触发。

如下写法,getFoogetBar是同时触发,这样就会缩短程序的执行时间。

let [foo, bar] = await Promise.all([getFoo(), getBar()]);

示例代码:

// 定义求平方函数
function getSquare(num) {
    return new Promise((resolve) => {
        setTimeout(() => {
            resolve(num ** 2)
        }, 1000)
    })
}

// 继发处理
!(async function () {
    const arr = [1, 2, 3]
    const res = []
    for (const num of arr) {
        res.push(await getSquare(num))
    }

    console.log(res)
})()
// 延时3秒后,打印[1, 4, 9]

// 并发处理
!(async function () {
    const arr = [1, 2, 3]
    const promiseArr = arr.map(x => getSquare(x))
    let res = await Promise.all(promiseArr)

    console.log(res)
})()
// 延时1秒后,打印[1, 4, 9]

遍历

数组的forEach()map()等方法都不支持按顺序等待异步处理(即不支持上述继发处理),forfor...infor...of 都会按顺序等待异步。

示例代码:

function getSquare(num) {
    return new Promise((resolve) => {
        setTimeout(() => {
            resolve(num ** 2)
        }, 1000)
    })
}

!(function () {
    const arr = [1, 2, 3]
    arr.forEach(async num => {
        console.log(await getSquare(num));
    })
})()
// 延时1秒后,同时打印1 4 9

!(async function () {
    const arr = [1, 2, 3]
    for (const num of arr) {
        console.log(await getSquare(num));
    }
})()
// 延时1秒打印1,再延时1秒打印4,再延时1秒打印9

注意事项

  • await命令只能用在async函数之中,如果用在普通函数中会报错。

  • async函数体内,await命令之前的代码以及await命令所在语句中await后的代码都为正常的同步代码,在async函数调用时正常执行。

  • await命令后的Promise对象的运行结果可能是rejected,所以最好把await命令放在try...catch代码块中。

  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值