async/await 和 promise/promise.all 的示例

big_cat發表於2021-12-16

概述

promise 為 js 提供了併發非同步能力。

promise.all 可以等待一批 promise 任務 全部執行完成 後返回 結果集合

async/await 可以使一批 promise同步序列 的模式去執行(一些介面依賴場景是有此需求的)。

Promise / Promise.all

Promise 提供了非同步介面,搭配 resolve/reject 可以很方便的讓業務程式碼併發執行,但同時也產生了 回撥地獄 問題。

Promise.all 則是併發執行任務集合,且等待所有的任務執行完成後,一併返回結果集。

function promiseReq(url) {
    return new Promise((resolve, reject) => {
        fetch(url).then((res) => {
            resolve(res)
        }).catch((e) => {
            reject(e)
        })
    })
}
promiseReq(url)
.then(res => console.log(res))
.catch(e => console.error(e))

async / await

async 正如其名,用於定義一個非同步方法,它會自動將方法封裝成一個 Promise 返回,並且使用 return 代表 resolvethrow 代表 reject

async function asyncFoo() {
    if (Math.floor(Math.random() * 10) > 5) {
        return "asyncFoo resolved"
    }
    throw "asyncFoo rejected"
}

console.log(asyncFoo() instanceof Promise)

asyncFoo().then((res) => {
    console.log(res)
}).catch((e) => {
    console.error(e)
})

但日常開發中我們並不會單獨使用 async,而是搭配 await 去同步多個 promise 任務。await 的作用就是讓 Promise 的非同步回撥降維至同步模式。

場景1 - 介面併發執行且資料結果彙總

當需要等待一批介面的資料全部返回後,才可以繼續執行後面的介面時,則可以使用 Promise.all 來處理 一批任務。其會 併發 執行 任務集合 中的 Promise 任務,等待所有的任務執行完成後,彙總結果並返回(結果陣列)。

function promiseEnum(countDown) {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve("promiseEnum countDown " + countDown)
        }, countDown * 1000)
    })
}

Promise.all([promiseEnum(1), promiseEnum(2), promiseEnum(3)])
.then((values) => {
    // 等待 1 2 3 併發執行完成
    console.log("Promise.all values", values)
    promiseEnum(4).then((res4) => {
        console.log(res4)
    })
})

場景2 - 介面順序依賴執行

試想一下,有4個資料介面,後者依賴前者返回的結果才能繼續執行,如果使用傳統的 Promise,那大概就是

// callback hell
promiseReq(url1).then((res1) => {
    promiseReq(url2 + res1).then((res2) => {
        promiseReq(url3 + res2).then((res3) => {
            promiseReq(url4 + res3).then((res4) => {
                console.log("promiseReq finished")
            }).catch(err => console.error(err))
        }).catch(err => console.error(err))
    }).catch(err => console.error(err))
}).catch(err => console.error(err))

使用 async/await 則可以友好的解決依賴回撥問題,讓程式碼以 同步 的風格編寫和執行。

// 使用 async/await 則可以友好的解決依賴回撥問題
async function promiseSyncReq() {
    let res1 = await promiseReq(url1)
    let res2 = await promiseReq(url2 + res1)
    let res3 = await promiseReq(url3 + res2)
    let res4 = await promiseReq(url4 + res3)
    return res4
}
promiseSyncReq().then((res4) => {
    console.log(res4)
}).catch(e => console.log(err))

async/await 的執行耗時等於各個 Prmoise 累計的總耗時(執行流本就是要序列,耗時自然是各介面的累計,回撥模式也一樣的)。

async function awaitAllPromise() {
    let res1 = await promiseEnum(1)//阻塞等待 耗時1秒
    let res2 = await promiseEnum(2)//阻塞等待 耗時2秒
    let res3 = await promiseEnum(3)//阻塞等待 耗時3秒
    let res4 = await promiseEnum(4)//阻塞等待 耗時4秒
    
    //執行總耗時為各 Promise 耗時的累加
    return [res1, res2, res3, res4]
}

組合使用

async/awaitpromise/promise.all 組合使用,靈活實現 部分任務併發執行部分任務同步執行

async function batchExec() {
    console.group("batchExec")

    let startTime = new Date().getTime()
    console.log("batchExec start", startTime)
    
    // 等待 1,2,3 併發執行完成
    let paralValues = await Promise.all([promiseEnum(1), promiseEnum(2), promiseEnum(3)])

    let paralTime = new Date().getTime()
    console.log("parallel 1,2,3 finished", paralTime, (paralTime - startTime) / 1000, paralValues)

    // 再繼續執行 4
    let res4 = await promiseEnum(4)

    let endTime = new Date().getTime()
    console.log("batchExec end", endTime, (endTime - startTime) / 1000)

    console.groupEnd("batchExec")

    return [...paralValues, res4]
}

batchExec()
.then(res => console.log(res))
.catch(e => console.error(e))

相關文章