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