關於 promise 吃到錯誤的理解


關於 promise 吃到錯誤的理解

下面的內容需要對瀏覽器原生支持的 promise 的基本用法有了解,如果你還不知道 promise 和 promise 的 catch 方法,你可能需要先在 這里 了解一下。

在 阮一峰大神的 《ECMAScript 6 入門》 關於 Promise 對象那一章在介紹 Promise.prototype.catch() 方法時,里面有一句描述是這樣寫的 :

跟傳統的try/catch代碼塊不同的是,如果沒有使用catch方法指定錯誤處理的回調函數,Promise 對象拋出的錯誤不會傳遞到外層代碼,即不會有任何反應。

一開始我 錯誤的理解 了這句話的意思,認為是在 promise 內部如果發生錯誤的話,可以使用 catch 方法來捕獲錯誤,但是如果不使用 catch 方法,它會靜悄悄的,不會有報錯,就像加了 try-catch 但是在 catch 里面不進行任何處理。

然后我做了一些測試發現和我想的不一樣:

首先,試一下在 promise 里面拋出一個異常,看看能不能通過 catch 捕獲到

var promise = new Promise(function(resolve, reject) {
    throw new Error('test');   // 這里拋了一個異常
});
promise
    .then(function(value) { console.log(value) })
    .catch((err) => console.log('promise catch err'))

// 控制台輸出:
// promise catch err

沒什么問題,catch 方法成功捕獲到異常了,但是這個異常會不會再被拋到外面去呢?為了以防萬一,我們在window 對象上加個 onerror 事件監測一下看看

window.onerror = function () {
    console.log('window err')
} 

var promise = new Promise(function(resolve, reject) {
    throw new Error('test');
});
promise
    .then(function(value) { console.log(value) })
    .catch((err) => console.log('promise catch err'))

// 控制台輸出:
// promise catch err

結果還是一樣,說明 catch 方法成功捕獲到異常而且沒有繼續往外面拋,這和書里說的一樣

那如果去掉 catch 方法呢?我們繼續往下看

var promise = new Promise(function(resolve, reject) {
    throw new Error('test');
});
promise.then(function(value) { console.log(value) })    // 這里不捕獲異常了

// 控制台輸出:
// Uncaught (in promise) Error: test

這里控制台輸出了一個報錯:Uncaught (in promise) Error: test,咦,如果像書里說的:

跟傳統的try/catch代碼塊不同的是,如果沒有使用catch方法指定錯誤處理的回調函數,Promise 對象拋出的錯誤不會傳遞到外層代碼,即不會有任何反應。

上面說不會有任何反應,那怎么控制台還會報錯呢?

我們可以猜測一下,其實這里有點像使用 try-catch 那樣捕獲了異常然后打印出錯誤信息后就不再做其他處理一樣:

try{
    console.log(x)
}catch(err){
    console.error(err)
}

然后我們加一個 window.onerror 發現,這時 promise 外面添加錯誤監聽事件不會捕獲到 promise 對象里面沒有進行捕獲的錯誤,像下面這樣:

window.onerror = function () {   // 我們添加了 window 的 onerror 處理函數
    console.log('window err')
} 

promise = new Promise(function(resolve, reject) {
    throw new Error('test');
});
promise.then(function(value) { console.log(value) })   

// 控制台輸出:
// Uncaught (in promise) Error: test   

可以看到 window 的錯誤處理事件並沒有被觸發,所以報錯應該是 promise 內部捕獲處理的時候直接打印的而沒有被拋出,也就驗證了我們上面的猜測。

所以到這里,我們可以把書上那句話重新翻譯一下得到一條結論

promise 對象里面同步代碼拋出的錯誤在沒有通過 promise 的 catch 方法捕獲時是會打印報錯的(不會阻止 promise 外面代碼的執行),但是不會傳遞到外面觸發其他錯誤監聽函數(比如 window.onerror 、try-catch 等)


書上還講到一個東西也挺奇怪的

window.onerror = function () {
    console.log('window err')
} 

var promise = new Promise(function (resolve, reject) {
    setTimeout(function () { 
        throw new Error('test') 
    }, 0)

    resolve('ok');
});
promise
    .then(function (value) { console.log(value) })
    .catch(() => console.log('promise catch err'))

根據我們上面的理解,你覺得這個控制台會顯示什么東西呢?

如果你對答案是 promise catch err,那你就錯了,他的結果是:

// 控制台輸出:
// ok
// window err
// Uncaught Error: test

這里由於是在 setTimeout 里面拋出錯誤的,所以報錯會在同步代碼執行完后的下一輪 “事件循環” 里執行,也就是說當 setTimeout 里面的函數執行后報錯時,promise 已經執行完了(所以就算 resolve('ok') 寫在 setTimeout 下面也是先輸出 ok),所以這個錯誤是在 Promise 函數體外拋出的,當然也就不會被 promise 的 catch 方法捕獲,所以就會傳到 window 上被捕獲並輸出 window err,然后再被瀏覽器捕獲輸出Uncaught Error: test,如果在 window onerror 處理程序里面 return true,就不會看到瀏覽器捕獲輸出的 Uncaught Error: test 報錯。

再看一下 下面的兩個例子:

這里是在 promise 里面同步執行了 throw Error 和 resolve 兩個操作

window.onerror = function () {
    console.log('window err');
} 

var promise = new Promise(function (resolve, reject) {
    throw new Error('test');
    resolve('ok');
});
promise.then(function (value) { console.log(value) })
    .catch(() => console.log('promise catch err'))

// 控制台輸出:
// promise catch err

這里是在 promise 里面用 setTimeout 異步執行了 throw Error 和 resolve 兩個操作

window.onerror = function () {
    console.log('window err');
} 

var promise = new Promise(function (resolve, reject) {
    setTimeout(function () {               // 這里包了一個 setTimeout
        throw new Error('test');
        resolve('ok');
    }, 0)
});
promise.then(function (value) { console.log(value) })
    .catch(() => console.log('promise catch err'))

// 控制台輸出:
//window err
//Uncaught Error: test

可以看到第二個例子中就算 promise 的狀態還是 pedding ,異步操作里面的報錯也不會被 promise 的 catch 方法捕獲

所以這里又可以得到一條結論

Promise.prototype.catch() 方法對錯誤處理和捕獲的規則只對 promise 里面的同步執行代碼有效,如果此時 promise 里面有異步操作出錯的話,是不受 promise 這些規則限制的,而是像正常的報錯一樣處理。

總結:

  1. promise 對象里面同步代碼拋出的錯誤在沒有通過 promise 的 catch 方法捕獲時是會打印報錯的(不會阻止 promise 外面代碼的執行),但是不會傳遞到外面觸發其他錯誤監聽函數(比如 window.onerror 、try-catch 等)
  2. Promise.prototype.catch() 方法對錯誤處理和捕獲的規則只對 promise 里面的同步執行代碼有效,如果此時 promise 里面有異步操作出錯的話,是不受 promise 這些規則限制的,而是像正常的報錯一樣處理。
  3. 其實上面兩條規則可以看出 promise 對錯誤的處理應該是在內部使用了像 try-catch 的方式處理了錯誤,所以異步的它是處理不了的。就行下面這樣一樣,try-catch 並不會捕獲到錯誤,錯誤會被 window.onerror 捕獲
window.onerror = function () {
    console.log('window err');
    return true
} 

try{
    setTimeout(function () { 
        console.log(x)    // 這里 x 未定義
    }, 10)
}catch(err){
    console.log('try err')
}

// 控制台輸出:
//window err

最后,可能還會有一些理解上的錯誤,希望大家多多指正。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM