Promise 和 async-await 的異常處理,原來還有這種坑!

Promise 和 async/await 在 JavaScript、TypeScript 中已經很常見了,但你有遇到過這個報錯嗎?

這篇文章爲你分析此類問題的成因和解決辦法。

先看一個典型的例子

function getList(){
    return new Promise((rs,rj)=>{
        rj('假裝發生了錯誤')
    })
}

try{
    getList();
}
catch(e){
    alert('遇到了些許錯誤哦~')
}

一看就是有經驗的攻城獅,使用 try...catch... 捕獲異常,發生錯誤時顯示友好的提示。

emmm…… 可是實際會發生什麼呢?

答案是,此處的異常將無法捕獲到!當getList()拋出異常後,嗯,流程就失控了。

Promise 的異常捕獲方式

打開控制檯會發現,上面的例子將會拋出 Uncaught (in promise)異常。

這裏要提到 Promise 的異常處理流程。

Promise 創建時需要傳入一個 function,在這個 function 執行過程中,如果出現了異常則會對外拋出。外部有 2 種方式來捕獲這個異常:

方式一:catch()

let promise = new Promise(...);
promise.catch(e=>{
    // TODO sth with e
})

方式二:async/await中的try...catch...

let promise = new Promise(...);
async function test(){
    try{
        await promise;
    }
    catch(e){
        // TODO sth with e
    }
}

全局異常

如果這兩種方式都沒有出現,則異常將會被視爲 “Uncaught (in promise)” 被拋出到全局去。在 NodeJS 中,你可以通過process.on('unhandledRejection', e => {...})來捕獲全局異常。遺憾的是,在瀏覽器中,尚未發現有效的方式能捕獲此類全局異常。所以應當極力避免出現全局異常的情況,尤其是在前端,可能由異常導致流程中斷。

只要 Promise 的異常被方式一或方式二捕獲,就不會拋出全局異常!

未捕獲異常的原因

例子中的 Promise 異常未被正常捕獲,是因爲promise雖然出現在try...catch...中,但是並沒有被await,如此將不進入上述的異常捕獲流程,一旦出現異常並且沒有其它有效的 catch 時,就將拋出至全局。

嗯,看起來似乎你已經瞭解了,那麼把例子 1 按如下修改一下,你覺得是否可以正常工作呢?

function getList(){
    return new Promise((rs,rj)=>{
        rj('假裝發生了錯誤')
    })
}

async function main(){
    try{
        getList();
    }
    catch(e){
        alert('遇到了些許錯誤哦~')
    }
}

main();

依舊不行!因爲 Promise 雖然出現在了 async 方法中,但由於沒有被 await,所以其異常視爲全局異常。

此處有神坑!請注意!

new Promise(rs=>{
    throw new Error('Error')
}).catch(e=>{
    console.log('異常被捕獲到了1')
})

new Promise(async rs=>{
    throw new Error('Error')
}).catch(e=>{
    console.log('異常被捕獲到了2')
})

上面 2 個 Promise 裏的異常,能被捕獲到嗎?

答案是第一個 Promise 裏的異常能被捕獲,因爲 Promise 裏同步拋出的異常,也會被視爲Promise.reject。但第二個 Promise,由於裏面的函數是 async 的,異常是異步拋出的,所以並不會觸發 Promise 的 reject ,因此 Promise.catch 也就捕獲不到。

怎麼理解呢?換個方式可能好理解一些。 async 函數本身,就是 Promise 的另一種寫法,二者一定能夠互相轉化且等效。轉化成 Promise 的等效寫法就是:

new Promise(rs=>{
    // async 相當於同步函數里又包了一層Promise
    return new Promise(()=>{
        // 內層Promise拋出異常
        throw new Error('Error')
    })
}).catch(e=>{  // 這裏catch的是外層Promise
    // 由於異常並未向上拋給外層Promise,所以此處catch不到
    console.log('異常被捕獲到了2')
})

如之前所述,Promise 內拋出的異常,無論身在何處,只要未經捕獲,就會直接上升爲全局 “未經捕獲的異常”,而不是層層拋出。

小測試

嗯,如果你已經瞭解了這些重要的區別,那麼用下面的例子測試一下吧~

看看下面的例子,getList()中的異常能被捕獲到嗎?

async function getList(){
    new Promise((rs,rj)=>{
        rj('假裝發生了錯誤')
    });
}

async function main(){
    try{
        await getList().catch(e=>{
            console.log('異常捕獲到了,位置1')
        });
    }
    catch(e){
        console.log('異常捕獲到了,位置2')
    }
}

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