Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How much is the limit of Promise.all?

Recently i got the following error while resolving a VERY large number of promises:

RangeError: Too many elements passed to Promise.all

I couldn't find any information regarding limits on MDN or ECMA-262.

like image 713
Nick Avatar asked Apr 18 '19 21:04

Nick


People also ask

Does promise all improve performance?

Notice that it's not await that resolves them. Promise. all does not improve performance. It's the "not waiting for the first promise before starting the second task" that improves performance (if done correctly).

How does promise all work?

all() The Promise. all() method takes an iterable of promises as an input, and returns a single Promise that resolves to an array of the results of the input promises. This returned promise will fulfill when all of the input's promises have fulfilled, or if the input iterable contains no promises.

Does promise all maintain order?

Here, Promise. all() method is the order of the maintained promises. The first promise in the array will get resolved to the first element of the output array, the second promise will be a second element in the output array and so on.

What if one promise fails in promise all?

Rejection of Promise. Promise. all() shows fail-fast behavior, that is, Promise. all() asynchronously rejects with the value of the promise that rejected, if any of the passed-in elements are rejected.


3 Answers

According to the V8/V8 error code TooManyElementsInPromiseAll of the source code objects Promise

  T(TooManyElementsInPromiseAll, "Too many elements passed to Promise.all")

there is this limit. For the Promise.all i.e. the C++ PromiseAll we have there is a concept of MaximumFunctionContextSlots and kPromiseAllResolveElementCapabilitySlot, here it is the most interesting stuff from the source code:

// 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
  };

I would expect to see a error throw like here

ThrowTypeError(context, MessageTemplate::TooManyElementsInPromiseAll);

Here it is the code that raise the TooManyElementsInPromiseAll error. Thank to Clarence that pointed me in the right direction!

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();
  }

The check of this limit it is here

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

so the kMax should solve the clue!

like image 113
loretoparisi Avatar answered Nov 15 '22 21:11

loretoparisi


From the V8 unit tests, we see this:

// 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);
  });
});

It looks like the maximum number of elements is capped at 2^21 (= 2097151), which is in line the practical tests that the other answers ran.

like image 43
Clarence Leung Avatar answered Nov 15 '22 22:11

Clarence Leung


I can say what the limit appears to be, though I can't pinpoint why exactly it is the way it is in the V8 source code. I wrote the following code (only run it if you're bored, it'll take a while):

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);

The result: an error is thrown when an array with 2097151 elements is passed, but 2097150 elements is 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);

So, 2097150 is the limit. It likely has something to do with the fact that 2097151 is 0x1FFFFF.

like image 41
CertainPerformance Avatar answered Nov 15 '22 22:11

CertainPerformance