在之前的文章中详细讲过ES6中的Promise点击查看原文),先简单回顾一下,其实在业务中使用Promise,目的是为了使方法同步执行,避免接口B需要使用接口A的回调数据时,接口A还没有请求完成,之前文章中的做法,比如有A、B、C、D四个方法,全部要同步顺序执行,A -> B -> C -> D,当时是这样做的:

/**
 * @description: Promise 队列同步执行方法
 * @param {Array} arr 队列数组 例如 [a,b,c] 注意:a,b,c仅仅为函数名,例如 test 而不是 test(),不能加括号
 */
const team = (arr) => {
    let sequence = Promise.resolve();
    for(let item of arr){
        sequence = sequence.then(item);
    }
    return sequence;
};

在全局中封装了一个team方法,将要同步顺序执行的方法使用数组方式传入,当第一个方法执行请求完成时,标记为Promise.resolve(),再使用循环执行第二个,以此类推,就是这个原理;

但就目前的情况看,这样完全能满足需求,但有一个缺点,team方法中,传入的方法只能是方法名,类似于testFunc这种,方法名后面也不能加括号,testFunc()这种就不行,因为team方法仅仅处理的是传入的方法,依次调用,判断什么时候执行完毕,再接着执行下一个,而我们很多时候,调用方法时要传参,这样一来造成了某些困扰,仍然不是最完美、最科学的方案。

而在ES2018中,Promise有一个语法糖,async,比起Promise的.then链式调用、team方法不能传参,简直不能太方便,使用async后,代码变得更简洁,代码的逻辑和数据流都变得更可控,并且每个同步执行的方法可以单独打断点调试,下面就开始详细讲解:

先说说使用async需要注意的点:

1.async说白了,就是Promise的一个扩展(超集),因为async的原理还是Promise实现的,用async的目的就是代码看起来更简单,逻辑更加清晰而已;

2.由于async的原理还是以Promise为基础,所以部分时候,async是要和Promise配合使用的,它并不能完全脱离Promise独立运行;

先来解释上面两点,凭什么说async是Promise的扩展,我们直接跑代码看看:

  /**
   * @description: 测试方法
   * @param {type} 
   */
  async testFunc() {

  },

给一个函数前面加上async,内部什么都不写,在页面显示时,直接打印该方法,看看具体是什么

console.log(this.testFunc());  //直接打印testFunc方法,看看具体是什么

可以看出,async就是一个Promise对象;

下面具体说async的使用方法和规则:

1.给函数加上 async,可以理解为将该函数包装为Promise,同样,函数内部要搭配使用 await,就是等待的意思,可以理解为Promise中的resolve,只要使用了await,那么JS在执行时,一定会将await后面的内容执行完并且有结果后,才会执行下一步,哪怕请求等待的时间有一万年,也会停在这里等待,有结果后才会执行下一步;

2.await必须在async函数里使用,不能单独使用;

3.await后面需要跟Promise对象,不然就没有意义,而且await后面的Promise对象不必写then,因为await的作用之一就是获取后面Promise对象成功状态传递出来的参数;

下面以业务中的实例进行讲解:

假如有3个接口,A接口(getSwiper),B接口(getWinner),C接口(getBeans),先执行A,等A接口请求完成,再执行B,等B接口请求完成,再执行C,重点在于,必须等A请求完成,才能执行下一个,先看三个方法的代码:

