async-await 函數到底要不要加 try catch ?

作者:Ethan_Zhou

https://juejin.cn/post/7213362932423376933

前言

寫異步函數的時候,promise 和 async 兩種方案都非常常見,甚至同一個項目裏,不同的開發人員都使用不同的習慣, 不過關於兩者的比較不是本文關注的重點,只總結爲一句話:“async 是異步編程的終極解決方案”。

當使用 async 函數的時候,很多文章都說建議用 try catch 來捕獲異常, 可是實際上我看了很多項目的代碼,遵循的並不是嚴謹,很多都沒有用,甚至 catch 函數都沒寫,這是爲什麼呢?

我們先看下使用 try catch 情況下的代碼示例:

示例 1 :使用 try catch

function getUserInfo () {
    return new Promise((resolve, reject) ={
        setTimeout(() ={
                reject('請求異常')
        }, 1000)
    })
}

async function logined () {
    try {
        let userInfo = await getUserInfo()
        // 執行中斷
        let pageInfo = await getPageInfo(userInfo?.userId)
    } catch(e) {
        console.warn(e)
    }
}

logined()

執行後會在 catch 裏捕獲 請求異常,然後 getUserInfo 函數中斷執行,這是符合邏輯的,對於有依賴關係的接口,中斷執行可以避免程序崩潰,這裏唯一的問題是 try catch 貌似佔據了太多行數,如果每個接口都寫的話看起來略顯冗餘。

示例 2:直接 catch

鑑於正常情況下,await 命令後面是一個 Promise 對象, 所以上面代碼可以很自然的想到優化方案:

function getUserInfo () {
    return new Promise((resolve, reject) ={
        setTimeout(() ={
                reject('請求異常')
        }, 1000)
    })
}

async function logined () {
    let userInfo = await getUserInfo().catch(e => console.warn(e))
    // 執行沒有中斷,userInfo 爲 undefined
    if (!userInfo) return // 需要做非空校驗
    let pageInfo = await getPageInfo(userInfo?.userId)
}

logined()

執行後 catch 可以正常捕獲異常,但是程序沒有中斷,返回值 userInfoundefined, 所以如果這樣寫的話,就需要對返回值進行非空校驗, if (!userInfo) return 我覺得這樣有點反邏輯,異常時就應該中斷執行纔對;

示例 3:在 catch 裏 reject

可以繼續優化,在 catch 裏面加一行 return Promise.reject(e), 可以使 await 中斷執行;

完整代碼:

function getUserInfo () {
    return new Promise((resolve, reject) ={
        setTimeout(() ={
            reject('請求異常')
        }, 1000)
    })
}

async function logined () {
    let userInfo = await getUserInfo().catch(e ={
        console.warn(e)
        return Promise.reject(e) // 會導致控制檯出現 uncaught (in promise) 報錯信息
    })
    // 執行中斷
    let pageInfo = await getPageInfo(userInfo?.userId)
}

logined()

一般我們在項目裏都是用 axios 或者 fetch 之類發送請求,會對其進行一個封裝,也可以在裏面進行 catch 操作,對錯誤信息先一步處理,至於是否需要 reject,就看你是否想要在 await 命令異常時候中斷了;不使用 reject 則不會中斷,但是需要每個接口拿到 response 後先 非空校驗, 使用 reject 則會在異常處中斷,並且會在控制檯暴露 uncaught (in promise) 報錯信息。

建議

不需要在 await 處異常時中斷,可以這樣寫,需要做非空校驗,控制檯不會有報錯信息

let userInfo = await getUserInfo().catch(e => console.warn(e))
if (!userInfo) return

需要在 await 處異常時中斷,並且在意控制檯報錯,可以這樣寫

try {
    let userInfo = await getUserInfo()
    // 執行中斷
    let pageInfo = await getPageInfo(userInfo?.userId)
} catch(e) {
    console.warn(e)
}

需要在 await 處異常時中斷,但是不在意控制檯報錯,則可以這樣寫

let userInfo = await getUserInfo().catch(e ={
    console.warn(e)
    return Promise.reject(e) // 會導致控制檯出現 uncaught (in promise) 報錯信息
})
// 執行中斷
let pageInfo = await getPageInfo(userInfo?.userId)

總結

幾種寫法,初看可能覺得第三種 catch 這種寫法是最好的,但是細想下,從用戶體驗上來看,我覺得 try catch 是最好的,邏輯直觀、符合同步編程思維,控制檯不會暴露 uncaught (in promise) 報錯信息;

而鏈式調用的 catch (裏面再 reject),是傳統 promise 的回調寫法,既然已經用 async await 這種同步編程寫法了,再用 catch 鏈式寫法,感覺沒必要。

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/_NwTEPFTLe0SfjNMqw3VVg