驗證forEach
是同步還是非同步
之前遇到過一些人跟我說,
forEeach
是非同步的~不能放非同步程式碼進去執行~我當時其實挺迷惑的,好像當初學習javascript
的時候不是這樣的
有些人在你身上刻上了一道疤~tmd笑嘻嘻的就跑了~留下我一個人承受痛苦~
test.html
<script>
const arr = [1, 2, 3, 4]
console.log('start')
arr.forEach(item => {
console.log(item)
})
console.log('end')
</script>
請不要在說foreach
是一個非同步的了!
forEach
執行非同步程式碼
可能你遇到的情況是
forEach
中執行的都是非同步函式,你想在裡面逐個執行出來!但是不行!比如下面的程式碼
const sleep = (ms) => {
return new Promise((resolve) => {
setTimeout(resolve, ms)
})
}
const arr = [
() => console.log("start"),
() => sleep(1000),
() => console.log(1),
() => sleep(1000),
() => console.log(2),
() => sleep(1000),
() => console.log(3),
() => sleep(1000),
() => console.log("end")
]
arr.forEach(async fn => {
await fn()
})
期待的結果是先列印start
,然後每隔一秒往下執行一次,直到end
,但是你執行會發現,這些console
會一次瞬間執行完成!
那為什麼會這樣呢?我們可以去mdn找到forEach
原始碼
// Production steps of ECMA-262, Edition 5, 15.4.4.18
// Reference: http://es5.github.io/#x15.4.4.18
if (!Array.prototype.forEach) {
Array.prototype.forEach = function(callback, thisArg) {
var T, k;
if (this == null) {
throw new TypeError(' this is null or not defined');
}
// 1. Let O be the result of calling toObject() passing the
// |this| value as the argument.
var O = Object(this);
// 2. Let lenValue be the result of calling the Get() internal
// method of O with the argument "length".
// 3. Let len be toUint32(lenValue).
var len = O.length >>> 0;
// 4. If isCallable(callback) is false, throw a TypeError exception.
// See: http://es5.github.com/#x9.11
if (typeof callback !== "function") {
throw new TypeError(callback + ' is not a function');
}
// 5. If thisArg was supplied, let T be thisArg; else let
// T be undefined.
if (arguments.length > 1) {
T = thisArg;
}
// 6. Let k be 0
k = 0;
// 7. Repeat, while k < len
while (k < len) {
var kValue;
// a. Let Pk be ToString(k).
// This is implicit for LHS operands of the in operator
// b. Let kPresent be the result of calling the HasProperty
// internal method of O with argument Pk.
// This step can be combined with c
// c. If kPresent is true, then
if (k in O) {
// i. Let kValue be the result of calling the Get internal
// method of O with argument Pk.
kValue = O[k];
// ii. Call the Call internal method of callback with T as
// the this value and argument list containing kValue, k, and O.
callback.call(T, kValue, k, O);
}
// d. Increase k by 1.
k++;
}
// 8. return undefined
};
}
關鍵程式碼在這裡
foreach
內部呢是一個while
迴圈,然後再內部去執行回撥函式!你可以看到並沒有對內部非同步進行什麼處理~async fn => { await fn() }
相當於每次迴圈,都只是回撥執行了外層的
fn
,執行就完事了,而沒有對內部的await
做一些操作,其實就是在迴圈中沒有去等待執行await
的結果,所以裡面的非同步sleep,
還是放到非同步佇列去等待同步執行完成後再去執行,也就是先列印再去sleep
,所以沒有sleep的效果
如果直接用for
迴圈去處理,那麼就是針對每一次迴圈去做了一個非同步的await
,
const sleep = (ms) => {
return new Promise((resolve) => {
setTimeout(resolve, ms)
})
}
const arr = [
() => console.log("start"),
() => sleep(1000),
() => console.log(1),
() => sleep(1000),
() => console.log(2),
() => sleep(1000),
() => console.log(3),
() => sleep(1000),
() => console.log("end")
]
async function run(arr) {
for (let i = 0; i < arr.length; i++) {
await arr[i]()
}
}
run(arr)
寫一個自己的forEach
其實就是把
forEach
內部實現改成for
迴圈,讓每次迴圈都能捕捉執行await
,而不是外層的async
函式
Array.prototype.asyncForEach = async function (callback, args) {
const _arr = this, // 因為呼叫的方式 [1,2,3].asyncForEach this指向陣列
isArray = Array.isArray(_arr), //判斷呼叫者是不是陣列
_args = args ? Object(args) : window //物件化
if (!isArray) {
throw new TypeError("the caller must be a array type!")
}
for (let i = 0; i < _arr.length; i++) {
await callback.call(_args,_arr[i])
}
}
const sleep = (ms) => {
return new Promise((resolve) => {
setTimeout(resolve, ms)
})
}
const arr = [
() => console.log("start"),
() => sleep(1000),
() => console.log(1),
() => sleep(1000),
() => console.log(2),
() => sleep(1000),
() => console.log(3),
() => sleep(1000),
() => console.log("end")
]
arr.asyncForEach(async(fn)=>{
await fn()
})
測試一下,成功的!
本作品採用《CC 協議》,轉載必須註明作者和本文連結