需求分析

当我们想依次执行多个异步请求时,或者实现setTimeout为setInterval时,可以使用Promise对象和async await关键字来实现该功能。

尤其是,我们在使用node做网络爬虫时,需要进行大量的依次进行的异步请求。

波及到的知识点如下:

1、数组的reduce方法

reduce() 方法对数组中的每个元素执行一次处理函数,将其结果汇总为单个返回值。

reduce详情:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Array/Reduce

2、Promise

微任务,立即执行的函数,resolve和reject为完成状态下执行的方法。

3、async和await

async函数执行结果为一个Promise对象;await 等待一个Promise执行完成后的结果,即resolve或reject的值。

实现代码:

注意:reduce方法中,有两种实现方式。

reduce必须接收一个Promise对象,且每次执行reduce处理函数,都是一个Promise对象,所以reduce的处理函数返回值必须为Promise

setTime函数是一个异步方法,所以必须要用async await来等待setTime异步函数的执行完成。

因此我们的写法如下:

        var setTime = function(v){
            return new Promise(function(resolve, reject){
                setTimeout(()=> {
                    console.log(v)
                    resolve();
                }, 1000)
            })
        }
        var arr = [10,20,30,40];
        arr.reduce(async function(acc,cur,index){
            return acc.then(() => {
                //第二种实现方法
                // return new Promise(async (resolve, reject) => {
                //     await setTime(cur);
                //     resolve();
                // })
                return (async function a(){
                    //await之前的宏任务,因为上一个异步的await完成后,就会立即执行该宏任务
                    //console.log(cur) 
                    await setTime(cur);
                    //await之后的宏任务,该await执行完后,立即执行后面的宏任务,相当于对该await的补充
                    // console.log(cur) 
                })()
            })
        },Promise.resolve())

深入理解:

核心思想是:宏任务执行建一个Promise链,类似下面这种:——Promise链是宏任务瞬间执行完成的,结合数组reduce累加器可以累加构造Promise链

Promise.resolve().then(异步处理函数1).then(异步处理函数2).then....

 所以我们的代码目的是建Promise链,比如下面这种写法:

        var setTime = function (v) {
            return new Promise(function (resolve, reject) {
                setTimeout(() => {
                    console.log(v)
                    resolve();
                }, 1000)
            })
        }
        var arr = [10, 20, 30, 40];
        arr.reduce(async function (acc, cur, index) {
            return acc.then(() => {
                return setTime(cur);
            })
        }, Promise.resolve())

我们的目的是构造Promise链,如下也是可以的:

        var setTime = function (v) {
            return new Promise(function (resolve, reject) {
                setTimeout(() => {
                    console.log(v)
                    resolve();
                }, 1000)
            })
        }
        var p = Promise.resolve();
        for(let i = 0; i < 5; i++){
            p = p.then(function(){
                return setTime(i);
            })
        }

你是怎么想的呢?

封装一个Promise链函数

注意:for循环内的宏任务瞬间执行完,执行次数为arr.length - 1,之后是promise链的链式执行;

    let promiseChain = function (arr) {
      let setTime = (v => {
        return new Promise(function (resolve, reject) {
          setTimeout(() => {
            console.log(v)
            resolve();
          }, 1000)
        })
      })
      let p = Promise.resolve();
      for (let i = 0; i < arr.length; i++) {
        console.log(1111) // 注意此处瞬间执行完,执行了arr.length -1 次
        p = p.then(function () {
          return setTime(arr[i]);
        })
      }
    }
    let arr = ['a','b','c']
    promiseChain(arr);

如下修改请注意:

如果不使用let 或 const 而是使用的var,那么promise链执行结果为arr[length-1]的值,所以此处的let和const 块作用域也相当关键

for (var i = 0; i < arr.length; i++) {
        var m = arr[i]
        p = p.then(function () {
          return setTime(m);
        })
      }

如果使用async和await,整个for循环在执行到await时都会等待

    async function message() {
      for (let item of arr) {
        console.log(item)
        await new Promise(function (resolve, reject) {
          setTimeout(() => {
            console.log(item)
            resolve();
          }, 1000)
        })
      }
    }
    message()

打印结果:

立即打印a——1s后,打印a b——1s后打印b c——1s后打印c

Logo

腾讯云面向开发者汇聚海量精品云计算使用和开发经验,营造开放的云计算技术生态圈。

更多推荐