await-to-js 原始碼分析,體驗一把捕獲異常的優雅

語言: CN / TW / HK

theme: cyanosis highlight: atelier-dune-light


前言

await-to-js 的原始碼較少,程式碼較少的情況,可以嘗試採用 3W 方法進行原始碼分析,幫助快速瞭解它的實際應用場景。

閱讀本文,會有以下收穫:

  • 瞭解 await-to-js 庫能解決什麼問題。
  • 分析 await-to-js 庫是如何解決問題。
  • 摸索 await-to-js 庫的實際使用場景。

Why:await-to-js 誕生的契機

開啟它的 github 地址,有很簡單的一句介紹:

Async await wrapper for easy error handling

這句話的意思是:

Async await 包裝器,便於錯誤處理

等等,asyncawait 怎麼捕獲異常來著。

常規異常捕獲

一般情況下,await 命令後面是一個 Promise 物件,返回該物件的結果。

如果我們希望捕獲 Promise 中的錯誤, 這個時候需要將 await 放在try...catch結構裡面。

同時,這樣做,無論前面的 await 是否成功,都不會影響後續的功能。

功能設計

該功能設計源自原始碼的 examples 檔案中提供的案例,我將它改成了使用 await-to-js 前的寫法。

  • UserModel:提供了 findById 方法,是一個 Promise,它主要做了兩件事,如果 userId 存在,則返回 userObjet 物件,如果 userId 不存在,則用 reject 丟擲錯誤。
  • asyncTask: 兩個引數,userId 和結果返回函式。使用async / await 處理非同步操作 UserModel,將 await 放在 try...catch 結構裡面。如果 UserModel 正常,則判斷是否查到了 userId,如果 UserModel 阻塞,也不影響後面程式碼的執行。
  • 呼叫 asyncTask 的時候,第一個引數傳 null 。

``` const UserModel = { findById: userId => { return new Promise((resolve, reject) => { if (userId) { const userObjet = { id: userId, notificationsEnabled: true, };

    return resolve(userObjet);
  }

  reject('Data is missing');
});

}, };

/* * 非同步任務 / async function asyncTask(userId, cb) { try { const user = await UserModel.findById(userId); if (!(user && user.id)) return cb('No user found'); } catch () {; } return cb('前面的 await 可能失敗了'); }

asyncTask(null, (err, newTask) => { console.log('fail'); console.log(err); console.log(newTask); }); ```

執行結果

因為 userId 的值為 null,所以 UserModel.findById 丟擲了異常,而我在 catch 中沒有做任何處理,所以後面的程式碼正常執行,將最後的程式碼處理結果拋給了 cb 函式。

所以,最終列印 cb 裡的結果:

捕獲異常

如果需要進行異常的捕獲,在 catch 中將錯誤返回即可。

async function asyncTask(userId, cb) { try { const user = await UserModel.findById(userId); if (!(user && user.id)) return cb('No user found'); } catch (e) { return cb(e); } return cb('前面的 await 可能失敗了'); }

執行結果

此時返回的報錯內容為: Data is missing

契機

上面的例子中,雖然程式碼執行正常,也可以進行異常的捕獲。

但是有個問題,如果想捕獲不同場景下的異常,需要將每個場景都放到 try...catch 結構中,這樣程式碼會顯得有些冗餘。

能否,將 try...catch 結構封裝起來,直接返回結果?

這不,await-to-js 就誕生了麼。

What:如何優雅捕獲異常

開頭說原始碼內容少,並不是說說看,它是真的少。

核心原始碼

原始碼用的 TypeScript,因為程式碼量不多,所以沒有用過 TypeScript 的開發者,也可以很順暢的閱讀。

``` /* * @param { Promise } promise * @param { Object } errorExt - Additional Information you can pass to the err object * @return { Promise } / export function to ( promise: Promise, errorExt?: object ): Promise<[U, undefined] | [null, T]> { return promise .then<[null, T]>((data: T) => [null, data]) .catch<[U, undefined]>((err: U) => { if (errorExt) { const parsedError = Object.assign({}, err, errorExt); return [parsedError, undefined]; }

  return [err, undefined];
});

}

export default to; ```

