最近几篇文章都跟微信小程序开发有关,所以有人就问:“小程序不懂啊,能不能写点别的?”。其实不用太在意“小程序”这件事情,因为“小程序”在文章中只是一个开发场景,我们实际解决的问题并非只在小程序中才会遇到,而解决问题的手段完全与小程序无关!
在 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)
});
});
};
}也不卖关子了,这里有几个方案可选:
{ promise, originalResult} 或 [promise, originalResult];awx.request(params, outBox = {}),在处理时为 outBox 赋值:outBox.originalResult;promise.originalResult = ...。从使用者的角度来考虑,多数时候是不需要原返回值的,这时候是肯定是希望 await awx.request(),而不是先解构再 await(或 then()),所以,第 1 种方法不可选。
第 2 种方法可行,不需要原返回值的时候,直接使用即可。但是需要原返回值的时候,稍嫌麻烦,需要先产生一个容器对象传入。
第 3 种方法使用起来应该是最“无感”的。无论如何,原值随 Promise 对象带出来了,用或是不用,请便!
现在我们来实现第 3 种方法,改造 wxPromisify():
一开始想得很简单,原来直接 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 对象带出来和通过一个专门的“容器”对象带出来,本质是一样的,所以就不多说了。
推荐教程:《微信小程序》
以上就是改进异步封装:处理带返回值的异步调用 - 边城客栈的详细内容,更多请关注php中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号