小程序異步問題:多個網絡請求依次執行並依次收集請求結果

業務邏輯

最近開發一個便籤小程序的時候,有這樣一個需求:用戶能夠在寫便籤的時候添加一個或多個圖片。es6

對於這個需求,咱們用戶按下保存鍵時,內部具體的實現上是這樣的邏輯:小程序

  1. 首先檢測用戶是否傳入了圖片,若是存儲本地圖片地址的數組長度>=1,則將圖片數組放入上傳圖片的函數。
  2. 因爲小程序網絡請求大小限制,咱們只能採起循環上傳單文件,而後收集每次請求的結果--圖片在服務器的地址,最後將結果放在一個數組中供後續的操做使用。
  3. 當圖片上傳函數所有執行完畢後,將數組中的圖片數組取出來,賦值到日記對象中,再將整個日記對象提交到服務器。
  4. 服務器返回保存成功或失敗。

思路其實很是清晰簡單,可是在代碼實現上卻翻了大跟頭。數組

異步帶來的問題

小程序的網絡請求是異步的:咱們沒法經過return來將網絡請求結果返回出來使用。promise

wx.request({

          //...省略其餘屬性

          success: function (res) {

          },

          fail: function (res) {

          }

    })

例如在微信中發送網絡請求,咱們只能使用微信提供的方法wx.xxx,其中請求的結果保存在res中,而res沒法直接return獲得。 服務器

解決:res雖然沒法直接獲取,可是咱們能經過將須要使用到這個請求結果的業務邏輯代碼放入這個網絡請求的回調函數中直接讀取網絡請求結果,也就是一切都須要經過回調來解決。微信

wx.request({

          //...省略其餘屬性

          success: function (res) {

            console.log(res);

            //接業務邏輯代碼

          },

          fail: function (res) {

            console.log(res);

          }

    })

例如這個微信的網絡請求,咱們能夠經過success和fail的回調函數來讀取res的值從而完成依賴res結果的業務邏輯。網絡

回調地獄

雖然解決告終果獲取的問題,可是又產生了另外一個問題,當多個請求中有明確的前後順序時,回調會嵌套的很厲害,形成回調地獄,代碼可讀性和可維護性都會不好。
例如對於一個日記頁面,須要先請求到頁面的數據(裏面包含了圖片數據和其餘數據的地址),再根據頁面數據去請求圖片數據後再請求音頻數據。例如如下代碼:異步

//請求頁面總體數據

   wx.request({

         //...省略其餘屬性

         success: function (res) {//成功

               //請求圖片數據

               wx.request({

                 success: function (res) {//成功

                     //請求音頻數據

                     wx.request({

                         success: function (res) {//成功

                         },

                         fail: function (res) {//失敗

                             console.log("請求失敗:"+res);

                         }

                     })

                 },

                 fail: function (res) {//失敗

                     console.log("請求失敗:"+res);

                 }

               })

         },

         fail: function (res) {//失敗

             console.log("請求失敗:"+res);

         }

   })

如何優化?幸運的是,在es6裏面咱們能夠用promise去優化咱們的回調,用then代替回調,首先將網絡請求封裝成一個Promise:async

// 後臺post請求

   function postRequest(posturl, postdata) {

     return new Promise((resolve, reject) => {

       wx.request({

         //省略其餘屬性

         success: function (res) {

           console.log("at post request: 請求成功")

           resolve(res.data)//設置promise成功標誌

         },

         fail: function (res) {

           console.log("at post request: 請求失敗")

           reject(res.data)//設置promise失敗標誌

         }

       })

     });

   }

這樣封裝之後,咱們的網絡請求會在success和fail後回調resolve,這樣能夠告訴promise,「hey,我完成個人工做了,你能夠進行你的then操做了」,這樣就能夠用then來簡化嵌套邏輯。使用promise來完成上面那個問題的請求將會是這樣的:函數

postRequest(posturl,postdata)

    .then(function(res){

      //業務邏輯

      //調用下一個請求

      return postRequest(next_posturl,next_postdata);

    })

    .then(function(res){

      //業務邏輯

      //調用下一個請求

      return postRequest(next_next_posturl,next_next_postdata);

    })

    .then(function(res){

      //業務邏輯

    });

是否是簡潔的多~

