博客 / 詳情

返回

記錄arr.map 和arr.foreach 遇到的回調函數(異步和同步問題)的坑

大致問題是:
通過map或者foreach循環的回調函數操作數組,回調函數內部有ajax異步函數,通過await同步的寫法來調用的。console.log打印出數組跟實際渲染到view層的頁面始終不一致。
後來改寫成for循環就好了。

所以總結出來,基礎知識,在理解map和foreach這個api不夠深刻,也對await 關鍵詞的使用場景理解不透徹(雖然是for循環,但作用域函數可以找到頂層的函數作用域,所以不會報錯)

貼出代碼:

錯誤代碼

adBannerList.map(async (item) => {
  // 異步函數 轉同步的寫法
  let res = await this.getAliyunAds();
  // 異步函數執行成功後,改變此數組的值
  item.imageUrl = res.imgUrl;
  item.linkUrl = res.actUrl;
})
// map 循環結束後,再執行雙向綁定
this.setData({
  bannerList: adBannerList,
});
console.log('bannerList', this.data.bannerList)

期望結果是map循環結束後,才會執行雙向綁定setData函數。
實際結果是:還未等map循環完(還未等內部的await 後面的異步函數執行完),setData 就執行了。

先搞清下async-await:
後來查詢資料:await是異步轉同步的寫法,但並不會阻塞主線程的同步進行的代碼,只會阻塞異步代碼
但看如下代碼:

async function timeout(ms) {
  await new Promise((resolve) => {
    setTimeout(resolve, ms);
  });
}
 
async function asyncPrint(value, ms) {
  await timeout(ms);
  console.log(value);
}
 
asyncPrint('hello world', 3000);
// hello world (3s後被打印)

所以:

在async語句裏,同步和await異步代碼不會以Eventloop事件方式進行區分(也就不存在先執行同步 後執行異步的操作)帶有await的異步和同步代碼統一被視為同步代碼,更準確的説是以同步式阻塞方式 從上至下依次執行

回到正題:
forEach、map這樣的高級循環遍歷函數,在循環的同時,是不能更改內部item對象的(map更改後,返回的是新數組,forEach是原數組被更改)。
推薦看forEach 和for循環的區別,這篇文章
for和forEach的區別
此時console.log 數組所看見的結果,跟view渲染呈現的結果不一致。
這裏需要去深入理解下,引用類型和簡單類型的console.log,以及console.log的機制。
推薦看這篇文章:console.log遇見的坑

正確代碼:

for(let i = 0;i < adBannerList.length; i++) {
  let item = adBannerList[i]
  // 這裏await關鍵詞可以用是因為for循環內部沒函數作用域,
  //所以會向上找函數作用域,只要有async就可以。
  let res = await this.getAliyunAds();
  item.imageUrl = res.imgUrl;
  item.linkUrl = res.actUrl;
}

this.setData({
  bannerList: adBannerList,
});

知識點總結:
1.map和foreach(應該還有更多的類似函數)的回調函數是一個同步函數,非異步函數。

需要升入的知識點:
1、console.log的機制。尤其是針對引用類型
2、類型map和foreach 這樣的循環函數機制。

至於為何for循環可以解決我的業務問題,我其實還沒完全搞懂。
其實查詢資料發現,阮一峯在介紹async-await的時候,就舉例
説過類似的問題。只是在運用在實際工作中,翻車後才能真正掌握其知識點,並嘗試去理解為什麼?
如果有讀者知道怎麼解釋for和forEach 為何前者可以滿足我的業務,歡迎留言。

摘抄阮一峯一章節的例子:
下面的代碼也可以解決我的問題,其實無非就是接口都請求成功後,按順序更改我的目標數組罷了。

async function dbFuc(db) {
  let docs = [{}, {}];
  let promises = docs.map((doc) => db.post(doc));

  let results = await Promise.all(promises);
  console.log(results);
}
// 或者使用下面的寫法 
async function dbFuc(db) {
  let docs = [{}, {}];
  let promises = docs.map((doc) => db.post(doc));

  let results = [];
  for (let promise of promises) {
    results.push(await promise);
  }
  console.log(results);
}

參考文獻:

阮一峯async-await方法

解釋async-await

user avatar zzd41 頭像 guizimo 頭像 zhangxishuo 頭像 waweb 頭像 79px 頭像 qianduanlangzi_5881b7a7d77f0 頭像 b_a_r_a_n 頭像 user_ze46ouik 頭像 codeoop 頭像 huaihuaidedianti 頭像 mofaboshi 頭像 xuriliang 頭像
39 位用戶收藏了這個故事!

發佈 評論

Some HTML is okay.