关于forEach同步异步的问题

关于forEach同步异步的问题

验证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
讨论数量: 2

说到底就是,循环是同步的,方法是异步的,循环十次调用十个方法而已,哪有那么玄乎

2年前 评论

就是这玩意用看源码么,foreach 写法就是一个函数, nodejs 的所有基础函数都是异步的 异步函数调用,能同步才怪

1年前 评论

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!