首頁 > 軟體

JavaScript非同步佇列進行try catch時的問題解決

2022-07-05 18:01:43

一、前言

我們在寫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內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!


IT145.com E-mail:sddin#qq.com