如何在 JS 循環(huán)中正確使用 async 與 await

asyncawait 的使用方式相對(duì)簡(jiǎn)單茬贵。 當(dāng)你嘗試在循環(huán)中使用await時(shí),事情就會(huì)變得復(fù)雜一些。

在本文中,分享一些在如果循環(huán)中使用await值得注意的問(wèn)題堰汉。

準(zhǔn)備一個(gè)例子

對(duì)于這篇文章,假設(shè)你想從水果籃中獲取水果的數(shù)量祭务。

const fruitBasket = {
 apple: 27,
 grape: 0,
 pear: 14
};

你想從fruitBasket獲得每個(gè)水果的數(shù)量中狂。 要獲取水果的數(shù)量,可以使用getNumFruit函數(shù)啦膜。

const getNumFruit = fruit => {
  return fruitBasket[fruit];
};

const numApples = getNumFruit('apple');
console.log(numApples); //27

現(xiàn)在有送,假設(shè)fruitBasket是從服務(wù)器上獲取,這里我們使用 setTimeout 來(lái)模擬僧家。

const sleep = ms => {
  return new Promise(resolve => setTimeout(resolve, ms))
};

const getNumFruie = fruit => {
  return sleep(1000).then(v => fruitBasket[fruit]);
};

getNumFruit("apple").then(num => console.log(num)); // 27

最后雀摘,假設(shè)你想使用awaitgetNumFruit來(lái)獲取異步函數(shù)中每個(gè)水果的數(shù)量。

const control = async _ => {
  console.log('Start')

  const numApples = await getNumFruit('apple');
  console.log(numApples);

  const numGrapes = await getNumFruit('grape');
  console.log(numGrapes);

  const numPears = await getNumFruit('pear');
  console.log(numPears);

  console.log('End')
}

1.gif

在 for 循環(huán)中使用 await

首先定義一個(gè)存放水果的數(shù)組:

const fruitsToGet = [“apple”, “grape”, “pear”];

循環(huán)遍歷這個(gè)數(shù)組:

const forLoop = async _ => {
  console.log('Start');

  for (let index = 0; index < fruitsToGet.length; index++) {
    // 得到每個(gè)水果的數(shù)量
  }

  console.log('End')
}

for循環(huán)中八拱,過(guò)上使用getNumFruit來(lái)獲取每個(gè)水果的數(shù)量阵赠,并將數(shù)量打印到控制臺(tái)。

由于getNumFruit返回一個(gè)promise肌稻,我們使用 await 來(lái)等待結(jié)果的返回并打印它豌注。

const forLoop = async _ => {
  console.log('start');

  for (let index = 0; index < fruitsToGet.length; index ++) {
    const fruit = fruitsToGet[index];
    const numFruit = await getNumFruit(fruit);
    console.log(numFruit);
  }
  console.log('End')
}

當(dāng)使用await時(shí),希望JavaScript暫停執(zhí)行灯萍,直到等待 promise 返回處理結(jié)果轧铁。這意味著for循環(huán)中的await 應(yīng)該按順序執(zhí)行。

結(jié)果正如你所預(yù)料的那樣旦棉。

“Start”;
“Apple: 27”;
“Grape: 0”;
“Pear: 14”;
“End”;

這種行為適用于大多數(shù)循環(huán)(比如whilefor-of循環(huán))…

但是它不能處理需要回調(diào)的循環(huán)齿风,如forEachmap绑洛、filterreduce救斑。在接下來(lái)的幾節(jié)中,我們將研究await 如何影響forEach真屯、map和filter脸候。

在 forEach 循環(huán)中使用 await

首先,使用 forEach 對(duì)數(shù)組進(jìn)行遍歷绑蔫。

const forEach = _ => {
  console.log('start');

  fruitsToGet.forEach(fruit => {
    //...
  })

  console.log('End')
}

