原文連接:Catching without Awaitingjavascript
當執行一項須要等待一段時間才能返回的任務時,若是使用async/await
,就顯得比較麻煩了。若是async
方法尚未獲得返回值,咱們就捕獲不到其中的異常。java
在個人上一篇文章Learn to Throw Again中寫到,當使用async/await
時,如何同時捕獲到回調函數和throw
拋出的錯誤。在這篇文章中,咱們將討論如何在「後臺」中執行異步操做並捕獲異常(這裏使用雙引號,由於在單線程平臺上沒有真正的後臺操做)git
從回調函數的模式開始,思考下列代碼:github
function email(user, message, callback) {
if (!user) {
// 拋出異常
throw new Error('Invlid user');
}
if (!user.address) {
// 回調函數,可能拋出異常
return callback();
}
// 異步的
return mailer.send(user.address, message, callback);
}
複製代碼
上述代碼遵循典型的throw-on-bad-input / callback-asynchronous-errors
模式(一旦程序接收到錯誤的輸入,異步拋出異常),若是咱們想要發出一封郵件,咱們這樣調用:api
email(user, message, () => {});
複製代碼
對於非法的輸入,調用這個函數依舊可能拋出異常。可是,若是電子郵件在傳輸中產生錯誤,這個函數調用時會忽略異步拋出的錯誤。異步
咱們把它改成Promise
的版本:async
function email(user, message) {
if (!user) {
throw new Error('Invlid user');
}
if (!user.address) {
return Promise.resolve();
}
return mailer.send(user.address, message); // 函數返回一個Promise
}
複製代碼
這樣,對於非法的輸入,依舊能夠捕獲到異常。而對於mailer.send()
操做則會返回一個Promise
,咱們可以輕鬆地經過Promise.catch()
捕獲到異常:函數
email(user, message).catch(() => {});
複製代碼
無論是回調函數仍是Promise
,他們都是異步的,咱們的應用程序都不會由於email
發送而被阻塞。工具
對於async/await
的模式,若是在try...catch
語句中不使用await
關鍵字,那麼try...catch
子句不會真正工做。來看下面的async
版本:性能
function email(user, message) {
if (!user) {
throw new Error('Invlid user');
}
if (!user.address) {
return;
}
return mailer.send(user.address, message); // async function
}
複製代碼
若是咱們像這樣去調用:
try {
email(user, message);
} catch (err) {
Bounce.rethrow(err, 'system');
}
複製代碼
對於非法的輸入錯誤,仍然會正常地拋出異常,這沒問題。可是對於任何異步返回的異常,例如在mailer.send()
拋出的異常,則會被忽略掉。無論這種錯誤咱們想不想捕獲到,反正都是捕獲不到的。爲了修補這個bug
,則要使用await
關鍵字。可是問題來了,這將會致使整個「後臺操做」的阻塞。
有一種方案是混用async/await
和Promise
:
email(user, message).catch(() => {});
複製代碼
但這樣的問題在於,對於沒有address
的用戶,這個方法返回的返回值類型並非Promise
,於是其也不會有catch()
方法,所以程序會出現TypeError: Cannot read property ‘catch’ of undefined
這樣的錯誤。
你可能會嘗試直接把email()
函數聲明爲async
函數, 並使得它必定會返回一個Promise
,可是這並非一個很好的解決方案,由於async / await
其實也只是Promise
對象的一層包裝。若是不使用await
關鍵字,把一個函數聲明爲async
函數是徹底沒有必要的。由於async
函數老是要經過返回一個Promise
,經過next-tick
拿到結果,這樣會浪費Promise
包裝和next-tick
事件循環機制所形成的性能損耗。
此外,若是要在循環中使用async
函數,而且這個循環中執行了不少任務,可是其實不少任務並非真正意義上異步的,那就沒有必要使用async / await
,能夠參考hapi.js
中的checking if you really need to await下列代碼判斷是否真的須要使用await
,這樣或許能得到一些性能的提高:
var response = (typeof func === 'function' ? func(this) : this._invoke(func));
if (response && typeof response.then === 'function') { // Skip await if no reason to
response = await response;
}
複製代碼
判斷是否真的須要await
,其實就是判斷其是否存在then
方法,而且then
方法是一個函數。由於await
的做用其實就是取得一個異步操做的返回結果。
若是你可以保證email
方法老是返回一個Promise
,咱們能夠經過更改咱們的email()
函數來達到這一點,但這樣就顯得急功近利了!代碼顯得十分不簡潔,並且使用了很沒必要要的異步操做。在一個完整的async/await
函數調用棧中,不須要咱們手動構建Promise
。對於這個例子來講還好,更重要的是,咱們不可能總經過改變email()
方法來實現,由於這只是一個例子,在實際運用中,可能email()
方法是經過模塊引入的。
其中一種解決方案是經過await
關鍵字來調用async
函數。一般狀況下,在一個函數中使用阻塞操做,若是不等待這個函數執行完成,它不會拋出異常,可是咱們能夠經過try...catch
來包裹:
async function backgroundEmail(user, message) {
try {
await email(user, message);
} catch (err) {
Bounce.rethrow(err, 'system');
}
}
複製代碼
而後不經過await
調用backgroundEmail
:
backgroundEmail(user, message);
複製代碼
這樣咱們不但可以捕獲到應用程序的異常,還可以捕獲到異步拋出的異常。
爲了讓異常捕獲更加簡單,咱們使用Bounce模塊,它提供了一個background()
方法。
Bounce.background(() => email(user, message));
複製代碼
若是咱們使用Node.js
的AssertionError
原型,這樣就可以使得Bounce拋出輸入異常的錯誤了。
async/await
函數去除了一些同步函數(() => {}
)的功能,爲了達到和普通函數相同的效果,咱們不得不寫一些額外的代碼來實現。可是使用新的工具庫,能夠很簡便地突破這一限制。