代码审查,异步调用的常见问题剖析

原创
2020/04/12 23:20
阅读数 48

先来看一段代码,就是一小段而已:

 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.loginsuccess 会立即结束,触发 complete。而这时候 wx.request 可能还在等待服务器响应。

那么是不是应该把 wx.hideLoading() 放到内部逻辑中去?理论上来说,是的!

但实际情况是,内部逻辑分支较多,深次较深,既有同步逻辑,也有异步逻辑……考虑应该放在哪些地方,需要非常的谨慎。实际上,案例中的代码就已经在内部逻辑中放了一些 wx.hideLoading(),只不过

  1. 覆盖不全;

  2. 因为最外层的 hideLoading()  提前执行,失效了。

  3. 违反了规范性约束:成对逻辑应该尽量避免一对多的情况

解释一下第 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 || "登录时发生错误");
        }
    }
 }




喜欢此文,点个在看 ⇘

支持作者,赏个咖啡豆 ⇓


本文分享自微信公众号 - 边城客栈(fancyidea-full)。
如有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。

展开阅读全文
jwt
加载中
点击引领话题📣 发布并加入讨论🔥
打赏
0 评论
0 收藏
0
分享
返回顶部
顶部