### [改进异步封装:处理带返回值的异步调用 - 边城客栈](https://www.prosaas.cn/article/16598) **Published:** 2020-06-23T02:07:23 **Author:** 5201314 **Excerpt:** 最近几篇文章都跟微信小程序开发有关,所以有人就问:“小程序不懂啊,能不能写点别的?”。其实不用太在意“小程序”… > 最近几篇文章都跟微信小程序开发有关,所以有人就问:“小程序不懂啊,能不能写点别的?”。 > > 其实不用太在意“小程序”这件事情,因为“小程序”在文章中只是一个开发场景,我们实际解决的问题并非只在小程序中才会遇到,而解决问题的手段完全与小程序无关! ## 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 对象带出来和通过一个专门的“容器”对象带出来,本质是一样的,所以就不多说了。 推荐教程:《微信小程序》 以上就是改进异步封装:处理带返回值的异步调用 - 边城客栈的详细内容,更多请关注知企PROSAAS其它相关文章! **Categories:** 后端开发 ---