一個看似簡單的需求

咱們的有一個很簡單的需求是須要對一組數量不定的圖片作分別上傳(由於微信限制因此沒法作多上傳),而且在上傳完成之後須要獲取到全部的返回結果。

那麼用咱們前面的回調函數+then的話,很天然的想到這樣的寫法

postRequest(posturl,postdata)

.then(function(res){

    //獲取返回res

    //上傳下一個圖片

    return postRequest(next_posturl,next_postdata);

})

.then(function(res){

    //獲取返回res

    //上傳下一個圖片

    return postRequest(next_next_posturl,next_next_postdata);

})

.then(function(res){

    //獲取返回res

});

這樣看起來很簡單明瞭,可是個人圖片數量是不定的,怎麼動態的構建.then.then.then這樣的鏈式調用呢?通過個人研究後發現能夠經過一個輔助的promise鏈去完成主鏈的鏈式構建。

//多文件上傳

function jabingp_upLoad(uploadurl, files) {

  return new Promise((resolve, reject) => {

    //初始化promise鏈

    var mergedAjax = Promise.resolve();

    var response = [];

    // 循環上傳

   
    // 這裏必定要使用let來爲沒一次循環構建一個塊級做用域
    // 使用var則須要配合當即執行函數
    for (let i = 0; i < files.length; i++) {

      mergedAjax = mergedAjax.then(() => {

        return jabingp_upLoadSingle(uploadurl, files[i]).then((res) => {

          response.push(res);

        });

      });

    }

    //當前面循環中全部的then執行完畢時會執行這個then

    mergedAjax.then(() => {

      resolve(response);            //設置這個函數的promise對象爲完成狀態並放入數據

    });

  });

}

經過這個函數,就完成了多個請求依次執行並收集結果的效果。這個函數的重點在於利用另一個已經處於完成狀態的promise,不斷的迭代自身,在每次迭代的then內部經過return來完成輔助鏈到業務鏈的切換。

2019-04-27 更新

使用await/async更加優雅地處理異步吧!

在es7標準中,引入了await和async這對兄弟,它們可讓咱們的異步代碼看起來和同步代碼同樣。讓咱們來看看await和async都能作什麼吧。

await能夠等待一個promise運行到完成狀態而且獲取結果,或者等待一個async修飾的函數運行完成並獲取結果,可是使用await的時候,必須在async函數體內部。好比我有這樣一個網絡請求:

function postRequest(posturl, postdata) {

     return new Promise((resolve, reject) => {

       wx.request({

         //省略其餘屬性

         success: function (res) {

           console.log("at post request: 請求成功")

           resolve(res.data)//設置promise成功標誌

         },

         fail: function (res) {

           console.log("at post request: 請求失敗")

           reject(res.data)//設置promise失敗標誌

         }

       })

     });

   }

那麼若是不使用await,我就須要這樣取得請求結果

function test(){
  postRequest(xxx,xxx).then(function(res){
      // 這裏面能夠讀取請求結果res了
      console.log(res);
  });
}
test();

能夠看到,這樣的代碼不太符合常規邏輯,咱們但願函數做用是返回數據,這樣更清晰明瞭,有了await,咱們的願望就能夠實現了。

async function test(){
 let res = await  postRequest(xxx,xxx);
 // 下面就能夠正常寫對res的讀取了
 console.log(res);
}
test();

注意我給函數加上了async,有了async和await,咱們就能夠像同步代碼同樣使用異步請求了~
那麼上面那個經過複雜的構建鏈完成的需求,經過await實現將會變得很是簡單易懂。

async function jabingp_upLoad(uploadurl, files) {
    let response = [];
  
    // 循環依次等待上傳結果
    for (let i = 0; i < files.length; i++) {
        let  res = await jabingp_upLoadSingle(uploadurl, files[i]);
        // 結果放入數組
        response.push(res);
    }
    // 返回結果
    return  response ;
}

代碼一會兒變得簡潔易懂了,注意調用的時候也一樣須要在一個async函數內部執行await。

async function test(){
 let response = await  jabingp_upLoad(xxx,xxx);
 console.log(response );
}
test();

是否是很是簡單呢,趕忙在你的異步請求中使用async和await吧~

相關文章
相關標籤/搜索