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

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

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()。考虑到逻辑的复杂性,这不是强制约束规则,但应该尽量去避免。

处理的办法是,重构,将内部逻辑拆分出来;然后,将完成事件处理逻辑作为一个参数,一层层的往里传:

显然在当前的技术环境中,这并不是最优方案,还可以继续优化——反正都要封装,干脆封装成 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 || "登录时发生错误");
        }
    }
}



喜欢此文,点个赞 ?

支持作者,赏个咖啡豆 ?

原文地址:https://blog.51cto.com/jamesfancy/2486842

时间: 2024-08-30 10:16:49

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

利用委托实现异步调用

同步调用示例(委托是一个类型安全的,面向对象的指针) using System; using System.Threading; namespace Demo { public delegate int Operate(int x, int y); public class DelegateAsync { static int Add(int a, int b) { Console.WriteLine ("Add() invoked on thread:{0}\n", Thread.C

[C#] 走进异步编程的世界 - 剖析异步方法(下)

走进异步编程的世界 - 剖析异步方法(下) 序 感谢大家的支持,这是昨天发布<走进异步编程的世界 - 剖析异步方法(上)>的补充篇. 目录 异常处理 在调用方法中同步等待任务 在异步方法中异步等待任务 使用 Task.Delay() 暂停操作 一.异常处理 await 表达式也可以使用 try...catch...finally 结构. 1 internal class Program 2 { 3 private static void Main(string[] args) 4 { 5 va

(转)spring boot注解 [email&#160;protected] 异步调用

原文:http://www.cnblogs.com/azhqiang/p/5609615.html EnableAsync注解的意思是可以异步执行,就是开启多线程的意思.可以标注在方法.类上. 1 @Component 2 public class Task { 3 4 @Async 5 public void doTaskOne() throws Exception { 6 // 同上内容,省略 7 } 8 9 @Async 10 public void doTaskTwo() throws

CXF简单示例(二)之异步调用

在实际使用中,客户端在调用服务时,并不能及时得到响应,比如调用的服务本身是一个耗时费事的活,服务器破死命的跑,也只能在一分钟后才能够返回结果,这时候如果是同步的情况,那么客户端就必须在这里到等上一分钟,啥事不干.这肯定是不能容忍的,光吃饭不干活,再有这么好的事,请告诉我! OK,所以我们需要异步调用. 在这里,先介绍CXF提供的一个工具:WSDL2Java. 我们可以方便的利用它来生成相应的客户端接口,配置什么的脏活累活它都给我们干了,而我们只需要这样直接用就行了. 1. 获得服务的WSDL路径

spring-boot实战【10】【转】:Spring Boot中使用@Async实现异步调用

什么是"异步调用"? "异步调用"对应的是"同步调用",同步调用指程序按照定义顺序依次执行,每一行程序都必须等待上一行程序执行完成之后才能执行:异步调用指程序在顺序执行时,不等待异步调用的语句返回结果就执行后面的程序. 同步调用 下面通过一个简单示例来直观的理解什么是同步调用: 定义Task类,创建三个处理函数分别模拟三个执行任务的操作,操作消耗时间随机取(10秒内) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16

简述自己用过的几种异步调用方式

直接上代码 1.BeginInvoke和EndInvoke方式 private static void BeginInvoke1() { Func<int,string> fun = Todo; for (int i = 0; i < 5; i++) { //fun.BeginInvoke(i,TodoCallBack, fun); /* 异步调用委托BeginInvoke * handler.EndInvoke(x)为执行委托的结果 */ fun.BeginInvoke(i, x =&

C#委托异步调用

废话不多说,直接上代码(PS:我就喜欢简单.直接.粗暴) using System;using System.Collections.Generic;using System.Linq;using System.Runtime.Remoting.Messaging;using System.Text;using System.Threading;using System.Threading.Tasks; namespace 异步调用委托{    class Program    {       

同步调用和异步调用同时存在导致的混乱

其实在Promise之外也存在这个问题,这里我们以一般的使用情况来考虑此问题.这个问题的本质是接收回调函数的函数,会根据具体的执行情况,可以选择是以同步还是异步的方式对回调函数进行调用.下面我们以 onReady(fn) 为例进行说明,这个函数会接收一个回调函数进行处理. mixed-onready.js function onReady(fn) { var readyState = document.readyState; if (readyState == 'interactive' ||

WCF的异步调用

1.服务契约 namespace Contracts { [ServiceContract] public interface ICalculator { [OperationContract] double Add(double x,double y); } } 2.服务实现 namespace Services { public class CalculatorService:ICalculator { public double Add(double x, double y) { retu