Promise.all的限额是多少?

Nic*_*anc 6 node.js es6-promise

最近,我在解决大量承诺时遇到了以下错误:

RangeError:传递给Promise.all的元素过多

我找不到有关MDN或ECMA-262限制的任何信息。

lor*_*isi 6

根据源代码对象的V8 / V8错误代码承诺TooManyElementsInPromiseAll

  T(TooManyElementsInPromiseAll, "Too many elements passed to Promise.all")
Run Code Online (Sandbox Code Playgroud)

有这个限制。对于Promise.all即C ++ PromiseAll我们有一个概念MaximumFunctionContextSlotskPromiseAllResolveElementCapabilitySlot这里是从源代码中最有趣的东西:

// TODO(bmeurer): Move this to a proper context map in contexts.h?
  // Similar to the AwaitContext that we introduced for await closures.
  enum PromiseAllResolveElementContextSlots {
    // Remaining elements count
    kPromiseAllResolveElementRemainingSlot = Context::MIN_CONTEXT_SLOTS,

    // Promise capability from Promise.all
    kPromiseAllResolveElementCapabilitySlot,

    // Values array from Promise.all
    kPromiseAllResolveElementValuesArraySlot,

    kPromiseAllResolveElementLength
  };
Run Code Online (Sandbox Code Playgroud)

我希望看到一个错误抛出像这里

ThrowTypeError(context, MessageTemplate::TooManyElementsInPromiseAll);
Run Code Online (Sandbox Code Playgroud)

是引发TooManyElementsInPromiseAll错误的代码。感谢克拉伦斯,这为我指明了正确的方向!

BIND(&too_many_elements);
  {
    // If there are too many elements (currently more than 2**21-1), raise a
    // RangeError here (which is caught directly and turned into a rejection)
    // of the resulting promise. We could gracefully handle this case as well
    // and support more than this number of elements by going to a separate
    // function and pass the larger indices via a separate context, but it
    // doesn't seem likely that we need this, and it's unclear how the rest
    // of the system deals with 2**21 live Promises anyways.
    Node* const result =
        CallRuntime(Runtime::kThrowRangeError, native_context,
                    SmiConstant(MessageTemplate::kTooManyElementsInPromiseAll));
    GotoIfException(result, &close_iterator, var_exception);
    Unreachable();
  }
Run Code Online (Sandbox Code Playgroud)

此限制的检查在这里

// Check if we reached the limit.
    TNode<Smi> const index = var_index.value();
    GotoIf(SmiEqual(index, SmiConstant(PropertyArray::HashField::kMax)),
           &too_many_elements);
Run Code Online (Sandbox Code Playgroud)

所以kMax应该解决的线索!

  • 另一个难题在于:[contexts.h](https://github.com/v8/v8/blob/4b9b23521e6fd42373ebbcb20ebe03bf445494f9/src/contexts.h)...在这里我们可以看到`NATIVE_CONTEXT_INTRINSIC_FUNCTIONS`(* (堆分配的)激活上下文),包括“ MIN_CONTEXT_SLOTS”,这是“ PromiseAllResolveElementContextSlots”枚举的基本值 (2认同)

Cer*_*nce 5

我可以说出限制似乎是什么,但我无法准确指出为什么它在 V8 源代码中是这样的。我编写了以下代码(仅在无聊时运行它,这需要一段时间):

if (!window.chrome) {
  throw new Error('Only try this in Chromium');
}

// somewhere between 1e6 and 1e7
let testAmountStart = 5.5e6;
let changeBy = 4.5e6;
const delay = ms => new Promise(resolve => setTimeout(resolve, ms));
const next = (testAmount) => {
  changeBy = Math.ceil(changeBy / 2);
  if (changeBy === 1) {
    console.log('done');
    return;
  }
  console.log('start ' + testAmount);
  const proms = new Array(testAmount).fill(undefined);
  Promise.all(proms)
    .then(() => {
      // make this loop not fully blocking
      // give time for garbage collection
      console.log(testAmount + ': OK');
      delay(100).then(() => next(testAmount + changeBy));
    }).catch((e) => {
      console.log(testAmount + ': ' + e.message);
      delay(100).then(() => next(testAmount - changeBy));
    });
};
next(testAmountStart);
Run Code Online (Sandbox Code Playgroud)

结果:传递2097151个元素的数组时抛出错误,但2097150个元素就可以了:

const tryProms = length => {
  const proms = new Array(length).fill(undefined);
  Promise.all(proms)
      .then(() => {
      console.log('ok ' + length);
    }).catch(() => {
      console.log('error ' + length);
    });
};
tryProms(2097150);
tryProms(2097151);
Run Code Online (Sandbox Code Playgroud)

所以,2097150 是极限。这可能与 2097151 是 0x1FFFFF 有关。

  • 是的,就是这个错误,我添加了错误消息的日志 (3认同)

Cla*_*ung 5

V8 单元测试中,我们看到:

// Make sure we properly throw a RangeError when overflowing the maximum
// number of elements for Promise.all, which is capped at 2^21 bits right
// now, since we store the indices as identity hash on the resolve element
// closures.
const a = new Array(2 ** 21 - 1);
const p = Promise.resolve(1);
for (let i = 0; i < a.length; ++i) a[i] = p;
testAsync(assert => {
  assert.plan(1);
  Promise.all(a).then(assert.unreachable, reason => {
    assert.equals(true, reason instanceof RangeError);
  });
});
Run Code Online (Sandbox Code Playgroud)

看起来元素的最大数量上限为 2^21 (= 2097151),这与其他答案运行的实际测试一致。

  • 接得好!我仍在寻找引发 `TooManyElementsInPromiseAll` 的地方,但我没有找到它! (2认同)
  • 我的后续问题是:将其限制在 2^21 的原因可能是什么? (2认同)