先来看一段代码,就是一小段而已:
export function loginWithWx() {
wx.showLoading({ title: "登录中..." });
wx.login({
success: res => {
wx.request({
url: `${apiRoot}wx/${res.code}`,
method: "get",
success: res => {
const { data } = res;
const jwt = app.globalData.jwt = data?.jwt;
if (jwt) {
wx.reLaunch({ url: "../index/index" });
wx.hideLoading();
}
else {
showMessage(data.message || "登录时发生错误");
wx.hideLoading();
}
},
fail: res => {
showMessage("请求超时,请稍后重试");
}
});
wx.hideLoading();
},
fail: res => {
console.log(res);
}
});
wx.hideLoading();
}
这段代码乍一看,似乎没毛病。但是稍微思考一下,就能发现问题了。
首先,最直观的问题:缩进太深。缩进最深的地方是 24 个空格,也就是 6 层。一般我们认为 3 层以内的缩进比较容易阅读,超过 3 层应该考虑使用“Extract Method”方法进行重构。
接下来,看外层逻辑:
wx.showLoading()
wx.login()
wx.hideLoading()
这是期望的执行顺序。
注意到 wx.login
是一个异步过程,所以实际上 hideLoading()
并不会等登录过程结束就关闭了加载提示。所以第 2 个问题是忽略了异步执行的顺序。
马上可以想到使用 wx.login()
的 complete
参数来解决:
wx.showLoading();
wx.login({
complete: () => wx.hideLoading()
});
不过马上就引出了下一个问题:complete 还是太快!
为什么?我们再把内部的逻辑结构清理出来:
wx.login({
success: () => {
wx.request({
success: () => { },
fail: () => { }
})
},
fail: () => { }
})
注意到 wx.request
仍然是一个异步过程,所以 wx.login
的 success
会立即结束,触发 complete
。而这时候 wx.request
可能还在等待服务器响应。
那么是不是应该把 wx.hideLoading()
放到内部逻辑中去?理论上来说,是的!
但实际情况是,内部逻辑分支较多,深次较深,既有同步逻辑,也有异步逻辑……考虑应该放在哪些地方,需要非常的谨慎。实际上,案例中的代码就已经在内部逻辑中放了一些 wx.hideLoading()
,只不过
- 覆盖不全;
- 因为最外层的
hideLoading()
提前执行,失效了。 - 违反了规范性约束:成对逻辑应该尽量避免一对多的情况。
解释一个第 3 点,就是说:一个 showLoading()
最好只对应一个 hideLoading()
。考虑到逻辑的复杂性,这不是强制约束规则,但应该尽量去避免。
处理的办法是,重构,将内部逻辑拆分出来;然后,将完成事件处理逻辑作为一个参数,一层层的往里传:
function appLogin(params, complete) {
// ^^^^^^^^
wx.request({
...params,
complete: complete
// ^^^^^^^^
});
}
function wxLogin(params, complete) {
// ^^^^^^^^
wx.login({
...params,
success: () => appLogin({}, complete),
// ^^^^^^^^
fail: () => complete()
// ^^^^^^^^^^
// complete: complete // ✗
// 注意:由于 success 和 fail 里存在异步处理,不能直接使用 complete 事件。
// 原因在前面已经说了。
});
}
wx.showLoading();
wxLogin({}, () => wx.hideLoading());
// ^^^^^^^^^^^^^^^^^^^^^^ 传入的 complete
显然在当前的技术环境中,这并不是最优方案,还可以继续优化——反正都要封装,干脆封装成 Promise。然后通过 await
调用转换成同步语法,处理起来会轻松得多。封装的具体过程在前两篇文章中有详细的讲解,这里就不赘述了。总之,我们封装了 wx
的异步版本 awx
,在这里用就好:
export async function asyncLoginWithWx() {
wx.showLoading({ title: "登录中..." });
try {
return await internalProcess();
} catch (err) {
showMessage("请求超时,请稍后重试");
} finally {
wx.showLoading();
}
// 把内部逻辑用个局部函数封装起来,
// 主要是为了让 try ... catch ... 看起来清晰一些
async function internalProcess() {
const { code } = await awx.login();
const { data } = awx.request({
url: `${apiRoot}wx/${code}`,
method: "get",
});
const jwt = app.globalData.jwt = data?.jwt;
if (jwt) {
wx.reLaunch({ url: "../index/index" });
} else {
showMessage(data.message || "登录时发生错误");
}
}
}
请关注公众号边城客栈⇗
看完了先别走,点个赞啊 ⇓,赞赏 ⇘ 也行!