前言 forEach 是我們非常常使用的語法,而有些情況下可能需要做一些同步的事情,例如同步請求 AJAX,而這一塊在 forEach 上就很容易踩雷了。
forEach forEach 非常的好用,你只需要這樣子寫就可以輕鬆使用
1 2 3 4 var arr = [1 , 2 , 3 ]; arr.forEach ((item ) => { console .log (item); })
基本上在這邊看起來是沒有什麼問題的,但是如果今天的需求是必須使用迴圈來跑一段 AJAX 三次甚至是十次不等,那這時候結果可能就不同,這邊我寫一段簡易的程式碼給大家玩玩
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 var arr1 = [1 , 3 , 5 , 7 , 9 ];var arr2 = [2 , 4 , 6 , 8 , 10 ];function ajax (status, data, max ) { return new Promise ((resolve, reject ) => { const time = Math .floor (Math .random () * max); setTimeout (() => { if (status) { resolve (`成功:${data} ` ); } else { reject (`失敗:${data} ` ); } }, time); }) }function fn ( ) { arr1.forEach ((item ) => { ajax (true , item, 10 ).then ((res ) => { console .log (res); }) }); arr2.forEach ((item ) => { ajax (true , item, 10 ).then ((res ) => { console .log (res); }) }); }fn ();
我們可以看到上面的 AJAX 的結果在輸出時是亂數輸出的,而不是 1, 3, 5, 7… 這種方式,但我們期望的是依照順序來執行,而本身 forEach 是採用併發的方式運作(意旨同時執行、同時發送),因此並不會等另一個結果回傳之後才往下跑,就算你這樣寫也是無效
1 2 3 4 5 6 7 8 9 10 11 async function fn ( ) { await arr1.forEach (async (item) => { const res = await ajax (true , item, 10 ) console .log (res); }); await arr2.forEach (async (item) => { const res = await ajax (true , item, 10 ) console .log (res); }); }fn ();
至於為什麼呢?我們可以拉到 MDN 底下有一段關於 forEach 在舊版本 polyfill 加入的方式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 Array .prototype .forEach = function (callback ) { var T, k; if (this == null ) { throw new TypeError ('this is null or not defined' ); } var O = Object (this ); var len = O.length >>> 0 ; if (typeof callback !== 'function' ) { throw new TypeError (callback + ' is not a function' ); } if (arguments .length > 1 ) { T = arguments [1 ]; } k = 0 ; while (k < len) { var kValue; if (k in O) { kValue = O[k]; callback.call (T, kValue, k, O); } k++; } };
但是上面那一段確實很難懂,所以我傾向你把它想像成以下這樣,會比較好懂
1 2 3 4 5 Array .prototype .forEach = function (callback ) { for (let i = 0 ; i < this .length ; i++) { callback (this [i], i, this ); } }
我們可以看到不論是 MDN 所提供的語法,還是底下簡化的版本,通通都沒有 await,而且我們是將東西丟到原型內再去 callback 的。
那麼該怎麼解決呢?讓我們往下看。
asyncForEach 其中一種方式就是自己包裝一個 async、await 的 forEach,然後掛在原型下:
1 2 3 4 5 Array .prototype .asyncForEach = function (callback ) { for (let i = 0 ; i < this .length ; i++) { await callback (this [i], i, this ); } }
接下來讓我們看一下搭配上面修改之後的結果會是怎樣:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 var arr1 = [1 , 3 , 5 , 7 , 9 ];var arr2 = [2 , 4 , 6 , 8 , 10 ];function ajax (status, data, max ) { return new Promise ((resolve, reject ) => { const time = Math .floor (Math .random () * max); setTimeout (() => { if (status) { resolve (`成功:${data} ` ); } else { reject (`失敗:${data} ` ); } }, time); }) }async function fn ( ) { await arr1.asyncForEach (async (item) => { const res = await ajax (true , item, 10 ) console .log (res); }); await arr2.asyncForEach (async (item) => { const res = await ajax (true , item, 10 ) console .log (res); }); }fn ();
你可以發現結果不管怎麼樣都是 1…3…5…7…9 依照順序執行後才往下
當然如果你不想要寫在原型下的話就只需要寫成函式來使用也可以:
1 2 3 4 5 async function asyncForEach (callback ) { await for (let i = 0 ; i < this .length ; i++) { await callback (this [i], i, this ); } }
如果你覺得上面寫法太長的話,還可以將 asyncForEach 改成以下會更簡短:
1 2 3 Array .prototype .asyncForEach = async function (fn ) { for (let t of this ) await fn (t) }
for/for…in/for..of 當然你也可以乾脆就使用 for/for...in/for..of
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 var arr1 = [1 , 3 , 5 , 7 , 9 ];function ajax (status, data, max ) { return new Promise ((resolve, reject ) => { const time = Math .floor (Math .random () * max); setTimeout (() => { if (status) { resolve (`成功:${data} ` ); } else { reject (`失敗:${data} ` ); } }, time); }) }async function fn ( ) { for (let i = 0 ; i < arr1.length ; i++) { const res = await ajax (true , arr1[i], 10 ); console .log (res); } for (let i in arr1) { const res = await ajax (true , arr1[i], 10 ); console .log (res); } for (let item of arr1) { const res = await ajax (true , item, 10 ); console .log (res); } }fn ();
那麼以上就是解決的方式囉~
參考文獻
整理這些技術筆記真的很花時間,如果你願意 關閉 Adblock 支持我,我會把這份感謝轉換成更多「踩坑轉避坑」的內容給你!ヽ(・∀・)ノ
Advertisement