try...catch 抓不到 Promise 的錯誤?原來是這么回事
在 JavaScript 中,try...catch 是我們處理錯誤的得力助手。我們很自然地認(rèn)為,只要把可能出錯的代碼放進(jìn) try 塊,catch 就一定能捕獲到異常。但當(dāng)你開始和 Promise 打交道時(shí),可能會遇到一個讓你困惑的場景:
try {
// 假設(shè)這是一個會失敗的 API 請求
fetch('https://non-existent-url.com/api');
console.log('請求已發(fā)送');
} catch (error) {
// 這里的 catch 會執(zhí)行嗎?
console.log('抓到錯誤了!', error);
}
// 控制臺輸出:
// 請求已發(fā)送
// Uncaught (in promise) TypeError: Failed to fetch咦?catch 塊根本沒有執(zhí)行!錯誤信息直接在控制臺炸開了,帶著一個扎眼的 Uncaught (in promise)。
這究竟是為什么?難道 try...catch 對 Promise 無效嗎?
別急,這并非 try...catch 的 bug,而是我們對 同步 與 異步 的理解出了偏差。

核心原因:try...catch 是同步的,而 Promise 是異步的
讓我們用一個更簡單的比喻來理解:
你點(diǎn)了一份外賣(發(fā)起一個 Promise 請求)。try...catch 就像你家門口的保安。
- 你下單的動作是瞬間完成的。你按下“支付”按鈕,App 立刻告訴你“下單成功,騎手正在路上”。這個“下單成功”的反饋是 同步 的。
- 保安 try...catch 只在你下單的那個瞬間盯著你。他看到你成功下了單,沒出任何問題(比如網(wǎng)絡(luò)斷了、余額不足等),于是他就下班了。
- 半小時(shí)后,騎手送餐路上翻車了(Promise 狀態(tài)變?yōu)?rejected)。這個錯誤發(fā)生在未來,發(fā)生在保安下班之后。保安自然是抓不到這個“錯誤”的。
回到代碼中:
- try { ... } 塊里的代碼是 同步執(zhí)行 的。
- fetch(...) 這個函數(shù)被調(diào)用時(shí),它 立即返回 一個 Promise 對象。在 try 塊看來,這個返回動作是成功的,沒有任何錯誤被“拋出”(throw)。
- 所以,try 塊順利執(zhí)行完畢,catch 自然不會被觸發(fā)。
- 真正的網(wǎng)絡(luò)錯誤發(fā)生在稍后的某個時(shí)間點(diǎn),當(dāng)這個錯誤發(fā)生時(shí),它改變了那個已經(jīng)返回的 Promise 對象的狀態(tài),將其置為 rejected。這個錯誤屬于 異步世界,而同步的 try...catch早已執(zhí)行完畢,鞭長莫及。
正確的姿勢:使用 async/await
那么,如何讓保安(try...catch)等到外賣送到(或出事)再下班呢?答案就是使用 async/await。
await 關(guān)鍵字有一個神奇的魔力:它會“暫停”當(dāng)前 async 函數(shù)的執(zhí)行,直到它等待的 Promise 有了結(jié)果(無論是成功 resolved 還是失敗 rejected)。
如果 Promise 失敗了,await 會像一個“信使”,把這個異步的錯誤“解包”并 重新在當(dāng)前同步上下文中拋出。這樣一來,try...catch 就能穩(wěn)穩(wěn)地接住它了。
讓我們來改造一下代碼:

看,這次 catch 完美地捕獲了錯誤!
async/await 的工作流程:
- 函數(shù)用 async 標(biāo)記,表示這是一個異步函數(shù)。
- await 守在 fetch(...) 前面,函數(shù)執(zhí)行到這里就“暫停”了,但不會阻塞整個程序。
- 它耐心等待 fetch 返回的 Promise 結(jié)果。
- 當(dāng) Promise 因?yàn)榫W(wǎng)絡(luò)問題而 rejected 時(shí),await 將這個 rejection 的原因(也就是那個 error 對象)作為一個同步錯誤 throw 出來。
- 這個被 throw 出來的錯誤,正好在 try 塊的作用域內(nèi),于是被 catch 成功捕獲。
別忘了還有 .catch() 方法
當(dāng)然,處理 Promise 錯誤并非只有 async/await 這一條路。在 async/await 出現(xiàn)之前,我們一直使用 Promise 自帶的 .catch() 方法鏈?zhǔn)秸{(diào)用來處理錯誤,這同樣非常有效。
fetch('https://non-existent-url.com/api')
.then(response => {
if (!response.ok) {
// 手動拋出一個錯誤,讓下面的 .catch() 捕獲
throw new Error('網(wǎng)絡(luò)響應(yīng)不佳');
}
return response.json();
})
.then(data => {
console.log('請求成功:', data);
})
.catch(error => {
// 任何在 .then() 鏈中發(fā)生的錯誤都會在這里被捕獲
console.log('在 .catch() 方法中抓到錯誤了!', error);
});這種方式的優(yōu)點(diǎn)是代碼結(jié)構(gòu)清晰,形成了一條“成功路徑” (.then) 和一條“失敗路徑” (.catch)。





























