温馨提示×

温馨提示×

您好,登录后才能下订单哦!

密码登录×
登录注册×
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》

小程序中改进异步封装的问题有哪些

发布时间:2021-04-01 09:48:10 来源:亿速云 阅读:142 作者:小新 栏目:移动开发

小编给大家分享一下小程序中改进异步封装的问题有哪些,相信大部分人都还不怎么了解,因此分享这篇文章给大家参考一下,希望大家阅读完这篇文章后大有收获,下面让我们一起去了解一下吧!


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. 通过一个“容器”参数将返回值带出来,比如 awx.request(params, outBox = {}),在处理时为 outBox 赋值:outBox.originalResult

  3. JS 是动态类型,可以直接修改 Promise 对象,为其附加属性: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 对象带出来和通过一个专门的“容器”对象带出来,本质是一样的,所以就不多说了。

以上是“小程序中改进异步封装的问题有哪些”这篇文章的所有内容,感谢各位的阅读!相信大家都有了一定的了解,希望分享的内容对大家有所帮助,如果还想学习更多知识,欢迎关注亿速云行业资讯频道!

向AI问一下细节

免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

AI