JavsScript中Promise的錯誤捕獲詳解
我們需要在異步任務中準確的進行錯誤捕獲,以便我們可以知道錯誤出在什么地方
如果對Promise和trycatch不夠理解的話,很多時候會出現(xiàn)Promise中的錯誤無法被捕獲的情況,本文來討論這些情況
try catch
try catch
只能捕獲當前上下文中的錯誤,也就是只能捕獲同步任務的情況,如下場景:
try { throw "程序執(zhí)行遇到了一些錯誤"; } catch(e) { console.log(e) } // 控制臺會輸出:程序執(zhí)行遇到了一些錯誤
這很好,錯誤被捕獲了,我們可以在程序中進程錯誤的處理;
但是對于異步的任務,trycatch就顯得無能為力,不能正確捕獲錯誤:
try { setTimeout(() => { throw "程序執(zhí)行遇到了一些錯誤" }) } catch(e) { console.log(e); } // 控制臺輸出:Uncaught 程序執(zhí)行遇到了一些錯誤;
又或者這樣:
try { Promise.reject('程序執(zhí)行遇到了一些錯誤'); } catch(e) { console.log(e); } // 控制臺輸出:Uncaught (in promise) 程序執(zhí)行遇到了一些錯誤
上面的代碼都無法正常捕獲到錯誤,因為:trycatch永遠捕獲的是同步的錯誤
什么是同步的錯誤?
當在一個事件循環(huán)內(nèi),同一個任務隊列中出現(xiàn)的錯誤,對于這個任務所在的上下文而言,就是同步錯誤。
setTimeout和Promise被稱為任務源,來自不同的任務源注冊的回調(diào)函數(shù)會被放入不同的任務隊列中。
setTimeout中的任務會被放入宏任務
Promise中的任務會被放入微任務
- 拓展:setTimeout是宿主瀏覽器發(fā)起的任務,一般會被放入宏任務
- 而Promise是由JS引擎發(fā)起的任務,會被放入微任務
第一次事件循環(huán)中,JS引擎會把整個script代碼當成一個宏任務執(zhí)行,執(zhí)行完成之后,再檢測本次循環(huán)中是否存在微任務,存在的話就依次從微任務的任務隊列中讀取執(zhí)行完所有的微任務,再讀取宏任務的任務隊列中的任務執(zhí)行,再執(zhí)行所有的微任務,如此循環(huán)。
JS的執(zhí)行順序就是每次事件循環(huán)中的宏任務-微任務的不斷切換。
再看setTimeout中拋出的錯誤,這個錯誤已經(jīng)不在trycatch所在的事件循環(huán)中了,所以這是一個異步錯誤,無法被trycatch捕獲到。
同理,Promise.reject()此處雖然是同步執(zhí)行的,但是此處reject的內(nèi)容卻在另一個微任務循環(huán)中,對于trycatch來講也不是同步的,所以這兩個錯誤都無法被捕獲。
Promise.reject
要理解Promise.reject首先要了解它的返回值,Promise.reject返回的是一個Promise對象,請注意:是Promise對象
。
Promise對象在任何時候都是一個合法的對象,它不是錯誤也不是異常,所以在任何實現(xiàn),直接對Promise.reject或者一個返回Promise對象的調(diào)用直接try catch是沒有意義的,一個正常的對象永遠不可能觸發(fā)catch捕獲。
假設我們由如下代碼:
function getData() { Promise.reject('遇到了一些錯誤'); }; function click() { try { getData(); } catch(e) { console.log(e); } } click(); // 我們模擬業(yè)務場景中的click事件 // 控制臺輸出: Uncaught (in promise) 遇到了一些錯誤
Promise已經(jīng)通過reject拋出了錯誤,為什么try catch捕獲不到呢?
首先,需要知道,對于一個函數(shù)的錯誤是否可以被捕獲到,可以嘗試將函數(shù)調(diào)用的返回值替換到函數(shù)調(diào)用出,看看是否為一個錯誤
上面getDate()調(diào)用會被替換為undefined
;
對于一個沒有明確return的函數(shù)調(diào)用,其返回值永遠是undefined
的,所以代碼如下:
function click() { try { undefined; } catch(e) { console.log(e); } }
了解js基礎的人肯定知道,這不算異常,這個代碼會正常執(zhí)行,不會走到catch中。
可能會有另一種思路,就是將Promise.reject返回出去,那么代碼就變成:
function getData() { return Promise.reject('遇到了一些錯誤'); }; function click() { try { getData(); } catch(e) { console.log(e); } } click();
Promise.reject返回的是一個Promise對象,它是對象,不是錯誤。所以在try catch中完成getData()調(diào)用后這里會出現(xiàn)一個Promise對象,這個對象是一個再正常不過的對象,不會被catch捕獲,所以這個try catch依然是無效的。
于是,又出現(xiàn)一種思路:再調(diào)用處使用Promise的catch方法進行捕獲,于是代碼變成:
function getData() { return Promise.reject('遇到了一些錯誤'); }; function click() { try { getData().catch(console.log); } catch(e) { console.log(e); } } click();
這是可行的,rejext的錯誤可以被捕獲,但這不是try catch的功勞,而是Promise的內(nèi)部消化,所以這里的try catch依然沒有意義。
解決Promise異常捕獲
Promise異常是最常見的異步異常,其內(nèi)部的錯誤基本都是被包裝成了Promise對象后進行傳遞,所以解決Promise異步捕獲整體思路有兩個:
- 使用Promise的catch方法內(nèi)部消化;
- 使用async和await將異步錯誤轉(zhuǎn)同步錯誤再由try catch捕獲
Promise.catch
對于Promise.reject中拋出的錯誤,或者Promise構造器中拋出的錯誤,亦或者then中出現(xiàn)的錯誤,無論是運行時還是通過throw主動拋出的,原則上都可以被catch捕獲。
如下:
function getData() { Promise.reject('這里發(fā)生了錯誤').catch(console.log); } ? function click() { getData(); } ? click();
亦或者在調(diào)用處捕獲,但這需要被調(diào)用的函數(shù)能返回Promise對象;
function getData() { return Promise.reject('程序發(fā)生了一些錯誤'); } function click() { getData().catch(console.log); } click();
上面兩個方案都可行,事實上建議在業(yè)務邏輯允許的情況下,將Promise都返回出去,以便能向上傳遞,同時配合**unhandledrejection**進行兜底
async await 異步轉(zhuǎn)同步
使用async和await可以將一個異步函數(shù)調(diào)用在語義上變成同步執(zhí)行的效果,這樣我們就可以使用try catch去統(tǒng)一處理。
例如:
function getData() { return Promise.reject('程序發(fā)生錯誤'); } async function click() { try { await getData(); } catch(e) { console.log(e); } } click();
需要注意的是,如果getData方法沒有寫return, 那么就無法將Promise對象向上傳遞,那么調(diào)用出的await等到的就是一個展開的undefined, 依舊不能進行錯誤處理。
注意事項
一個函數(shù)如果內(nèi)部處理了Promise異步對象,那么原則上其處理結果應該也是一個Promise對象,對于需要進行錯誤捕獲的場景,Promise對象應該始終通過return向上傳遞
兜底方案
一般情況下,同步錯誤如果沒有進行捕獲,那么這個錯誤所在的事件循環(huán)將終止,所以在開發(fā)階段沒有捕獲的錯誤,使用一種方法進行兜底是很有必要的。
對于同步錯誤,可以定義window.onerror
進行兜底處理,或者使用window.addEventListener('error', errHandler)
來定義兜底函數(shù)。
對于Promise異常,則可以同步使用window.onunhandledrejection
或者window.addEventListener('unhandledrejection', errHandler)
來定義兜底函數(shù)。
我們再討論then方法中的第二個參數(shù)和Promise.catch方法的區(qū)別
Promise中的then的第二個參數(shù)和catch有什么區(qū)別?
- reject是用來拋出錯誤的,屬于Promise的方法
- catch是用來處理異常的,屬于Promise實例的方法、
區(qū)別
主要區(qū)別就是,如果在then的第一個函數(shù)中拋出了異常,后面的catch能捕獲到,但是then的第二個參數(shù)卻捕獲不到
then的第二個參數(shù)和catch捕獲信息的時候會遵循就近原則,如果是promise內(nèi)部報錯,reject拋出錯誤后,then的第二個參數(shù)和catch方法都存在的情況下,只有then的第二個參數(shù)能捕獲到,如果then的第二個參數(shù)不存在,則catch方法會被捕獲到。
題: then方法的連續(xù)調(diào)用,怎么能夠知道是第幾個then方法報錯了呢。
new Promise((resolve,reject) => { setTimeout(() => { resolve(1); }, 1000) }).then(res => { console.log(res); return new Promise((resolve,reject) => { reject('第一個then方法報錯了'); }) }).then(res => { console.log(res); return new Promise((resolve,reject) => { reject('第二個then方法報錯了'); }) }).catch(err => { console.log(err); })
總結
到此這篇關于JavsScript中Promise錯誤捕獲的文章就介紹到這了,更多相關JS Promise錯誤捕獲內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
JavaScript實現(xiàn)淘寶購物件數(shù)選擇
這篇文章主要為大家詳細介紹了JavaScript實現(xiàn)淘寶購物件數(shù)的選擇,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下2022-08-08JS循環(huán)中正確使用async、await的姿勢分享
async?/?await是ES7的重要特性之一,也是目前社區(qū)里公認的優(yōu)秀異步解決方案,下面這篇文章主要給大家介紹了關于JS循環(huán)中正確使用async、await的相關資料,文中通過實例代碼介紹的非常詳細,需要的朋友可以參考下2021-12-12JavaScript實現(xiàn)圖片DIV豎向滑動的方法
這篇文章主要介紹了JavaScript實現(xiàn)圖片DIV豎向滑動的方法,涉及javascript操作div層的相關技巧,需要的朋友可以參考下2015-04-04