JavaScript異步隊列進行try catch時的問題解決
一、前言
我們在寫js的時候,經常的會遇到需要異步去請求接口,或者通過setTimeout或Promise去做什麼事, 然後讓同步進程繼續向下走, 當到某個時間節點的時候或者數據請求成功的時候在通過eventloop
的方式回調執行。這本身是js的特點和優勢。
但是,異步隊列執行也存在錯誤的情況,這時,對於怎麼進行錯誤處理,就成瞭我們的重點。
想一下項目中用到的方式,或者jquery的ajax方式,一般都會有catch、fail之類的回調方法供我們對錯誤結果進行處理。 那麼現在討論的話題是能不能使用try catch
進行處理。
為什麼寫這篇文章? 是因為我在寫JavaScript 的setTimeout與事件循環機制event-loop的時候,舉例express的異步錯誤獲取的時候,想到瞭這個點,我覺得有必要單獨拿出來,寫一篇斷篇幅的,又能夠清晰明瞭表達的一篇文章。於是這篇文章便生成瞭。
好瞭, 正文開始。
二、主要講的異步隊列方法
2.1 setTimeout
這裡的setTimeout指的是一類,包括 setTimeout
, setInterval
這類所謂宏任務。 他們可以用try catch來捕獲錯誤麼?
2.1.1 問題表現
try{ setTimeout(() => { let a = c; }, 100) } catch(e) { console.log('能獲取到錯誤麼??', e); }
結果是不能獲取到,程序直接報錯瞭, 那麼出現的後果可能就是整個頁面掛瞭,或者在node中,整個服務掛瞭。 我們的初心是想讓程序更加健壯,但卻做瞭無用功。
那麼我們在想,既然在setTimeout 外邊無法獲取,那麼能不能在setTimeout裡面先用try catch獲取一下,然後捕獲到錯誤後再傳出去呢? 想到就幹,繼續分析:
try{ setTimeout(() => { try { let a = c; } catch(e) { throw new Error('some variable is not defined'); } }, 100) } catch(e) { console.log('能獲取到錯誤麼??', e); }
很抱歉,想法很好,但是也不行。外邊也catch不到
。
2.1.2 問題原因
好瞭,我們把這個疑問分析一下吧。其實,這裡的根本原因還是剛開始提到的事件循環
。 事件循環不是空空的一句表述、一個概念,而是在代碼中實實在在存在的。
具體事件循環的相關知識,可以看下我很早前寫的JavaScript 的setTimeout與事件循環機制event-loop 文章。
回到這個例子中, 最外層的try catch是在一個task
中,我們定義它為我們js文件的同步主任務,從上到下執行到這裡瞭, 然後,會把裡面的setTimeout推到一個任務隊列
中, 這個隊列是存儲在內存中的,由V8來管理。然後主task就繼續向下執行, 一直到結束。
當該setTimeout時間到瞭,且沒有其它的task執行瞭, 那麼,就將這個setTimeout的代碼推入執行棧
開始執行。 當執行到錯誤代碼的時候,也就是這個 let a = c
, 因為c未定義,所以就會報錯。
但問題的本質是,這個錯誤跟最外層的try catch並不在一個執行棧中,當裡面執行的時候,外邊的這個task早已執行完, 他們的context(上下文)已經完全不同瞭。
所以,頁面會直接報錯,甚至程序崩潰。
2.2 Promise
我們知道,Promise
也是一個異步的處理過程,它對應事件循環中的微任務
。 那麼這裡其實與上面的setTimeout存在同樣的問題。
舉個例子:
try { new Promise((resolve, reject) => { reject('promise error'); }) } catch(e) { console.log('異步錯誤,能catch到麼??', e); }
相信大傢能夠推導出結果瞭: 也catch不到
原因其實與上面的setTimeout是一樣的,執行棧上下文已經不同瞭。
那麼針對Promise,ECMA官方已經給我們提供瞭一個方法,那就是 catch
, 通過catch我們獲取到錯誤,可以阻止程序崩潰。 但是喜歡發散思維的你可能會想到, 那我用catch接到瞭,是不是就可以讓外層的catch獲取到瞭呢? 想到就試一下
try { new Promise((resolve, reject) => { reject('promise error'); }).catch(e => { throw new Error(e); }) } catch(e) { console.log('異步錯誤,能catch到麼??', e); }
結果就是 不行
。相信大傢通過我詳細的例子和思維脈絡,對這塊已經真正掌握瞭吧?
2.3 callback
那麼通過上面的,大傢可能會有一種想法,隻要是callback,就是catch不住的。 其實這種想法是錯誤的,我通過一個例子來證明。
function Fn(cb) { console.log('callback執行瞭'); cb(); } try { const cb = () => { throw new Error('callback執行錯誤'); } Fn(cb); } catch(e) { console.log('能夠catch住麼???') }
其實這裡就是個煙霧彈, 考驗大傢對這個事件循環相關機制是不是明白瞭。
2.4 Async await
現在的項目中,大傢越來越願意使用Async await
這對 es7標準裡的api瞭。 因為它們這對組合是在是太好用瞭。 那麼通過異步等待的方式,用try catch能夠行麼?
那麼咱們使用一個例子驗證一下:
const asyncFn = () => { return new Promise((resolve, reject) => { setTimeout(() => { reject('asyncFn執行時出現錯誤瞭') }, 100); }) } const executedFn = async () => { try{ await asyncFn(); }catch(e) { console.log('攔截到錯誤..', e); } }
如果執行一下,就發現: catch到瞭!
asyncFn
裡面是有 Promise的,為什麼外邊就能catch到瞭呢? 是不是跟上面講的矛盾瞭呢? 其實並沒有。 看我分析一下:
async-await 是使用生成器、promise 和協程實現的,wait操作符還存儲返回事件循環之前的執行上下文,以便允許promise操作繼續進行。當內部通知解決等待的承諾時,它會在繼續之前恢復執行上下文。
所以說,能夠回到最外層的上下文, 那就可以用try catch 啦。
到此這篇關於JavaScript異步隊列進行try catch時的問題解決的文章就介紹到這瞭,更多相關JS try catch內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!
推薦閱讀:
- JS異步代碼單元測試之神奇的Promise
- vue中Promise的使用方法詳情
- 一篇文章帶你瞭解vue.js的事件循環機制
- 一文秒懂nodejs中的異步編程
- await-to-js源碼深入理解處理異步任務用法示例