最近幾篇文章都跟微信小程序開發有關,因此有人就問:「小程序不懂啊,能不能寫點別的?」。其實不用太在乎「小程序」這件事情,由於「小程序」在文章中只是一個開發場景,咱們實際解決的問題並不是只在小程序中才會遇到,而解決問題的手段徹底與小程序無關!ios
在 Proxy 封裝微信小程序的異步調用 中留下了一個問題:git
像
wx.request()
這種本來就有返回值的狀況,該如何封裝呢?
若是須要在請求的過程當中取消請求,就會用到 wx.request()
的返回值:github
const requestTask = wx.request(...); if (...) { // 由於某些緣由須要取消此次請求 requestTask.abort(); }
封裝事後的 awx.request()
會返回一個 Promise 對象,跟 wx.request()
原來的返回值毫無關係。若是想要可以取消請求,就必須將 wx.request()
原來的返回值帶出來,應該怎麼辦?axios
function wxPromisify(fn) { return async function (args) { return new Promise((resolve, reject) => { const originalResult = fn({ // ^^^^^^^^^^^^^^^^^^^^^^^ // 怎麼把 originalResult 帶出去? ...(args || {}), success: res => resolve(res), fail: err => reject(err) }); }); }; }
也不賣關子了,這裏有幾個方案可選:小程序
{ promise, originalResult}
或 [promise, originalResult]
;awx.request(params, outBox = {})
,在處理時爲 outBox
賦值:outBox.originalResult
;promise.originalResult = ...
。從使用者的角度來考慮,多數時候是不須要原返回值的,這時候是確定是但願 await awx.request()
,而不是先解構再 await
(或 then()
),因此,第 1 種方法不可選。segmentfault
第 2 種方法可行,不須要原返回值的時候,直接使用便可。可是須要原返回值的時候,稍嫌麻煩,須要先產生一個容器對象傳入。微信小程序
第 3 種方法使用起來應該是最「無感」的。不管如何,原值隨 Promise 對象帶出來了,用或是不用,請便!數組
如今咱們來實現第 3 種方法,改造 wxPromisify()
:promise
一開始想得很簡單,原來直接 return new Promise()
,如今加個臨時變量應該就能夠吧:微信
function wxPromisify(fn) { return async function (args) { const promise = new Promise((resolve, reject) => { // ^^^^^^^^^^^^^^^^ promise.originalResult = fn({ // ^^^^^^^^^^^^^^^^^^^^^^^^^ ...(args || {}), success: res => resolve(res), fail: err => reject(err) }); }); return promise; // ^^^^^^^^^^^^^^^ }; }
而後獲得一個錯誤:
TypeError: Cannot set property 'originalResult' of undefined
這個錯很好理解,也很容易改……不過確實也很容易犯!
原本是認爲 promise
是個局部變量,能夠直接訪問,因此在其子做用域中使用是沒問題。可是這裏忽略了這個子做用域是在構造函數中。來大概分析一下:
new Promise()
須要一個函數(假設叫 factory
)做爲參數,可是這個 factory
執行的時機是什麼?注意到 new Promise()
產生 Promise 實例以後,咱們再沒有主動調用這個實例的任何方法,因此能夠判定,factory
是在構造的過程當中執行的。換句話說,這時候 Promise 實例還沒產生呢,promise
引用的是 undefined
。
既然已經知道問題所在,咱們接着分析。
構造 Promise 實例的過程當中調用了 factory
,而 factory
的在函數體中直接執行了 fn
,能夠當即拿到 fn
的返回值,因此這個 Promise 實例構造完成以後,是能夠拿到原返回值的。
如今來修改一下代碼:
function wxPromisify(fn) { return async function (args) { let originalResult; // ^^^^^^^^^^^^^^^^^^^ const promise = new Promise((resolve, reject) => { originalResult = fn({ // ^^^^^^^^^^^^^^ ...(args || {}), success: res => resolve(res), fail: err => reject(err) }); }); promise.originalResult = originalResult; // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ return promise; }; }
咱們須要在 new Promise()
以後對 promise.originalResult
賦值,而這個「值」產生於 new Promise()
的過程當中,那麼再加個局部變量 originalResult
把它帶出來就好。
搞定!
原本應該結束了,但我猜必定會有人這麼幹(由於我在其餘場景下見過):
注意:下面這個是錯誤示例!
function wxPromisify(fn) { return async function (args) { let promise = new Promise(); // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ promise = new Promise((resolve, reject) => { // ^^^^^^^^^^ promise.originalResult = fn({ ... }); // ^^^^^^^^^^^^^^^^^^^^^^ }); return promise; }; }
這樣作不會產生前面提到的 TypeError
,可是外面拿到的 Promise 對象卻並不攜帶 originalResult
。具體緣由跟上面失敗的那次嘗試同樣,因此再也不詳述,只提醒一下:這裏產生了兩個 Promise 對象。
此次帶出原返回值是以 wx.request()
爲例,其返回值的主要用途是提供 .abort()
方法用於取消請求。這個應用場景其實和 Axios 處理「取消請求 (Cancellation)」相似,因此不妨參考 Axios 經過 cancelToken
實現的方法。cancelToken
的實質就是前面提到的第 2 種方法 —— 傳入「容器」對象把須要的東西帶出來。經過 Promise 對象帶出來和經過一個專門的「容器」對象帶出來,本質是同樣的,因此就很少說了。
請關注公衆號邊城客棧⇗
看完了先別走,點個贊 ⇓ 啊,讚揚 ⇘ 就更好啦!