<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
如果對Promise和trycatch不夠理解的話,很多時候會出現Promise中的錯誤無法被捕獲的情況,本文來討論這些情況
try catch
try catch
只能捕獲當前上下文中的錯誤,也就是隻能捕獲同步任務的情況,如下場景:
try { throw "程式執行遇到了一些錯誤"; } catch(e) { console.log(e) } // 控制檯會輸出:程式執行遇到了一些錯誤
這很好,錯誤被捕獲了,我們可以在程式中程序錯誤的處理;
但是對於非同步的任務,trycatch就顯得無能為力,不能正確捕獲錯誤:
try { setTimeout(() => { throw "程式執行遇到了一些錯誤" }) } catch(e) { console.log(e); } // 控制檯輸出:Uncaught 程式執行遇到了一些錯誤;
又或者這樣:
try { Promise.reject('程式執行遇到了一些錯誤'); } catch(e) { console.log(e); } // 控制檯輸出:Uncaught (in promise) 程式執行遇到了一些錯誤
上面的程式碼都無法正常捕獲到錯誤,因為:trycatch永遠捕獲的是同步的錯誤
什麼是同步的錯誤?
當在一個事件迴圈內,同一個任務佇列中出現的錯誤,對於這個任務所在的上下文而言,就是同步錯誤。
setTimeout和Promise被稱為任務源,來自不同的任務源註冊的回撥函數會被放入不同的任務佇列中。
setTimeout中的任務會被放入宏任務
Promise中的任務會被放入微任務
- 拓展:setTimeout是宿主瀏覽器發起的任務,一般會被放入宏任務
- 而Promise是由JS引擎發起的任務,會被放入微任務
第一次事件迴圈中,JS引擎會把整個script程式碼當成一個宏任務執行,執行完成之後,再檢測本次迴圈中是否存在微任務,存在的話就依次從微任務的任務佇列中讀取執行完所有的微任務,再讀取宏任務的任務佇列中的任務執行,再執行所有的微任務,如此迴圈。
JS的執行順序就是每次事件迴圈中的宏任務-微任務的不斷切換。
再看setTimeout中丟擲的錯誤,這個錯誤已經不在trycatch所在的事件迴圈中了,所以這是一個非同步錯誤,無法被trycatch捕獲到。
同理,Promise.reject()此處雖然是同步執行的,但是此處reject的內容卻在另一個微任務迴圈中,對於trycatch來講也不是同步的,所以這兩個錯誤都無法被捕獲。
Promise.reject
要理解Promise.reject首先要了解它的返回值,Promise.reject返回的是一個Promise物件,請注意:是Promise物件
。
Promise物件在任何時候都是一個合法的物件,它不是錯誤也不是異常,所以在任何實現,直接對Promise.reject或者一個返回Promise物件的呼叫直接try catch是沒有意義的,一個正常的物件永遠不可能觸發catch捕獲。
假設我們由如下程式碼:
function getData() { Promise.reject('遇到了一些錯誤'); }; function click() { try { getData(); } catch(e) { console.log(e); } } click(); // 我們模擬業務場景中的click事件 // 控制檯輸出: Uncaught (in promise) 遇到了一些錯誤
Promise已經通過reject丟擲了錯誤,為什麼try catch捕獲不到呢?
首先,需要知道,對於一個函數的錯誤是否可以被捕獲到,可以嘗試將函數呼叫的返回值替換到函數呼叫出,看看是否為一個錯誤
上面getDate()呼叫會被替換為undefined
;
對於一個沒有明確return的函數呼叫,其返回值永遠是undefined
的,所以程式碼如下:
function click() { try { undefined; } catch(e) { console.log(e); } }
瞭解js基礎的人肯定知道,這不算異常,這個程式碼會正常執行,不會走到catch中。
可能會有另一種思路,就是將Promise.reject返回出去,那麼程式碼就變成:
function getData() { return Promise.reject('遇到了一些錯誤'); }; function click() { try { getData(); } catch(e) { console.log(e); } } click();
Promise.reject返回的是一個Promise物件,它是物件,不是錯誤。所以在try catch中完成getData()呼叫後這裡會出現一個Promise物件,這個物件是一個再正常不過的物件,不會被catch捕獲,所以這個try catch依然是無效的。
於是,又出現一種思路:再呼叫處使用Promise的catch方法進行捕獲,於是程式碼變成:
function getData() { return Promise.reject('遇到了一些錯誤'); }; function click() { try { getData().catch(console.log); } catch(e) { console.log(e); } } click();
這是可行的,rejext的錯誤可以被捕獲,但這不是try catch的功勞,而是Promise的內部消化,所以這裡的try catch依然沒有意義。
解決Promise異常捕獲
Promise異常是最常見的非同步異常,其內部的錯誤基本都是被包裝成了Promise物件後進行傳遞,所以解決Promise非同步捕獲整體思路有兩個:
Promise.catch
對於Promise.reject中丟擲的錯誤,或者Promise構造器中丟擲的錯誤,亦或者then中出現的錯誤,無論是執行時還是通過throw主動丟擲的,原則上都可以被catch捕獲。
如下:
function getData() { Promise.reject('這裡發生了錯誤').catch(console.log); } function click() { getData(); } click();
亦或者在呼叫處捕獲,但這需要被呼叫的函數能返回Promise物件;
function getData() { return Promise.reject('程式發生了一些錯誤'); } function click() { getData().catch(console.log); } click();
上面兩個方案都可行,事實上建議在業務邏輯允許的情況下,將Promise都返回出去,以便能向上傳遞,同時配合**unhandledrejection**進行兜底
async await 非同步轉同步
使用async和await可以將一個非同步函數呼叫在語意上變成同步執行的效果,這樣我們就可以使用try catch去統一處理。
例如:
function getData() { return Promise.reject('程式發生錯誤'); } async function click() { try { await getData(); } catch(e) { console.log(e); } } click();
需要注意的是,如果getData方法沒有寫return, 那麼就無法將Promise物件向上傳遞,那麼呼叫出的await等到的就是一個展開的undefined, 依舊不能進行錯誤處理。
注意事項
一個函數如果內部處理了Promise非同步物件,那麼原則上其處理結果應該也是一個Promise物件,對於需要進行錯誤捕獲的場景,Promise物件應該始終通過return向上傳遞
兜底方案
一般情況下,同步錯誤如果沒有進行捕獲,那麼這個錯誤所在的事件迴圈將終止,所以在開發階段沒有捕獲的錯誤,使用一種方法進行兜底是很有必要的。
對於同步錯誤,可以定義window.onerror
進行兜底處理,或者使用window.addEventListener('error', errHandler)
來定義兜底函數。
對於Promise異常,則可以同步使用window.onunhandledrejection
或者window.addEventListener('unhandledrejection', errHandler)
來定義兜底函數。
Promise中的then的第二個引數和catch有什麼區別?
區別
主要區別就是,如果在then的第一個函數中丟擲了異常,後面的catch能捕獲到,但是then的第二個引數卻捕獲不到
then的第二個引數和catch捕獲資訊的時候會遵循就近原則,如果是promise內部報錯,reject丟擲錯誤後,then的第二個引數和catch方法都存在的情況下,只有then的第二個引數能捕獲到,如果then的第二個引數不存在,則catch方法會被捕獲到。
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錯誤捕獲內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!
相關文章
<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
综合看Anker超能充系列的性价比很高,并且与不仅和iPhone12/苹果<em>Mac</em>Book很配,而且适合多设备充电需求的日常使用或差旅场景,不管是安卓还是Switch同样也能用得上它,希望这次分享能给准备购入充电器的小伙伴们有所
2021-06-01 09:31:42
除了L4WUDU与吴亦凡已经多次共事,成为了明面上的厂牌成员,吴亦凡还曾带领20XXCLUB全队参加2020年的一场音乐节,这也是20XXCLUB首次全员合照,王嗣尧Turbo、陈彦希Regi、<em>Mac</em> Ova Seas、林渝植等人全部出场。然而让
2021-06-01 09:31:34
目前应用IPFS的机构:1 谷歌<em>浏览器</em>支持IPFS分布式协议 2 万维网 (历史档案博物馆)数据库 3 火狐<em>浏览器</em>支持 IPFS分布式协议 4 EOS 等数字货币数据存储 5 美国国会图书馆,历史资料永久保存在 IPFS 6 加
2021-06-01 09:31:24
开拓者的车机是兼容苹果和<em>安卓</em>,虽然我不怎么用,但确实兼顾了我家人的很多需求:副驾的门板还配有解锁开关,有的时候老婆开车,下车的时候偶尔会忘记解锁,我在副驾驶可以自己开门:第二排设计很好,不仅配置了一个很大的
2021-06-01 09:30:48
不仅是<em>安卓</em>手机,苹果手机的降价力度也是前所未有了,iPhone12也“跳水价”了,发布价是6799元,如今已经跌至5308元,降价幅度超过1400元,最新定价确认了。iPhone12是苹果首款5G手机,同时也是全球首款5nm芯片的智能机,它
2021-06-01 09:30:45