接下來(lái)运沦,我們將嘗試使用getNumFruit獲取水果數(shù)量。 (注意回調(diào)函數(shù)中的async關(guān)鍵字配深。我們需要這個(gè)async關(guān)鍵字携添,因?yàn)?code>await在回調(diào)函數(shù)中)。

const forEachLoop = _ => {
  console.log('Start');

  fruitsToGet.forEach(async fruit => {
    const numFruit = await getNumFruit(fruit);
    console.log(numFruit)
  });

  console.log('End')
}

我期望控制臺(tái)打印以下內(nèi)容:

“Start”;
“27”;
“0”;
“14”;
“End”;

但實(shí)際結(jié)果是不同的篓叶。在forEach循環(huán)中等待返回結(jié)果之前烈掠,JavaScrip先執(zhí)行了 console.log('End')羞秤。

實(shí)際控制臺(tái)打印如下:

‘Start’
‘End’
‘27’
‘0’
‘14’

JavaScript 中的 forEach不支持 promise 感知,也支持 asyncawait左敌,所以不能在 forEach 使用 await 瘾蛋。

在 map 中使用 await

如果在map中使用await, map 始終返回promise數(shù)組,這是因?yàn)楫惒胶瘮?shù)總是返回promise矫限。

const mapLoop = async _ => {
  console.log('Start')
  const numFruits = await fruitsToGet.map(async fruit => {
    const numFruit = await getNumFruit(fruit);
    return numFruit;
  })

  console.log(numFruits);

  console.log('End')
}

“Start”;
“[Promise, Promise, Promise]”;
“End”;

如果你在 map 中使用 await瘦黑,map 總是返回promises,你必須等待promises 數(shù)組得到處理奇唤。 或者通過(guò)await Promise.all(arrayOfPromises)來(lái)完成此操作幸斥。


const mapLoop = async _ => {
  console.log('Start');

  const promises = fruitsToGet.map(async fruit => {
    const numFruit = await getNumFruit(fruit);
    return numFruit;
  });

  const numFruits = await Promise.all(promises);
  console.log(numFruits);

  console.log('End')
}

運(yùn)行結(jié)果如下:

如果你愿意,可以在promise 中處理返回值咬扇,解析后的將是返回的值甲葬。

const mapLoop = _ => {
  // ...
  const promises = fruitsToGet.map(async fruit => {
    const numFruit = await getNumFruit(fruit);
    return numFruit + 100
  })
  // ...
}

“Start”;
“[127, 100, 114]”;
“End”;

在 filter 循環(huán)中使用 await

當(dāng)你使用filter時(shí),希望篩選具有特定結(jié)果的數(shù)組懈贺。假設(shè)過(guò)濾數(shù)量大于20的數(shù)組经窖。

如果你正常使用filter (沒(méi)有 await),如下:

const filterLoop =  _ => {
  console.log('Start')

  const moreThan20 =  fruitsToGet.filter(async fruit => {
    const numFruit = await fruitBasket[fruit]
    return numFruit > 20
  })

  console.log(moreThan20) 
  console.log('END')
}

運(yùn)行結(jié)果

Start
["apple"]
END

filter 中的await不會(huì)以相同的方式工作梭灿。 事實(shí)上画侣,它根本不起作用。

const filterLoop = async _ => {
  console.log('Start')

  const moreThan20 =  await fruitsToGet.filter(async fruit => {
    const numFruit = fruitBasket[fruit]
    return numFruit > 20
  })

  console.log(moreThan20) 
  console.log('END')
}

// 打印結(jié)果
Start
["apple", "grape", "pear"]
END

為什么會(huì)發(fā)生這種情況?

當(dāng)在filter 回調(diào)中使用await時(shí)堡妒,回調(diào)總是一個(gè)promise配乱。由于promise 總是真的,數(shù)組中的所有項(xiàng)都通過(guò)filter 皮迟。在filter 使用 await類(lèi)以下這段代碼

