Promise.all的上限是多少?
How much is the limit of Promise.all?
最近我在解决大量承诺时遇到以下错误:
RangeError: Too many elements passed to Promise.all
我找不到任何关于 MDN 或 ECMA-262 限制的信息。
我可以说 什么 限制似乎是什么,虽然我不能准确指出 为什么 它是这样的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);
结果:传入2097151个元素的数组时报错,但2097150个元素就OK了:
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);
所以,2097150 是极限。这可能与 2097151 是 0x1FFFFF 这一事实有关。
根据 V8/V8 error code TooManyElementsInPromiseAll
of the source code objects Promise
T(TooManyElementsInPromiseAll, "Too many elements passed to Promise.all")
有这个限制。对于 Promise.all 即 C++ PromiseAll we have there is a concept of MaximumFunctionContextSlots
and kPromiseAllResolveElementCapabilitySlot
, here 它是源代码中最有趣的东西:
// 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
};
我希望看到像 here
这样的错误抛出
ThrowTypeError(context, MessageTemplate::TooManyElementsInPromiseAll);
Here 是引发 TooManyElementsInPromiseAll
错误的代码。感谢 Clarence 为我指明了正确的方向!
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();
}
这个limit的校验是here
// Check if we reached the limit.
TNode<Smi> const index = var_index.value();
GotoIf(SmiEqual(index, SmiConstant(PropertyArray::HashField::kMax)),
&too_many_elements);
所以kMax
应该可以解决线索!
从 V8 unit tests,我们看到:
// 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);
});
});
看起来元素的最大数量上限为2^21(= 2097151),这符合其他答案的实际测试运行。
最近我在解决大量承诺时遇到以下错误:
RangeError: Too many elements passed to Promise.all
我找不到任何关于 MDN 或 ECMA-262 限制的信息。
我可以说 什么 限制似乎是什么,虽然我不能准确指出 为什么 它是这样的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);
结果:传入2097151个元素的数组时报错,但2097150个元素就OK了:
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);
所以,2097150 是极限。这可能与 2097151 是 0x1FFFFF 这一事实有关。
根据 V8/V8 error code TooManyElementsInPromiseAll
of the source code objects Promise
T(TooManyElementsInPromiseAll, "Too many elements passed to Promise.all")
有这个限制。对于 Promise.all 即 C++ PromiseAll we have there is a concept of MaximumFunctionContextSlots
and kPromiseAllResolveElementCapabilitySlot
, here 它是源代码中最有趣的东西:
// 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
};
我希望看到像 here
这样的错误抛出ThrowTypeError(context, MessageTemplate::TooManyElementsInPromiseAll);
Here 是引发 TooManyElementsInPromiseAll
错误的代码。感谢 Clarence 为我指明了正确的方向!
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();
}
这个limit的校验是here
// Check if we reached the limit.
TNode<Smi> const index = var_index.value();
GotoIf(SmiEqual(index, SmiConstant(PropertyArray::HashField::kMax)),
&too_many_elements);
所以kMax
应该可以解决线索!
从 V8 unit tests,我们看到:
// 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);
});
});
看起来元素的最大数量上限为2^21(= 2097151),这符合其他答案的实际测试运行。