javascript - for 循环中的 Promise 是如何工作的?

标签 javascript node.js arrays reactjs

在我的程序源代码中我有以下函数(Promise 并发限制函数,类似于 pLimit):

async function promiseMapLimit(
  array,
  poolLimit,
  iteratorFn,
) {
  const ret = [];
  const executing = [];
  for (const item of array) {
    const p = Promise.resolve().then(() => iteratorFn(item, array));
    ret.push(p);

    if (poolLimit <= array.length) {
      const e = p.then(() => executing.splice(executing.indexOf(e), 1));
      executing.push(e);
      if (executing.length >= poolLimit) {
        await Promise.race(executing);
      }
    }
  }

  return Promise.all(ret);
}
它工作正常,所以如果我传递给它一个数字数组 [1..99] 并尝试将它乘以 2,它将给出正确的输出 [0..198]。
const testArray = Array.from(Array(100).keys());

promiseMapLimit(testArray, 20, async (value) => value * 2).then((result) =>
  console.log(result)
);
代码示例 - js playground .
但是我无法理解它的逻辑,在我注意到的调试过程中,它以 20 个为一组添加了 Promise,并且只有在这之后才会更进一步:
enter image description here
例如,这个代码块:
  for (const item of array) {
    const p = Promise.resolve().then(() => iteratorFn(item, array));
    ret.push(p);
将迭代数组的 20 项以上( 为什么不是全部 100 项???)
同样在这里:
if (poolLimit <= array.length) {
      const e = p.then(() => executing.splice(executing.indexOf(e), 1));
      executing.push(e);
它只会将 20 项添加到 executing数组,并且只有在 if (executing.length >= poolLimit) 内的那一步之后代码块。
非常感谢您解释此功能的工作原理。

最佳答案

非常有趣的问题!我认为这里代码的重要部分是Promise.race(...)只要其中一个 promise 解决,它就会解决。
我添加了一个 sleep使用随机因子(最多 6 秒)来更好地可视化其工作方式。
预期的功能是:我们总是希望并行执行 20 个 Promise,一旦一个完成,队列中的下一个就会执行。
在视觉上,这看起来像这样,对于 3 和 10 个 promise 的限制 - 在下面的示例中,您可以注意到在每个时刻都有 3 个有效的 promise (除了它们结束时):

PromiseID  | Start                 End |
0          [====]
1          [==]
2          [======]
3             [==========]
4               [====]
5                 [================]
6                    [==]
7                       [====]
8                        [======]
9                            [========]
创建随机延迟的代码如下:

// Create the utility sleep function
const sleep = x => new Promise(res => setTimeout(res, x))

async function promiseMapLimit(array, poolLimit, iteratorFn) {
  const ret = [];
  const executing = [];
  for (const item of array) {
    const p = Promise.resolve().then(() => iteratorFn(item, array));
    ret.push(p);

    console.log(ret.length)
    if (poolLimit <= array.length) {
      const e = p.then(() => executing.splice(executing.indexOf(e), 1));
      executing.push(e);
      if (executing.length >= poolLimit) {
        console.log(`Running batch of ${executing.length} promises.`);
        await Promise.race(executing);
        // As ssoon one of the promise finishes, we continue the loop.
        console.log("Resolved one promise.")
      }
    }
  }

  return Promise.all(ret);
}

const testArray = Array.from(Array(100).keys());

promiseMapLimit(testArray, 20, async (value) => {
  // Log
  console.log(`Computing iterator fn for ${value}`)
  await sleep(3000 + Math.random() * 3000);
  return value * 2
}).then((result) =>
  console.log(result)
);

will iterate over 20 items of an array (why not all 100???)


在开始时,就像在图表中一样,它不会迭代所有 100 个项目,而是前 20 个项目,然后循环被 await Promise.race(...) 暂停(因为 executing.length >= poolLimit 将在迭代 20 个项目后为真)。
一旦一个 promise 完成,它就会从 executing 中删除。数组由 executing.splice(executing.indexOf(e), 1) .
我认为当有延迟( await sleep(...) )时事情会变得更加清晰,以便我们可以模拟真正的异步操作(例如数据库请求等)。
如果还有其他不清楚的地方,请告诉我。

关于javascript - for 循环中的 Promise 是如何工作的?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/70124591/

相关文章:

javascript - IE 和 jQuery Masonry 不同意,(无法在初始化之前调用 Masonry 上的方法)

node.js - 向端点结果添加分页光标和附加功能

javascript - 如何将缓冲区数组转换为十六进制?

javascript - 使用kineticJS和Clip功能可拖动 Canvas

javascript - 单击按钮时使用 Jquery 创建隐藏元素

javascript - 如何将 --harmony Node 标志添加到 grunt-express

javascript - 如何停止 Node js-winston库中出现audit.json文件

java - 将 ArrayList 作为参数传递

Javascript |从对象数组中删除不需要的数据

javascript - Elasticsearch 数据表