const filtered = array.filter(true);

filter使用 await 正確的三個(gè)步驟

  1. 使用map返回一個(gè)promise 數(shù)組
  2. 使用 await 等待處理結(jié)果
  3. 使用 filter 對(duì)返回的結(jié)果進(jìn)行處理
const filterLoop = async _ => {
  console.log('Start');

  const promises = await fruitsToGet.map(fruit => getNumFruit(fruit));

  const numFruits = await Promise.all(promises);

  const moreThan20 = fruitsToGet.filter((fruit, index) => {
    const numFruit = numFruits[index];
    return numFruit > 20;
  })

  console.log(moreThan20);
  console.log('End')
} 

在 reduce 循環(huán)中使用 await

如果想要計(jì)算 fruitBastet中的水果總數(shù)搬泥。 通常,你可以使用reduce循環(huán)遍歷數(shù)組并將數(shù)字相加伏尼。

const reduceLoop = _ => {
  console.log('Start');

  const sum = fruitsToGet.reduce((sum, fruit) => {
    const numFruit = fruitBasket[fruit];
    return sum + numFruit;
  }, 0)

  console.log(sum)
  console.log('End')
}

運(yùn)行結(jié)果:

當(dāng)你在 reduce 中使用await時(shí)忿檩,結(jié)果會(huì)變得非常混亂爆阶。

 const reduceLoop = async _ => {
  console.log('Start');

  const sum = await fruitsToGet.reduce(async (sum, fruit) => {
    const numFruit = await fruitBasket[fruit];
    return sum + numFruit;
  }, 0)

  console.log(sum)
  console.log('End')
}

[object Promise]14 是什么 鬼燥透??

剖析這一點(diǎn)很有趣辨图。

  1. 在第一次遍歷中班套,sum0numFruit27(通過(guò)getNumFruit(apple)的得到的值)徒役,0 + 27 = 27孽尽。
  2. 在第二次遍歷中窖壕,sum是一個(gè)promise忧勿。 (為什么杉女?因?yàn)楫惒胶瘮?shù)總是返回promises!)numFruit0.promise 無(wú)法正常添加到對(duì)象鸳吸,因此JavaScript將其轉(zhuǎn)換為[object Promise]字符串熏挎。 [object Promise] + 0object Promise] 0
  3. 在第三次遍歷中晌砾,sum 也是一個(gè)promise坎拐。 numFruit14. [object Promise] + 14[object Promise] 14

解開(kāi)謎團(tuán)养匈!

這意味著哼勇,你可以在reduce回調(diào)中使用await,但是你必須記住先等待累加器呕乎!

const reduceLoop = async _ => {
  console.log('Start');

  const sum = await fruitsToGet.reduce(async (promisedSum, fruit) => {
    const sum = await promisedSum;
    const numFruit = await fruitBasket[fruit];
    return sum + numFruit;
  }, 0)

  console.log(sum)
  console.log('End')
}

但是從上圖中看到的那樣积担,await 操作都需要很長(zhǎng)時(shí)間。 發(fā)生這種情況是因?yàn)?code>reduceLoop需要等待每次遍歷完成promisedSum猬仁。

有一種方法可以加速reduce循環(huán)帝璧,如果你在等待promisedSum之前先等待getNumFruits(),那么reduceLoop只需要一秒鐘即可完成:

const reduceLoop = async _ => {
  console.log('Start');

  const sum = await fruitsToGet.reduce(async (promisedSum, fruit) => {
    const numFruit = await fruitBasket[fruit];
    const sum = await promisedSum;
    return sum + numFruit;
  }, 0)

  console.log(sum)
  console.log('End')
}

這是因?yàn)?code>reduce可以在等待循環(huán)的下一個(gè)迭代之前觸發(fā)所有三個(gè)getNumFruit promise湿刽。然而的烁,這個(gè)方法有點(diǎn)令人困惑,因?yàn)槟惚仨氉⒁獾却捻樞颉?/p>

