在 Promises 上链接太多 .then 对性能有什么影响(如果有的话)?

What are the performance implications, if any, of chaining too many `.then`s on Promises?

我有一个 reduce 的实现,它可能会将许多 .then 处理程序链接在一起。

const reduceIterable = (fn, x0, x) => {
  const iter = x[Symbol.iterator].call(x)
  let cursor = iter.next()
  if (cursor.done) {
    throw new TypeError('reduce(...)(x); x cannot be empty')
  }
  let y = !isUndefined(x0) ? fn(x0, cursor.value) : (() => {
    const x0 = cursor.value
    cursor = iter.next()
    return cursor.done ? x0 : fn(x0, cursor.value)
  })()
  cursor = iter.next()
  while (!cursor.done) {
    const { value } = cursor
    y = isPromise(y) ? y.then(res => fn(res, value)) : fn(y, value)
    cursor = iter.next()
  }
  return y
}

// you would use reduce like reduce(add, 0)([1, 2, 3]) // => 6
const reduce = (fn, x0) => {
  if (!isFunction(fn)) {
    throw new TypeError('reduce(x, y); x is not a function')
  }
  return x => {
    if (isIterable(x)) return reduceIterable(fn, x0, x)
    if (isAsyncIterable(x)) return reduceAsyncIterable(fn, x0, x)
    if (is(Object)(x)) return reduceObject(fn, x0, x)
    throw new TypeError('reduce(...)(x); x invalid')
  }
}

具体来说,我正在查看 y.then(res => fn(res, value))。因为这个 issue,这让我彻夜难眠。我知道从那时起 Promises 已经走了很长一段路,但这从未得到解决。我真的很想知道我是否可以像这样使用 Promise API,或者我是否需要做一些更难的事情。

What are the performance implications, if any, of chaining too many .thens on Promises?

如果您比较一个 .reduce() 循环,在该循环中您将许多 .then() 链接在一起以对异步操作进行排序,并将其与异步函数内的 await 循环进行比较,其中您在开始下一个操作之前完成一个操作,主要区别在于峰值内存使用量。将许多 .then() 链接在一起将立即在内存中拥有所有 promise 对象的完整链。如果你做一个 await 循环,你一次只有一个活动的承诺。

现在,promises 不是大对象,所以即使您的链有数千个元素,它可能仍然没有 material 差异。但是,如果您想最大程度地减少峰值内存使用量,await 循环将使峰值内存使用量保持在较低水平。

至于纯粹的执行速度,故事一如既往。如果您真的关心执行速度,则必须使用 .reduce() 循环编写一个具有代表性的测试程序,并使用 await 循环编写一个生成等效输出和排序的程序,并对两者进行基准测试。性能非常依赖于特定情况,如果您真的想知道哪个更快,则必须进行测量。理论化往往是错误的,因为我们的直觉并不总是知道真正的瓶颈是什么。你得测量一下。

仅供参考,async/await 在最近的几个 nodejs 版本中已经加快了很多。

同时创建与可迭代元素一样多的 promise,这可能对通用函数不利。它完全分解为无限迭代。

您可以在检测到 promise 时跳转到异步函数:

const reduceAwait = async (fn, initial, iterable) => {
  let m = await initial

  for (const n of iterable) {
    m = await fn(m, n)
  }

  return m
}

const reduceIterable = (fn, initial, iterable) => {
  const iterator = iterable[Symbol.iterator]()
  let m = initial

  if (initial === undefined) {
    const first = iter.next()

    if (first.done) {
      throw new TypeError('reduce of empty iterable with no initial value')
    }

    m = first.value
  }

  for (const n of iterator) {
    m = fn(m, n)

    if (isPromise(m)) {
      return reduceAwait(fn, m, iterator)
    }
  }

  return m
}

m = await fn(m, n)也可以变成

m = fn(m, n)

if (isPromise(m)) {
  m = await m
}

如果您希望混合输入的微任务最少。