About forEach synchronization and asynchrony

About forEach synchronization and asynchrony

Verify whether forEach is synchronous or asynchronous

Some people have told me that forEeach is asynchronous ~ asynchronous code can't be put into execution ~ I was confused at that time, as if it wasn't the case when I first learned javascript

Some people engraved a scar on you ~ tmd smiled and ran away ~ leaving me to bear the pain alone ~

test.html

<script>
    const arr = [1, 2, 3, 4]
    console.log('start')
    arr.forEach(item => {
        console.log(item)
    })
    console.log('end')
</script>

Please don't say foreach is asynchronous!

forEach executes asynchronous code

You may encounter the situation that forEach executes asynchronous functions, and you want to execute them one by one! But no! For example, the following code

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

The expected result is to print start first, and then execute it every second until end. However, you will find that these console s will be executed in an instant!

Then why? We can go to mdn to find the forEach source code

// 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
  };
}

The key code is here

The inside of foreach is a while loop, and then execute the callback function internally! You can see that there is no processing for internal asynchrony

async fn => {
    await fn()
}

It is equivalent to that in each cycle, only the callback executes the fn of the outer layer, and the execution is finished without doing some operations on the internal await. In fact, it does not wait for the result of executing await in the cycle, so the asynchronous sleep in the loop is still placed in the asynchronous queue to wait for the completion of simultaneous execution, that is, print first and then sleep, so there is no sleep effect

If the for loop is directly used for processing, an asynchronous await is made for each loop,

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)

Write your own forEach

In fact, the internal implementation of forEach is changed into a for loop, so that each loop can capture and execute await, rather than the outer async function

Array.prototype.asyncForEach = async function (callback, args) {
    const _arr = this, // Because the calling method [1,2,3].asyncForEach this points to the array
        isArray = Array.isArray(_arr), //Determine whether the caller is an array
        _args = args ? Object(args) : window //Objectification
    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()
})

Test it, successful!

Posted by redking on Sun, 28 Nov 2021 18:40:01 -0800