在reduce中使用wait最簡(jiǎn)單(也是最有效)的方法是

  1. 使用map返回一個(gè)promise 數(shù)組

  2. 使用 await 等待處理結(jié)果

  3. 使用 reduce 對(duì)返回的結(jié)果進(jìn)行處理

    const reduceLoop = async _ => {
    console.log('Start');

    const promises = fruitsToGet.map(getNumFruit);
    const numFruits = await Promise.all(promises);
    const sum = numFruits.reduce((sum, fruit) => sum + fruit);

    console.log(sum)
    console.log('End')
    }

這個(gè)版本易于閱讀和理解诈闺,需要一秒鐘來(lái)計(jì)算水果總數(shù)渴庆。

從上面看出來(lái)什么

  1. 如果你想連續(xù)執(zhí)行await調(diào)用,請(qǐng)使用for循環(huán)(或任何沒(méi)有回調(diào)的循環(huán))雅镊。
  2. 永遠(yuǎn)不要和forEach一起使用await把曼,而是使用for循環(huán)(或任何沒(méi)有回調(diào)的循環(huán))。
  3. 不要在 filterreduce 中使用 await漓穿,如果需要嗤军,先用 map 進(jìn)一步驟處理,然后在使用 filterreduce進(jìn)行處理晃危。

原文鏈接:https://segmentfault.com/a/1190000019357943

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末叙赚,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子僚饭,更是在濱河造成了極大的恐慌震叮,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,378評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件鳍鸵,死亡現(xiàn)場(chǎng)離奇詭異苇瓣,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)偿乖,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,356評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門(mén)击罪,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)哲嘲,“玉大人,你說(shuō)我怎么就攤上這事媳禁∶吒保” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 152,702評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵竣稽,是天一觀的道長(zhǎng)囱怕。 經(jīng)常有香客問(wèn)我,道長(zhǎng)毫别,這世上最難降的妖魔是什么娃弓? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,259評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮岛宦,結(jié)果婚禮上忘闻,老公的妹妹穿的比我還像新娘。我一直安慰自己恋博,他們只是感情好齐佳,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,263評(píng)論 5 371
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著债沮,像睡著了一般炼吴。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上疫衩,一...
    開(kāi)封第一講書(shū)人閱讀 49,036評(píng)論 1 285
  • 那天硅蹦,我揣著相機(jī)與錄音,去河邊找鬼闷煤。 笑死童芹,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的鲤拿。 我是一名探鬼主播假褪,決...
    沈念sama閱讀 38,349評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼近顷!你這毒婦竟也來(lái)了生音?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 36,979評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤窒升,失蹤者是張志新(化名)和其女友劉穎缀遍,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體饱须,經(jīng)...
    沈念sama閱讀 43,469評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡域醇,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,938評(píng)論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片譬挚。...
    茶點(diǎn)故事閱讀 38,059評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡锅铅,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出殴瘦,到底是詐尸還是另有隱情狠角,我是刑警寧澤号杠,帶...
    沈念sama閱讀 33,703評(píng)論 4 323
  • 正文 年R本政府宣布蚪腋,位于F島的核電站,受9級(jí)特大地震影響姨蟋,放射性物質(zhì)發(fā)生泄漏屉凯。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,257評(píng)論 3 307
  • 文/蒙蒙 一眼溶、第九天 我趴在偏房一處隱蔽的房頂上張望悠砚。 院中可真熱鬧,春花似錦堂飞、人聲如沸灌旧。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,262評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)枢泰。三九已至,卻和暖如春铝噩,著一層夾襖步出監(jiān)牢的瞬間衡蚂,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,485評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工骏庸, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留毛甲,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,501評(píng)論 2 354
  • 正文 我出身青樓具被,卻偏偏與公主長(zhǎng)得像玻募,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子一姿,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,792評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容