当前位置 博文首页 > 彭加李:es6快速入门 系列 - async

    彭加李:es6快速入门 系列 - async

    作者:彭加李 时间:2021-06-30 18:33

    其他章节请看:

    es6 快速入门 系列

    async

    前文我们已经知道 promise 是一种异步编程的选择。而 async 是一种用于执行异步任务更简单的语法。

    Tip:建议学完 Promise 在看本文。

    async 函数

    async 函数是使用 async 关键字声明的函数。就像这样:

    async function fa(){
    
    }
    

    async 函数可以看作由多个异步操作包装成的一个 Promise 对象。

    async 函数返回 Promise

    async 函数总是返回一个 Promise 对象。如果一个 async 函数的返回值看起来不是 promise,那么它将会被隐式地包装在一个 promise 中。请看示例:

    async function fa() {
        return 1
    }
    
    // 等价于
    
    function fa() {
        return Promise.resolve(1)
    }
    
    console.log( fa() instanceof Promise) // true
    

    即使 async 方法中没有显示的 return ,async 方法仍会返回 Promise。请看示例:

    async function fa() {}
    console.log( fa() instanceof Promise)
    

    fa 方法等价于:

    function fa() {
        return Promise.resolve()
    }
    

    async 函数多种形式

    async 函数有多种使用形式。例如:

    // 函数表达式
    const fa = async funciton() {};
    
    // 对象的方法
    let obj = {async foo(){}}
    
    // Class 的方法
    class Dog{
        async say(){}
    }
    
    // 箭头函数
    const fa = async () => {}
    

    形式虽然很多,但都是在函数前面增加 async 关键字。

    async 函数中的 return

    async 函数内的 return 返回值,会成为 then() 方法回调函数的参数。请看示例:

    async function foo() {
        return 'hello'
    }
    
    foo().then(v => {
        console.log(v)
    })
    
    // hello
    

    async 函数内部抛出的错误会导致返回的 Promise 对象变为 reject 状态。抛出的错误对象会被 catch 方法回调接收到。请看示例:

    async function foo() {
        throw new Error('fail')
        return 'hello'
    }
    
    foo().catch(v => {
        console.log(v.message)
    })
    
    // fail
    

    Promise 对象的状态变化

    async 函数返回的 Promise 对象必须等到内部所有 await 命令后的 Promise 对象执行完才会发生状态变化,除非遇到 return 语句,或者抛出错误才会立刻结束。请看示例:

    function createPromise(val, time = 1000){
        return new Promise((resolve, reject) => {
            setTimeout(() => {
                console.log(val)
                resolve(val)
            }, time)
        })
    }
    
    async function foo() {
        let a = await createPromise(1)
        let b = await createPromise(2)
        return {a, b}
    }
    
    foo().then(v => {
        console.log(v)
    }, v => {
        console.log(v.message)
    })
    
    /*
    1
    2
    { a: 1, b: 2 }
    */
    

    这段代码需要 2 秒,等待内部两个 Promise 状态都置为已完成,才会输出 { a: 1, b: 2 }

    如果遇到 return 或者抛出错误,则会立即结束。就像这样:

    async function foo() {
        // 遇到 return
        return 1
        // 或抛出错误
        // throw new Error('fail')
        let a = await createPromise(1)
        let b = await createPromise(2)
        return {a, b}
    }
    

    await

    asyn 函数可能包含 0 个或多个 await 表达式。就像这样:

    async function fa() {
        return await 1 
     }
    

    await 表达式会暂停整个 async 函数的执行进程并出让其控制权,只有当其等待的基于 promise 的异步操作被兑现或被拒绝之后才会恢复进程。promise 的解决值会被当作该 await 表达式的返回值。

    await 的返回值

    首先看一段代码:

    async function fa() {
        const result = await 1 
        return result
    }
    
    fa().then(v => {
        console.log(v)
    })
    
    // 1
    

    为什么 result 是 1?

    首先,因为 await 命令后面是一个 Promise 对象。如果不是,会被转为一个立即 resolve 的 Promise 对象。所以下面 fa() 方法是相等的:

    async function fa() {
        return await 1
    }
    
    // 等价于 
    
    async function fa() {
        return await Promise.resolve(1)
    }
    

    而在 Promise 中所学,我们知道 fa() 方法又等于如下代码:

    async function foo() {
        return await new Promise((resolve, reject) => {
            resolve(1)
        })
     }
    

    其次,Promise 的解决值会被当作该 await 表达式的返回值。所以 result 等于 1。

    如果删除 fa() 方法中的 return,将输出 undefined。请看示例:

    async function fa() {
        // 删除 return
        await 1 
    }
    
    fa().then(v => {
        console.log(v)
    })
    
    // undefined
    

    reject 中断 async 函数

    await 命令后的 Promise 对象如果变成 reject 状态,则 reject 的参数会被 catch 方法回调函数接收。就像这样:

    async function foo() {
        await Promise.reject(1) // {1}
     }
    
    foo().then(v => {
        console.log(v)
    }).catch(v => {
        console.log(`catch, ${v}`)
    })
    
    // catch, 1
    

    请注意,await 语句(行{1})前面没有 return 语句,但是 reject() 方法的参数依然传入了 catch 方法的回调函数中,这点与 resolve 状态不相同。

    只要一个 await 语句后面的 Promise 变成 reject,那么整个 async 函数都会中断。请看示例:

    async function foo() {
        await Promise.reject(1)
    
        await new Promise((resolve, reject) => {
            console.log(2)
            resolve()
        })
        return 3
     }
    
    foo().then(v => {
        console.log(v)
    }).catch(v => {
        console.log(`catch, ${v}`)
    })
    
    // catch, 1
    

    由于第一个 await 后面的 Promise 变成 reject,整个 async 函数就中断执行。

    如果我们希望前一个异步操作失败,也不中断后面的异步操作,可以这么写:

    try{
        await Promise.reject(1)
    }catch(e){
    
    }
    
    // 亦或者
    // 在 Promise 一文中提到拒绝处理程序能恢复整条链的执行
    await Promise.reject(1).catch(() => {})
    ...
    

    如果 await 后面的异步操作出错,那么等同于 async 函数返回的 Promise 对象被 reject。就像这样:

    async function foo() {
        await new Promise((resolve, reject) => {
            throw new Error('fail')
        })
        return 3
     }
    
    foo().then(v => {
        console.log(v)
    }).catch(v => {
        console.log(`catch, ${v}`)
    })
    
    // catch, Error: fail
    

    防止出错的方法也是将其放在 try ... catch 方法中。下面例子使用 try...catch 实现多次尝试:

    function request(v){
        return new Promise((resolve, reject) => {
            if(v == 2){
                console.log(`resolve${v}`)
                resolve(v)
            }else{
                console.log(`fail${v}`)
                throw new Error('fail')
            }
        })
    }
    
    async function foo() {
        for(let i = 0; i < 5; i++){
            try{
                await request(i)
                break
            }catch(e){}   // {1}
        }
        return 'end'
     }
    
    foo().then(v => {
        console.log(v)
    }).catch(v => {
        console.log(`catch, ${v}`)
    })
    
    // fail0 fail1 resolve2 end
    

    这段代码,如果 await 操作成功,则会使用 break 语句退出循环;如果失败,则会被 catch(行{1}) 捕获,然后进入下一轮循环。

    await 与并行

    下面的代码,会依次输出 1 和 2,属于串行。

    function createPromise(val, time = 1000){
        return new Promise((resolve, reject) => {
            setTimeout(() => {
                console.log(val)
                resolve(val)
            }, time)
        })
    }
    
    async function foo() {
        let a = await createPromise(1)
        let b = await createPromise(2)
    }
    
    foo()
    
    // 1 2
    

    如果多个异步操作不存在继发关系,最好让它们同时触发。将 foo() 方法改为下面任一方式:

    // 方式一
    async function foo() {
        let p1 = createPromise(1)
        let p2 = createPromise(2)
        // 至此,两个异步操作都已经发出
        await p1
        await p2
    }
    
    // 方式二
    async function foo() {
        let [p1, p2] = await Promise.all([createPromise(1), createPromise(2)])
    }
    

    再次运行,只需要 1 秒就会同时输出 1 2。

    async 函数中的 await

    await 关键字只能用在 async 函数中。请看示例:

    async function fa(){
        let arr = [1, 2, 3]
    
        arr.forEach(v => {
            await v
        })
    }
    // SyntaxError: await is only valid in async function
    

    这段代码将报语法错误。

    如果将 forEach 方法的参数改为 async 函数,就像这样:

    function createPromise(val, time = 1000){
        return new Promise((resolve, reject) => {
            setTimeout(() => {
                console.log(val)
                resolve(val)
            }, time)
        })
    }
    
    async function fa(){
        let arr = [1, 2, 3]
        // 改为 async 函数
        arr.forEach(async v => {
            await createPromise(v)
        })
    }
    
    fa()
    
    // 1 2 3
    

    等待 1 秒后同时输出 1 2 3。因为这 3 个异步操作是并发执行。

    如果希望多个请求并发执行,也可以使用 Promise.all 方法。就像这样:

    // 替换 fa() 方法即可
    async function fa(){
        let arr = [1, 2, 3]
        let promises = arr.map(v => createPromise(v))
        let results = await Promise.all(promises)
        console.log(results)
    }
    

    而如果需要继发,可以采用 for 循环:

    // 替换 fa() 方法即可
    async function fa(){
        let arr = [1, 2, 3]
        // 将 forEach 改为 for 循环
        for(let i = 0; i < arr.length; i++){
            await createPromise(arr[i]) 
        }
    }
    

    每过一秒,会依次输出 1 2 3。

    其他章节请看:

    es6 快速入门 系列

    bk
    下一篇:没有了