天天看點

async await 和Promise的結合使用,Promise鍊解決循環異步請求,循環依次執行異步代碼

需求分析

當我們想依次執行多個異步請求時,或者實作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