当forEach 遇到sync/await, 我蚌埠住了

175 阅读1分钟

背景

最近遇到一个async/await 在forEach下不是串行执行的问题, 代码如下,一开始很自信是一秒输出一个(串行执行),依次输出1,2,3, 结果现实狠狠的打脸(好疼), 结果是1秒后输出1,2,3(并行执行)。 一脸懵逼.

const waitOneSecond = (i) => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve(i)
    }, 1000)
  })
}

[1, 2, 3].forEach(async (i) => {
  const res = await waitOneSecond(i)
  console.log(res)
})

forEach中的async/await为什么不是串行执行

既然 async/await 在for of遍历中是能串行执行的,那么问题肯定是出在forEach, 先看看forEach的实现方式, 在MDN上找到 forEach的polyfill实现方式。

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');
    }
    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 = thisArg;
    }
    k = 0;
    // 重点是使用while循环
    while (k < len) {
      var kValue;
      if (k in O) {
        kValue = O[k];
        callback.call(T, kValue, k, O);
      }
      k++;
    }
  };
}

简化后

  while (k < len) {
      callback.call(T, O[k], k, O);
      k++;
  }

forEach的第一个参数是回调函数,我们可以看到forEach内部使用 了while循环执行了回调函数, 所以最后会导致回调函数是并行执行的。

如果对于上述还是不理解为什么forEach中的sync/await是并行执行的,我们可以这样理解对于forEach来说, while循环会后展开成三个独立的async, await。独立的async下的await是并行执行的.

(async () => {
  const res = await waitOneSecond(1)
  console.log(res)
})();
(async () => {
  const res = await waitOneSecond(2)
  console.log(res)
})();
(async () => {
  const res = await waitOneSecond(3)
  console.log(res)
})();

只有async下对应的全部await才是串行执行的

 (async () => {
  const res1 = await waitOneSecond(1)
  console.log(res1)
  const res2 = await waitOneSecond(2)
  console.log(res2)
  const res3 = await waitOneSecond(3)
  console.log(res3)
})();

解决办法

1.对forEach加上 async/await(改原始的函数不是一种好的方式)

 Array.prototype.forEach = async function(callback, thisArg) {
    while (k < len) {]
      // await回调函数就可以串行执行了
      await callback.call(T, O[k], k, O);
      k++;
    }
  }
  1. 使用for of 代替 forEach(推荐)。这样就能保证async/await是个串行执行的了。