關於 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 這些規則限制的,而是像正常的報錯一樣處理。
總結:
- promise 對象里面同步代碼拋出的錯誤在沒有通過 promise 的 catch 方法捕獲時是會打印報錯的(不會阻止 promise 外面代碼的執行),但是不會傳遞到外面觸發其他錯誤監聽函數(比如 window.onerror 、try-catch 等)
Promise.prototype.catch()
方法對錯誤處理和捕獲的規則只對 promise 里面的同步執行代碼有效,如果此時 promise 里面有異步操作出錯的話,是不受 promise 這些規則限制的,而是像正常的報錯一樣處理。- 其實上面兩條規則可以看出 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
最后,可能還會有一些理解上的錯誤,希望大家多多指正。