關於forEach同步非同步的問題

村望老弟發表於2021-11-29

驗證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()
})

測試一下,成功的!

image-20211129100058320

本作品採用《CC 協議》,轉載必須註明作者和本文連結
CunWang@Ch

相關文章