大家好,我是若川。持續組織了5個月源碼共讀活動,感興趣的可以點此加我微信 ruochuan12?參與,每周大家一起學習200行左右的源碼,共同進步。同時極力推薦訂閱我寫的《學習源碼整體架構系列》?包含20余篇源碼文章。
這是源碼共讀活動第21期 await-to-js,優雅的處理 async await 的 try-catch,讀者掘金@愛嘿嘿的小黑 的投稿。
1前言
學而不思則罔
最近有在讀一些比較優秀的npm包的代碼,起因是感覺自己現在寫的代碼還是不夠規范,不夠簡潔。
可是我又不知道到底什么樣的代碼才算是比較好的代碼,在進行一番思考過后我認為還是要站在巨人的肩膀上。
通過閱讀優秀的源碼并從中學習如何寫出讓人覺得賞心悅目的代碼最后再寫文進行章總結對整個學習的過程進行一個梳理同時分享給其他人。
為什么要在開頭寫這么多呢?因為我需要為自己堅持下去找一個理由。這樣我才能乘風破浪,一往無前。
話不多說,開始總結。
2JS異步編程進化之路
回調地獄階段
在正式介紹await-to-js這個庫之前,讓我們先簡單的回顧一下有關于在JavaScript這門語言中,異步編程的進化之路。在Promise沒出現之前,異步編程一直是困擾著前端工程師的一個大難題,當時的前輩可能會經常看到下面這種代碼。
function?AsyncTask()?{asyncFuncA(function(err,?resultA){if(err)?return?cb(err);asyncFuncB(function(err,?resultB){if(err)?return?cb(err);asyncFuncC(function(err,?resultC){if(err)?return?cb(err);//?And?so?it?goes....});});});
}
這種同時在縱向和橫向延伸的回調中嵌套著回調的代碼又被稱為回調地獄。可見這玩意讓人多么惡心,具體來說有以下這幾個缺點
難以維護(看都不想看,還維護個**)
難以捕捉到錯誤(一個一個找?) 總而言之,這個問題在當時是很需要被解決的,所以在ES6中,出現了Promise。
Promise階段
Promise是一種優雅的異步編程解決方案。從語法上來將,它是一個對象, 代表著一個異步操作最終完成或失敗,從語意上來講,它是承諾,承諾過一段時間給你一個結果。
由于它的原型存在then,catch,finally會返回一個新的promise所以可以允許我們鏈式調用,解決了傳統的回調地獄的問題。
由于它本身存在all方法,所以可以支持多個并發請求,獲取并發請求中數據。
有了Promise后,上面的代碼可以被寫成下面這樣。
function?asyncTask(cb)?{asyncFuncA.then(AsyncFuncB).then(AsyncFuncC).then(AsyncFuncD).then(data?=>?cb(null,?data).catch(err?=>?cb(err));
}
相比較于上面的回調地獄,使用Promise可以幫助我們讓代碼只在縱向發展,并且提供了處理錯誤的回調。顯然優雅了很多。不過就算Promise已經這么優秀了,可是依然存在兩個每種不足的地方
不夠同步(代碼依然會縱向延伸)
不能給每一次異步操作都進行錯誤處理 這也就是為什么ES7中會出現async/await,號稱異步編程的最后解決方案的原因了。
async/await
async
函數是 Generator
函數的語法糖。使用 關鍵字 async
來表示,在函數內部使用 await
來表示異步。相較于 Generator
,async
函數的改進在于下面四點:
內置執行器。
Generator
函數的執行必須依靠執行器,而async
函數自帶執行器,調用方式跟普通函數的調用一樣更好的語義。
async
和await
相較于*
和yield
更加語義化更廣的適用性。
co
模塊約定,yield
命令后面只能是 Thunk 函數或 Promise對象。而async
函數的await
命令后面則可以是 Promise 或者 原始類型的值(Number,string,boolean,但這時等同于同步操作)返回值是 Promise。
async
函數返回值是 Promise 對象,比 Generator 函數返回的 Iterator 對象方便,可以直接使用then()
方法進行調用
此處總結參考自:理解async/await[1]
有了async/await,上面的代碼可以被改寫成下面這樣
function?async?asyncTask(cb)?{const?asyncFuncARes?=?await?asyncFuncA()const?asyncFuncBRes?=?await?asyncFuncB(asyncFuncARes)const?asyncFuncCRes?=?await?asyncFuncC(asyncFuncBRes)
}
同時我們可以對每一次異步操作進行錯誤處理
function?async?asyncTask(cb)?{try?{const?asyncFuncARes?=?await?asyncFuncA()}?catch(error)?{return?new?Error(error)}try?{const?asyncFuncBRes?=?await?asyncFuncB(asyncFuncARes)}?catch(error)?{return?new?Error(error)}try?{const?asyncFuncCRes?=?await?asyncFuncC(asyncFuncBRes)}?catch(error)?{return?new?Error(error)}
}
這樣一來上面Promise存在的兩個每種不足的地方是不是就被優化了呢?所以說async/await是JS中異步編寫的最后解決方案我個人覺得一點問題沒有,但是我不知道你看上面的代碼,每一次異步操作都要用try/catch進行錯誤處理是不是感覺不夠方便不夠智能呢?
3await-to-js-小而美的npm包
基本用法
作者是這樣介紹這個庫的
Async await wrapper for easy error handling without try-catch。
中文翻譯過來就是
無需 try-catch 即可輕松處理錯誤的異步等待包裝器。
這里做個簡單的對比,之前我們在異步操作中處理錯誤的方法是這樣的
function?async?asyncTask()?{try?{const?asyncFuncARes?=?await?asyncFuncA()}?catch(error)?{return?new?Error(error)}try?{const?asyncFuncBRes?=?await?asyncFuncB(asyncFuncARes)}?catch(error)?{return?new?Error(error)}try?{const?asyncFuncCRes?=?await?asyncFuncC(asyncFuncBRes)}?catch(error)?{return?new?Error(error)}
}
而用了await-to-js之后,我們可以這樣的處理錯誤
import?to?from?'./to.js';
function?async?asyncTask()?{const?[err,?asyncFuncARes]??=?await?to(asyncFuncA())if(err)?throw?new?(error);const?[err,?asyncFuncBRes]??=?await?tp(asyncFuncB(asyncFuncARes))if(err)?throw?new?(error);const?[err,?asyncFuncCRes]??=?await?to(asyncFuncC(asyncFuncBRes)if(err)?throw?new?(error);
}
是不是簡潔多了呢?
作者究竟用了什么黑魔法?
你可能不信,源碼只有僅僅15行。
源碼分析
export?function?to<T,?U?=?Error>?(promise:?Promise<T>,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;
上面這里是TS版的源碼,但是考慮到有些同學可能還沒接觸過TS,我著重分析一下下面這版JS版的源碼。
export?function?to(promise,?errorExt)?{return?promise.then((data)?=>?[null,?data]).catch((err)?=>?{if?(errorExt)?{const?parsedError?=?Object.assign({},?err,?errorExt);return?[parsedError,?undefined];}return?[err,?undefined];});
}
export?default?to;
這里我們先拋開errorExt這個自定義的錯誤文本,核心代碼是這樣的
export?function?to(promise)?{return?promise.then((data)?=>?[null,?data])?//?成功,返回[null,響應結果].catch((err)?=>?{return?[err,?undefined];?//?失敗,返回[錯誤信息,undefined]});
}
export?default?to;
可以看出,其代碼的邏輯用中文解釋是這樣的
無論成功還是失敗都返回一個數組,數組的第一項是和錯誤相關的,數組的第二項是和響結果相關的
成功的話數組第一項也就是錯誤信息為空,數組第二項也就是響應結果正常返回
失敗的話數組第一項也就是錯誤信息為錯誤信息,數組第二項也就是響應結果返回undefined
經過上面的分析我們可以認定,世界上沒有什么黑魔法,沒有你做不到,只有你想不到。
這里我們再來看函數to的第二個參數errorExt不難發現,這玩意其實就是拿來用戶自定義錯誤信息的,通過Object.assign
將正常返回的error和用戶自定義和合并到一個對象里面供用戶自己選擇。
4結語
源碼不可怕,可怕的是自己的面對未知的恐懼感。
敢于面對,敢于嘗試,才能更上一層樓。
繼續加油,少年。
關注我,vx:codebangbang,掘金:愛嘿嘿的小黑。
5參考資料
倉庫地址:https://github.com/scopsy/await-to-js
官方文章:How to write async await without try-catch blocks in Javascript[2]
參考資料
[1]
https://segmentfault.com/a/1190000010244279: https://link.juejin.cn?target=https%3A%2F%2Fsegmentfault.com%2Fa%2F1190000010244279
[2]How to write async await without try-catch blocks in Javascript: https://blog.grossman.io/how-to-write-async-await-without-try-catch-blocks-in-javascript/
·················?若川簡介?·················
你好,我是若川,畢業于江西高校。現在是一名前端開發“工程師”。寫有《學習源碼整體架構系列》20余篇,在知乎、掘金收獲超百萬閱讀。
從2014年起,每年都會寫一篇年度總結,已經寫了7篇,點擊查看年度總結。
同時,最近組織了源碼共讀活動,幫助3000+前端人學會看源碼。公眾號愿景:幫助5年內前端人走向前列。
識別上方二維碼加我微信、拉你進源碼共讀群
今日話題
略。分享、收藏、點贊、在看我的文章就是對我最大的支持~