淺析非同步迴圈for await of的使用及執行機制及for of/forEach本質區別和遇到非同步時的處理
一、基本介紹
1、語法定義
for await...of
語句建立一個迴圈,該迴圈遍歷非同步可迭代物件以及同步可迭代物件,包括: 內建的 String
, Array
,類似陣列物件 (例如 arguments
或 NodeList
),TypedArray
, Map
, Set
和使用者定義的非同步/同步迭代器。它使用物件的每個不同屬性的值呼叫要執行的語句來呼叫自定義迭代鉤子。
類似於 await
運算子一樣,該語句只能在一個async function 內部使用。
2、如何使用
function getTime(seconds){
return new Promise(resolve=>{
setTimeout(() => {
console.log(seconds)
resolve(seconds)
}, seconds);
})
}
function test(){
let arr = [getTime(2000), getTime(300), getTime(1000)]
for (let x of arr){
console.log(x);
}
}
test()
對比區別,這裡沒有 await 。你可以猜測下這個會怎麼輸出?分別是 2000的Promise、300的Promise、1000的Promise 順序輸出的,然後再按時間延時列印 300、1000、2000
那麼我們再想一下,如果我就是需要按 2000、300、1000 的順序輸出怎麼辦呢?
首先想到的是 async await 實現是可以的,但是會有缺陷。如果有好幾個promise或者非同步任務,就會寫相應數量的 await,程式碼量變得龐大臃腫。所以使用 for await of 來實現
此時的輸出就是 Promise undefined,然後過 2s 後,按 2000、300、1000 的順序輸出。
3、執行機制:for await of 迴圈可以暫停迴圈,當第一個非同步執行完成後才會執行下一個,最後結果是讓輸出結果保持同步順序輸出。
什麼意思呢?我們自己直接試試就知道了。
(1)上面程式碼 arr = [getTime(200), getTime(3000), getTime(1000)] 時,先列印 200,然後過 2s,再一起列印3000、1000
(2)[getTime(1000), getTime(5000), getTime(6000)] 時,先列印 1000,然後過4s,列印 5000,再過1s列印6000
二、async+await 遇見 forEach和 for···of
1、首先看 2 道題,自己先考慮下,能不能答對。
// 定義一個fetch函式模擬非同步請求
function fetch(x) {
return new Promise((resolve, reject) => {
console.log('aaa');
setTimeout(() => {
resolve(x)
}, 500 * x)
})
}
// 第一題:
function test() {
let arr = [3, 2, 1]
arr.forEach(async item => {
const res = await fetch(item)
console.log(res)
})
console.log('end')
}
test(); // 輸出什麼
// 第二題:
async function test() {
let arr = [3, 2, 1]
for (const item of arr) {
const res = await fetch(item)
console.log(res)
}
console.log('end')
}
test(); // 輸出什麼
第一題:先同時先3個aaa和end,test函式執行完成返回undefined,然後非同步回撥每隔500ms依次列印1、2、3
第二題:先aaa,1500ms後列印 3,再 aaa,1s後列印2,再aaa,500ms後列印1,再end,再test函式返回值undefined。
這裡我理解錯了,test函式執行完成,就應該返回 Promise undefined,也就是第二題輸出是這樣的:
這裡需要特別注意下 end 的列印,這裡涉及到“協程”的概念理解,asyc await 本質是利用協程來實現的。
2、為什麼同樣是遍歷,輸出結果卻不一樣呢?
因為 for...of 內部處理的機制和 forEach 不同,forEach 是直接呼叫回撥函式,for...of 是通過迭代器的方式去遍歷。
3、兩者的處理機制:
// 參考下 Polyfill 版本的 forEach,簡化後的虛擬碼:
while (index < arr.length) {
callback(item, index) //我們傳入的回撥函式
}
而使用迭代器寫第二題(既for...of 碼的語法糖)等價於:
async function test() {
let arr = [3, 2, 1]
const iterator = arr[Symbol.iterator]() //for of會自動呼叫遍歷器函式
let res = iterator.next()
while (!res.done) {
const value = res.value
const res1 = await fetch(value)
console.log(res1)
res = iterator.next()
}
console.log('end')
}