改進非同步封裝:處理帶返回值的非同步呼叫 - 邊城客棧

hzc
發布: 2020-06-23 10:07:23
轉載
2957 人瀏覽過
最近幾篇文章都跟微信小程式開發有關,所以有人就問:「小程式不懂啊,能不能寫點別的?」。

其實不用太在意「小程式」這件事情,因為「小程式」在文章中只是一個開發場景,我們實際解決的問題並非只在小程式中才會遇到,而解決問題的手段完全與小程式無關!

1. 問題

在Proxy 封裝微信小程式的非同步呼叫中留下了一個問題:

wx.request()  這種原本就有回傳值的情況,該如何封裝呢?

如果需要在請求的過程中取消請求,就會用到wx.request() 的回傳值:

const requestTask = wx.request(...);
if (...) {
    // 因为某些原因需要取消这次请求
    requestTask.abort();
}
登入後複製

封裝過後的awx. request() 會回傳一個Promise 對象,跟wx.request() 原來的回傳值毫無關係。如果想要能夠取消請求,就必須將 wx.request() 原來的回傳值帶出來,該怎麼辦?

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)
            });
        });
    };
}
登入後複製

2. 可選方案

也不賣關子了,這裡有幾個方案可選:

    ##傳回物件或陣列,解構後使用。例如返回
  1. { promise, originalResult}[promise, originalResult];
  2. 透過一個「容器」參數將傳回值帶出來,例如
  3. awx. request(params, outBox = {}),在處理時為outBox 賦值:outBox.originalResult;
  4. JS 是動態類型,可以直接修改Promise 對象,為其附加屬性:
  5. promise.originalResult = ...
從使用者的角度來考慮,多數時候是不需要原回傳值的,這時候是肯定是希望

await awx.request(),而不是先解構再await(或then()),所以,第1 種方法不可選。

第 2 種方法可行,不需要原回傳值的時候,直接使用即可。但是需要原回傳值的時候,稍微麻煩,就需要先產生一個容器物件傳入。

第 3 種方法使用起來應該是最「無感」的。無論如何,原值隨 Promise 物件帶出來了,用或是不用,請便!

現在我們來實作第3 種方法,改造

wxPromisify()

3. 失敗的嘗試

一開始想得很簡單,原來直接

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

4. 成功的嘗試

既然已經知道問題所在,我們接著分析。

建構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 把它帶出來就好。

搞定!

5. 搞笑卻又該嚴肅對待的事情

本來應該結束了,但我猜一定會有人這麼做(因為我在其他場景下見過):

注意:下面這個是錯誤範例!
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 物件

6. 再囉嗦

這次帶出原回傳值是以wx.request() 為例,其傳回值的主要用途是提供.abort() 方法用於取消請求。這個應用程式場景其實和 Axios 處理「取消請求 (Cancellation)」類似,所以不妨參考 Axios 透過 cancelToken 實現的方法。 cancelToken 的實質就是前面提到的第 2 種方法 —— 傳入「容器」物件把需要的東西帶出來。透過 Promise 物件帶出來和透過一個專門的「容器」物件帶出來,本質是一樣的,所以就不多說了。

推薦教學:《微信小程式

以上是改進非同步封裝:處理帶返回值的非同步呼叫 - 邊城客棧的詳細內容。更多資訊請關注PHP中文網其他相關文章!

相關標籤:
來源:segmentfault.com
本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn
作者最新文章
最新問題
熱門教學
更多>
最新下載
更多>
網站特效
網站源碼
網站素材
前端模板