譯者按: 使用 來捕獲所有的異常 原文: "Async Await Error Handling in JavaScript" 譯者: "Fundebug" 本文采用意譯,版權歸原作者所有 async/await 中的異常處理很讓人混亂。儘管有很多種方式來應對 "async 函數的異常" ,但是連經 ...
譯者按: 使用.catch()
來捕獲所有的異常
本文采用意譯,版權歸原作者所有
async/await 中的異常處理很讓人混亂。儘管有很多種方式來應對async 函數的異常,但是連經驗豐富的開發者有時候也會搞錯。
假設你有一個叫做run()
的非同步函數。在本文中,我會描述 3 種方式來處理run()
的異常情形: try/catch
, Go 語言風格, 函數調用的時候使用 catch()
(即run().catch()
)。 我會跟你解釋為什麼其實幾乎只需要catch()
就足夠。
try/catch
當你第一次使用async/await
, 你可能嘗試使用try/catch
將每一個 async 操作包圍起來。如果你await
一個被 reject 的 Promise,JavaScript 會拋出一個可以被捕獲的錯誤。
run();
async function run() {
try {
await Promise.reject(new Error("Oops!"));
} catch (error) {
error.message; // "Oops!"
}
}
try/catch
能夠捕獲非非同步的異常。
run();
async function run() {
const v = null;
try {
await Promise.resolve("foo");
v.thisWillThrow;
} catch (error) {
// "TypeError: Cannot read property 'thisWillThrow' of null"
error.message;
}
}
所以,只需要將所有的代碼邏輯都用 try/catch
包圍起來就可以搞定?也不完全正確。下麵的代碼會拋出unhandled promise rejection. await
將一個被拒絕的 promise 轉換為可捕獲的錯誤,但是 return
不行。
run();
async function run() {
try {
// 註意這裡是return,不是await
return Promise.reject(new Error("Oops!"));
} catch (error) {
// 代碼不會執行到這裡
}
}
也不可能使用 return await
來繞開。
還有一個缺點就是使用了try/catch
之後,就很難用.
的語法來進行 Promise 鏈式組合了。
使用 Go 的語法
另一個常見的方式就是使用then()
將一個本來需要用catch()
來捕獲並處理的 Promise 轉換為普通的 Promise。然後像 Go 語言中一樣,使用if(err)
來處理異常。
run();
async function throwAnError() {
throw new Error("Oops!");
}
async function noError() {
return 42;
}
async function run() {
// The `.then(() => null, err => err)` 來匹配正常/異常的情況。如果正常情況,返回`null`;如果異常,返回`err`
let err = await throwAnError().then(() => null, err => err);
if (err != null) {
err.message; // 'Oops'
}
err = await noError().then(() => null, err => err);
err; // null
}
如果你真的想要同時返回 error 和正確的值,你可以完全假裝在用 Go 語言。
run();
async function throwAnError() {
throw new Error("Oops!");
}
async function noError() {
return 42;
}
async function run() {
// The `.then(v => [null, v], err => [err, null])` pattern
// 你可以使用數組解構來匹配err和返回值
let [err, res] = await throwAnError().then(
v => [null, v],
err => [err, null]
);
if (err != null) {
err.message; // 'Oops'
}
err = await noError().then(v => [null, v], err => [err, null]);
err; // null
res; // 42
}
使用 Go 語言風格的錯誤處理並不能擺脫return
無法捕獲的情況。而且還讓整個代碼更加的複雜,如果忘記if(err != null)
,就會出問題。
總的來說,有兩大缺點:
- 代碼極度重覆,每一個地方都少不了
if (err != null)
,真的很累,而且容易漏掉; run()
函數中的非非同步的錯誤也無法處理;
總的來說,它並沒有比try/catch
好多少。
在函數調用的時候使用catch()
try/catch
和 Go 語言風格的異常處理都有各自的使用場景,但是處理所有異常最好的方法是在run()
函數的後面使用catch()
,像這樣:run().catch()
。換句話說,用一個catch()
來處理run
函數中的所有錯誤,而不是針對run
裡面的每一種情況都去寫代碼做相應的處理。
run()
.catch(function handleError(err) {
err.message; // Oops!
})
// 在handleError中處理所有的異常
// 如果handleError出錯,則退出。
.catch(err => {
process.nextTick(() => {
throw err;
});
});
async function run() {
await Promise.reject(new Error("Oops!"));
}
記住,async 函數總是返回 promise。只要函數中有異常,Promise 會 reject。而且,如果一個 async 函數返回的是一個 reject 的 Promise,那麼這個 Promise 依然會繼續被 reject。
run()
.catch(function handleError(err) {
err.message; // Oops!
})
.catch(err => {
process.nextTick(() => {
throw err;
});
});
async function run() {
// 註意:這裡使用了return,而不是await
return Promise.reject(new Error("Oops!"));
}
為什麼使用run().catch()
而不是將整個run()
函數用try/catch
包起來呢?我們首先來考慮一個情況:如果try/catch
的catch
部分有異常,我們應該如何處理呢?只有一個方法:在catch
裡面接著使用try/catch
。所以,run().catch()
的模式使得異常處理變得非常簡潔。
總結
我們最好是全局的有一個 errorHandler 來處理那些沒有考慮到的異常,比如使用run().catch(handleError)
,而不是在run()
函數裡面所有可能出錯的地方加上try/catch
。
關於Fundebug
Fundebug專註於JavaScript、微信小程式、微信小游戲、支付寶小程式、React Native、Node.js和Java線上應用實時BUG監控。 自從2016年雙十一正式上線,Fundebug累計處理了10億+錯誤事件,付費客戶有陽光保險、核桃編程、荔枝FM、掌門1對1、微脈、青團社等眾多品牌企業。歡迎大家免費試用!
版權聲明
轉載時請註明作者 Fundebug以及本文地址:https://blog.fundebug.com/2019/07/24/async-await-error-handling-in-js/