譯者:前端小智javascript
阿里雲最近在作活動,低至2折,有興趣能夠看看:promotion.aliyun.com/ntms/yunpar…前端
爲了保證的可讀性,本文采用意譯而非直譯。 async
與 await
的使用方式相對簡單。 蛤當你嘗試在循環中使用await
時,事情就會變得複雜一些。java
在本文中,分享一些在若是循環中使用await
值得注意的問題。git
對於這篇文章,假設你想從水果籃中獲取水果的數量。github
const fruitBasket = {
apple: 27,
grape: 0,
pear: 14
};
複製代碼
你想從fruitBasket
得到每一個水果的數量。 要獲取水果的數量,可使用getNumFruit
函數。數組
const getNumFruit = fruit => {
return fruitBasket[fruit];
};
const numApples = getNumFruit('apple');
console.log(numApples); //27
複製代碼
如今,假設fruitBasket
是從服務器上獲取,這裏咱們使用 setTimeout
來模擬。promise
const sleep = ms => {
return new Promise(resolve => setTimeout(resolve, ms))
};
const getNumFruit = fruit => {
return sleep(1000).then(v => fruitBasket[fruit]);
};
getNumFruit("apple").then(num => console.log(num)); // 27
複製代碼
最後,假設你想使用await
和getNumFruit
來獲取異步函數中每一個水果的數量。服務器
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')
}
複製代碼
首先定義一個存放水果的數組:app
const fruitsToGet = [「apple」, 「grape」, 「pear」];
複製代碼
循環遍歷這個數組:
const forLoop = async _ => {
console.log('Start');
for (let index = 0; index < fruitsToGet.length; index++) {
// 獲得每一個水果的數量
}
console.log('End')
}
複製代碼
在for
循環中,過上使用getNumFruit
來獲取每一個水果的數量,並將數量打印到控制檯。
因爲getNumFruit
返回一個promise
,咱們使用 await
來等待結果的返回並打印它。
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')
}
複製代碼
當使用await
時,但願JavaScript暫停執行,直到等待 promise 返回處理結果。這意味着for
循環中的await
應該按順序執行。
結果正如你所預料的那樣。
「Start」;
「Apple: 27」;
「Grape: 0」;
「Pear: 14」;
「End」;
複製代碼
這種行爲適用於大多數循環(好比while
和for-of
循環)…
可是它不能處理須要回調的循環,如forEach
、map
、filter
和reduce
。在接下來的幾節中,咱們將研究await
如何影響forEach
、map和filter
。
首先,使用 forEach
對數組進行遍歷。
const forEach = _ => {
console.log('start');
fruitsToGet.forEach(fruit => {
//...
})
console.log('End')
}
複製代碼
接下來,咱們將嘗試使用getNumFruit
獲取水果數量。 (注意回調函數中的async
關鍵字。咱們須要這個async
關鍵字,由於await
在回調函數中)。
const forEachLoop = _ => {
console.log('Start');
fruitsToGet.forEach(async fruit => {
const numFruit = await getNumFruit(fruit);
console.log(numFruit)
});
console.log('End')
}
複製代碼
我指望控制檯打印如下內容:
「Start」;
「27」;
「0」;
「14」;
「End」;
複製代碼
但實際結果是不一樣的。在forEach
循環中等待返回結果以前,JavaScrip先執行了 console.log('End')。
實際控制檯打印以下:
‘Start’
‘End’
‘27’
‘0’
‘14’
複製代碼
JavaScript 中的 forEach
不支持 promise 感知,也不支持 async
和await
,因此不能在 forEach
使用 await
。
若是在map
中使用await
, map
始終返回promise
數組,這是由於異步函數老是返回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
數組獲得處理。 或者經過await Promise.all(arrayOfPromises)
來完成此操做。
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')
}
複製代碼
運行結果以下:
若是你願意,能夠在promise
中處理返回值,解析後的將是返回的值。
const mapLoop = _ => {
// ...
const promises = fruitsToGet.map(async fruit => {
const numFruit = await getNumFruit(fruit);
return numFruit + 100
})
// ...
}
「Start」;
「[127, 100, 114]」;
「End」;
複製代碼
當你使用filter
時,但願篩選具備特定結果的數組。假設過濾數量大於20的數組。
若是你正常使用filter
(沒有 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')
}
複製代碼
運行結果
Start
["apple"]
END
複製代碼
filter
中的await
不會以相同的方式工做。 事實上,它根本不起做用。
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')
}
// 打印結果
Start
["apple", "grape", "pear"]
END
複製代碼
爲何會發生這種狀況?
當在filter
回調中使用await
時,回調老是一個promise
。因爲promise
老是真的,數組中的全部項都經過filter
。在filter
使用 await
類如下這段代碼
const filtered = array.filter(true);
複製代碼
在filter
使用 await
正確的三個步驟
使用map
返回一個promise 數組
使用 await
等待處理結果
使用 filter
對返回的結果進行處理
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') }
若是想要計算 fruitBastet
中的水果總數。 一般,你可使用reduce
循環遍歷數組並將數字相加。
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')
}
複製代碼
運行結果:
當你在 reduce
中使用await
時,結果會變得很是混亂。
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
是什麼 鬼??
剖析這一點頗有趣。
在第一次遍歷中,sum
爲0
。numFruit
是27
(經過getNumFruit(apple)
的獲得的值),0 + 27 = 27
。
在第二次遍歷中,sum
是一個promise
。 (爲何?由於異步函數老是返回promises
!)numFruit
是0
.promise 沒法正常添加到對象,所以JavaScript將其轉換爲[object Promise]
字符串。 [object Promise] + 0
是object Promise] 0
。
在第三次遍歷中,sum
也是一個promise
。 numFruit
是14
. [object Promise] + 14
是[object Promise] 14
。
解開謎團!
這意味着,你能夠在reduce
回調中使用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
操做都須要很長時間。 發生這種狀況是由於reduceLoop
須要等待每次遍歷完成promisedSum
。
有一種方法能夠加速reduce
循環,若是你在等待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')
}
複製代碼
這是由於reduce
能夠在等待循環的下一個迭代以前觸發全部三個getNumFruit
promise。然而,這個方法有點使人困惑,由於你必須注意等待的順序。
在reduce中使用wait最簡單(也是最有效)的方法是
使用map
返回一個promise 數組
使用 await
等待處理結果
使用 reduce
對返回的結果進行處理
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') }
這個版本易於閱讀和理解,須要一秒鐘來計算水果總數。
若是你想連續執行await
調用,請使用for
循環(或任何沒有回調的循環)。
永遠不要和forEach
一塊兒使用await
,而是使用for
循環(或任何沒有回調的循環)。
不要在 filter
和 reduce
中使用 await
,若是須要,先用 map
進一步驟處理,而後在使用 filter
和 reduce
進行處理。
爲了回饋讀者,《大遷世界》不按期舉行(每月一到三次),現金抽獎活動,保底200,外加用戶讚揚,但願你能成爲大遷世界的小錦鯉,快來試試吧
乾貨系列文章彙總以下,以爲不錯點個Star,歡迎 加羣 互相學習。
我是小智,公衆號「大遷世界」做者,對前端技術保持學習愛好者。我會常常分享本身所學所看的乾貨,在進階的路上,共勉!
關注公衆號,後臺回覆福利,便可看到福利,你懂的。
每次整理文章,通常都到2點才睡覺,一週4次左右,挺苦的,還望支持,給點鼓勵