前幾天看到 stack overflow 上的這個問題,感覺哪天莫名迷惘可能也會有相同疑問,特別記錄一下。
第一段程式碼:
const p1 = () => {
return new Promise((resolve, reject) => {
console.log("P1");
resolve();
});
};
const p2 = () => {
return new Promise((resolve, reject) => {
console.log("P2");
reject();
});
};
const p3 = () => {
return new Promise((resolve, reject) => {
console.log("P3");
resolve();
});
};
p1().catch(() => {
console.log("Caught p1");
}).then(p2).catch(() => {
console.log("Caught p2");
}).then(p3).catch(() => {
console.log("Caught p3");
}).then(() => {
console.log("Final then");
});
// 以上執行後會印出:
P1
P2
Caught p2
P3
Final then
第二段程式碼:
Promise.resolve().then(() => {
console.log("resolve #1");
return Promise.reject();
}).then(() => {
console.log("resolve #2");
return Promise.resolve();
}).then(() => {
console.log("resolve #3");
return Promise.resolve();
}).then(() => {
console.log("Final end");
}).catch(() => {
console.log("Caught");
});
// 以上會印出
resolve #1
Caught
問題:為什麼第一段在抓到 Caught p2
的錯誤後,後面的 then
還會繼續執行,而不是像第二段一樣,錯誤出現後面的程式碼就都不會做?
如果把第一段程式碼寫成同步搭配 try...catch
的版本,大致如下,會印出一樣的結果:
const f1 = () => {
console.log("F1");
};
const f2 = () => {
console.log("F2");
throw new Error();
};
const f3 = () => {
console.log("F3");
};
try {
f1();
} catch {
console.log("Caught f1");
}
try {
f2();
} catch {
console.log("Caught f2");
}
try {
f3();
} catch {
console.log("Caught f3");
}
console.log("Final code");
這樣除了比較看得清楚哪裡執行和出錯之外,還可以看出錯誤處理的用意。 try...catch
的想法是接到錯誤後,就不會讓它冒到全域,然後讓程式可以有一個修補的機會。也就是錯誤處理不但不會影響本來想執行的事,甚至是因為有它程式才能出錯而不中斷。
在 catch
中可以做的事是分析錯誤的種類或訊息 (例如使用 instanceof
過濾錯誤類型),然後有針對性的除錯。如果超出處理範圍,就再次拋出錯誤。例如以下的例子只處理 ReferenceError
(調用了不存在的變數),其餘的再次拋出:
try {
user = { /*...*/ };
} catch (err) {
if (err instanceof ReferenceError) {
alert('ReferenceError');
} else {
throw err; // rethrow (*)
}
}
同理,promise 的 catch()
也有一樣作用:讓程式在錯誤時可以採取措施、後面事情還可以往下做。也因此回到原 po 的問題,catch
執行並不等於中斷程式碼,而且呼叫 catch
和 then
一樣會回傳 promise,讓後面可以繼續鏈接。
反過來說,如果要中斷 promise 鏈,就像在 try...catch
,可以在接到錯誤後再次明確拋出錯誤或回傳一個 rejected promise,讓更後面的函式處理或通知出現不可修復的錯誤。
而原 po 的第二段程式碼之所以會出錯後就直接到最後的 catch
,是因為沒有別的更早的處理錯誤。寫成同步版大致如下:
try {
console.log("log #1");
throw new Error();
console.log("log #2");
console.log("log #3");
console.log("Final end");
} catch {
console.log("Caught");
}
道理都很簡單,但覺得原問題的這個回答很棒,不只是討論順序或哪一行有沒有執行,還對於錯誤處理的整體概念提供了解釋。