async
與 await
的使用方式相對(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è)你想使用await
和getNumFruit
來(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')
}
在 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)(比如while
和for-of
循環(huán))…
但是它不能處理需要回調(diào)的循環(huán)齿风,如forEach
、map
绑洛、filter
和reduce
救斑。在接下來(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 感知,也支持 async
和await
左敌,所以不能在 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è)步驟
- 使用
map
返回一個(gè)promise 數(shù)組 - 使用
await
等待處理結(jié)果 - 使用
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)很有趣辨图。
- 在第一次遍歷中班套,
sum
為0
。numFruit
是27
(通過(guò)getNumFruit(apple)
的得到的值)徒役,0 + 27 = 27
孽尽。 - 在第二次遍歷中窖壕,
sum
是一個(gè)promise
忧勿。 (為什么杉女?因?yàn)楫惒胶瘮?shù)總是返回promises
!)numFruit
是0
.promise 無(wú)法正常添加到對(duì)象鸳吸,因此JavaScript將其轉(zhuǎn)換為[object Promise]
字符串熏挎。[object Promise] + 0
是object Promise] 0
。 - 在第三次遍歷中晌砾,
sum
也是一個(gè)promise
坎拐。numFruit
是14
.[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)單(也是最有效)的方法是
使用
map
返回一個(gè)promise 數(shù)組使用
await
等待處理結(jié)果-
使用
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)什么
- 如果你想連續(xù)執(zhí)行
await
調(diào)用,請(qǐng)使用for
循環(huán)(或任何沒(méi)有回調(diào)的循環(huán))雅镊。 - 永遠(yuǎn)不要和
forEach
一起使用await
把曼,而是使用for
循環(huán)(或任何沒(méi)有回調(diào)的循環(huán))。 - 不要在
filter
和reduce
中使用await
漓穿,如果需要嗤军,先用map
進(jìn)一步驟處理,然后在使用filter
和reduce
進(jìn)行處理晃危。