不過,想看用 JavaScript 寫的也不難,將專案 build 一下,就能得到。順便在上面加了些註釋:

``` /* * @param { Promise } promise * @param { Object } errorExt - Additional Information you can pass to the err object * @return { Promise } / function to(promise, errorExt) { return promise .then(function (data) { // 返回陣列值:第一個元素是錯誤資訊(只有報錯時有值),第二個元素是資料資訊(只有成功時有值) return [null, data]; }).catch(function (err) { // =>true: 如果添加了額外的錯誤描述,則和錯誤描述合到一個空物件上 if (errorExt) { var parsedError = Object.assign({}, err, errorExt); return [parsedError, undefined]; } return [err, undefined]; }); }

export { to }; export default to; ```

這裡需要注意下面幾點:

  • errorExtObject 型別。
  • Object.assign 在進行拷貝的時候,會把非 Object 型別轉成 Object 型別。

``` const err1 = 'time'; const err2 = 's';

const task = Object.assign({}, err1, err2); console.log(task); // { '0': 's', '1': 'i', '2': 'm', '3': 'e' } ```

疑問

關於 errorExt 我是有疑問的,它的結果到底怎麼回顯?

原始碼中,promise 中的 reject 丟擲的都是字串。上面提到了,Object.assign 會將字串轉成 Object 型別,得到的結果不是很直觀。

來個例子看看執行結果:

``` async function asyncTask(userId, cb) { let err, user, savedTask, notification; [err, user] = await to(UserModel.findById(userId), { errorExt: ' ext error' }); if (err) return cb(err);

cb(null, savedTask); } asyncTask(null, (err, newTask) => { console.log('fail'); console.log(err); console.log(newTask); }); ```

執行結果

這個結果並不直觀

想要更直觀的內容,只能將結果進行一次特殊處理,比如物件迴圈,只展示 value 值

if (err) { let errList = Object.values(err); let errStr = errList.join(''); return cb(errStr); }

執行結果

這次列印的內容直觀多了。

關於疑問的部分,僅個人想法,歡迎留言討論

示例演示

還是上面的例子,這次使用 await-to-js 捕獲異常。只需要一行程式碼,便可輕鬆捕獲異常。

``` import to from '../../dist';

async function asyncTask(userId, cb) { let err, user, savedTask, notification; [err, user] = await to(UserModel.findById(userId)); // 直接捕獲異常 if (err) return cb(err); }

asyncTask(null, (err, newTask) => { console.log('fail'); console.log(err); console.log(newTask); }); ```

執行結果

How:使用場景小結

不僅可以直接捕獲異常,還可以根據資料的值進行特殊的邏輯處理,幫助完成一些複雜邏輯。

``` async function asyncTask(userId, cb) { let err, user, savedTask, notification; [err, user] = await to(UserModel.findById(userId)); if (!(user && user.id)) return cb('No user found');

cb(null, savedTask); } ```

執行結果

在上面的程式碼中,根據得到的 user 的值,進行判斷,在實際開發中的實用性更強一些。

總結

await-to-js 原始碼讀完,個人感覺實際用起來,程式碼會寫的比較優雅。而且它的原始碼內容不多,簡單易讀。

簡單總結一下本文收穫:

1、使用 await-to-js,可以幫助改善非同步捕獲異常的冗餘寫法。

2、原始碼的程式碼量不多,可以作為工具類放到專案中使用。

3、在日常開發中,可以留意一下類似的功能改造,減少重複寫法。

以上就是本次分享的內容。如果覺得有幫助,歡迎留言討論、點贊 、收藏,持續產出技術分享。


我是 葉一一,非職業「傳道授業解惑」的技術博主。「趣學前端」、「CSS暢想」系列作者。

華夏美食、國漫、古風重度愛好者,刑偵、無限流小說初級玩家。

歡迎技術或非技術問題的討論。

本文正在參加「金石計劃」