实际上,传统写法,是core.post().then(//拿到回调),但用到 await,直接可以在外部用变量res接受回调,十分方便

  /**
   * @description: 获取首页轮播图
   * @param {type} 
   */
  async getSwiper() {
    const res = await core.post('/loop/list', {
      location: 1,
      width: '750',
      height: '240',
    });

    return res;
  },


  /**
   * @description: 获取 中奖用户信息列表
   * @param {type} 
   */
  async getWinner(testParam) {
    const res = await core.post('/groupBuy/winner', {
      test: testParam,
    });

    return res;
  },


  /**
   * @description: 检测并领取获赠豆豆
   * @param {type} 
   */
  async getBeans() {
    const res = await core.post('/member/beans/receive', {
      member_id: app.globalData.member_id
    });

    return res;
  },

可以看出,由于要同步执行,所以每一个方法前面我都加了async,每个方法内部,定义了一个res变量,这个变量用于接收请求成功后服务端返回的数据,可以看到 core.post请求方法前,我加了一个await,那么此时的逻辑就是 res这个变量,等在这里,什么时候post请求有返回值,我才继续执行下面的语句,所以这里return res,必定是res变量里面有数据后,才return的,其实async中的return,就相当于Promise中的resolve(),意味着请求已经完成,三个不同的方法定义好了,我们需要顺序执行,一个方法请求完,才能执行下一个,看具体代码:

  /**
   * @description: testFunc
   * @param {type} 
   */
  async testFunc() {

    const func_1 = await this.getSwiper();

    const func_2 = await this.getWinner();

    const func_3 = await this.getBeans();

    console.log(`func_1: ${func_1} --- func_2: ${func_2} --- func_3: ${func_3}`);

  },

可以看到,testFunc中,使用使用const分别定义func1、func2、func3,简单说下执行的顺序,

const func_1 = await this.getSwiper();

当执行上面这一行时,我们可以认为语句停在了这一行,await(等待)this.getSwiper()有返回值后,才执行下面的语句,哪怕getSwiper需要等1年才有返回值,语句也会停在这等1年;

那么await this.getSwiper到底返回什么呢?其实就是在getSwiper()方法中,return的内容,想返回什么就return什么值;

当func_1有值之后,接着执行下一行,以此类推,使得func_2、func_3都有值,再执行最后一行的console.log()语句,因为上述语句都必定有值,所以避免了以往情况下undefind的尴尬情况,看看最后console.log()打印的内容:


可以看到func_1、func_2、func_3都有值,如果自己想用以上的值干点什么,这时候就能随心所欲地写了,因为所有值必然会先拿到再去执行后面的语句,所以使用async可以达到同步执行的目的;

本来写到这里就可以结束的,但我不能这么草草地结束,我希望有人问我一句,你凭什么说几个方法是同步顺序执行的?看下请求抓包图:


可以看到3个请求,当一个彻底请求完成时,下一个才会发起请求。

再说下请求的方法,都是使用core.post,如果想在core.post之前加上await,那么core.post方法也应该做一些处理:

/**
 * @description: post请求
 * @param {String} url 接口地址
 * @param {Object} params 请求参数
 * @param {String} version 版本号
 */
const post = (url, params, version) => {
    const promise = new Promise((resolve, reject) => {
        wx.request({
            // 版本默认v1,如果传了就用传的
            url: `${config.HOST_URL}${version ? version : config.VERSION}${url}`,
            method: 'POST',
            data: params,
            header: {
                'Accept': 'application/json',
                'content-type': 'application/json',
            },
            dataType: 'json',
            success: (res) => {
                resolve(res);
            },
        });
    });
    return promise;
};

为什么我在core.post方法中不使用async呢,因为core.post使用的是微信原生的wx.request方法发起请求,在success成功回调中,我们就必须在拿到值之后,标记为完成状态,文章一开始也讲了,async其实就是Promise的扩展,所以针对这种原生的请求方法时,直接使用Promise进行封装是最简单的方法。

个人觉得比较好的几篇关于async的文章:

与Promise血脉相连的async/await(重点推荐!

async / await 替代Promise?

「译」更快的 async 函数和 promises

vue中异步函数async和await的用法

文章写完了,我想说点别的,一个async和await我写了3000字,我的最终目的就是用最浅显易懂的语言将自己理解的事物表达出来,让傻瓜都能看懂,我也是从不会慢慢学会,其实这个过程有时候很痛苦,网上的答案写的并不通俗易懂,要么过于高大上,很多人对于简单的东西不屑一顾,我愿意花时间将自己的感悟一一写下,用简单粗浅的道理解答别人的疑惑,我很享受这个过程。

聪明人很多,做一个快乐的傻瓜。