JavaScript異步隊列進行try catch時的問題解決

一、前言

我們在寫js的時候,經常的會遇到需要異步去請求接口,或者通過setTimeout或Promise去做什麼事, 然後讓同步進程繼續向下走, 當到某個時間節點的時候或者數據請求成功的時候在通過eventloop的方式回調執行。這本身是js的特點和優勢。

但是,異步隊列執行也存在錯誤的情況,這時,對於怎麼進行錯誤處理,就成瞭我們的重點。

想一下項目中用到的方式,或者jquery的ajax方式,一般都會有catch、fail之類的回調方法供我們對錯誤結果進行處理。 那麼現在討論的話題是能不能使用try catch進行處理。

為什麼寫這篇文章? 是因為我在寫JavaScript 的setTimeout與事件循環機制event-loop的時候,舉例express的異步錯誤獲取的時候,想到瞭這個點,我覺得有必要單獨拿出來,寫一篇斷篇幅的,又能夠清晰明瞭表達的一篇文章。於是這篇文章便生成瞭。

好瞭, 正文開始。

二、主要講的異步隊列方法

2.1 setTimeout

這裡的setTimeout指的是一類,包括 setTimeoutsetInterval這類所謂宏任務。 他們可以用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!

推薦閱讀: