Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How does Promise.all with comma operator between array and object work?

I've come across this piece of code:

const results = await Promise.all(
                          [ Model1.find({}), Model2.find({}) ],
                          Model3.find({})
                      ),
        v1 = results[0],
        v2 = results[1],
        v3 = results[2]

which is invoking all() with an array and a single object — `Model* are Mongoose models.

This is an easily fixed bug, but I'd like to understand how it is giving the resulting values, which are:

  • v1 holds all the documents corresponding to Model1
  • v2 holds all the documents corresponding to Model2
  • v3 is undefined

As explained in this answer on the comma operator, I expected only the Model3.find({}) promise to actually return data in results, as the comma operator should evaluate the first operand but return its second operand (to Promise.all()). But it's instead the other way around: results[0] and results[1] both contain data, while results[2] (and thus v3) is undefined.

What am I missing?

like image 671
watery Avatar asked Aug 29 '18 09:08

watery


People also ask

How does promise all works?

The Promise.all() method takes an iterable of promises as input and returns a single Promise . This returned promise fulfills when all of the input's promises fulfill (including when an empty iterable is passed), with an array of the fulfillment values.

How does a comma operator work?

The comma operator ( , ) evaluates each of its operands (from left to right) and returns the value of the last operand. This lets you create a compound expression in which multiple expressions are evaluated, with the compound expression's final value being the value of the rightmost of its member expressions.

How does comma operator work in Python?

On the left-hand side of an assignment, the comma indicates that sequence unpacking should be performed according to the rules you quoted: a will be assigned the first element of the tuple, b the second.

How do you handle an array of Promises?

Given an array of Promises, we have to run that in a series. To do this task, we can use then(), to run the next promise, after completion of a promise. Approach: The then() method returns a Promise, which helps us to chain promises/methods.


2 Answers

Your function call

Promise.all([ Model1.find({}), Model2.find({}) ], Model3.find({}))
//          ^-     First parameter            -^  ^- second    -^

You have passed only two promises to the Promise.all function -

  1. first two as an array - [Model1.find({}), Model2.find({})].
  2. Model3 as the second parameter - Model3.find({}).

Model3 is passed but it is ignored by function - function takes only one parameter - the first one which must be an iterable

Syntax

Promise.all(iterable);


Promise.all just runs those promises which are defined in the first parameter and you get only results for Model1.find({}) and Model2.find({}).

You think that results[2] is the result of Model3.find({}), but not. In this case it is an array which contains 2 items - results of two promises which are passed via array/iterable. When you want to access an index of the array which is greater then it's length - 1, you will get undefined.

Function parameters

In JavaScript you can pass to function how many arguments you want, but parameters will be assigned from left to right and those arguments which does not fit in the range of the parameters will be just ignored.

Look at the below example. I have passed 5 arguments to the function, but my function takes only first two, so the 3, 4, 5 are just ignored. You have this case.

function foo(a, b) {
   console.log(a, b);
}

foo(1, 2, 3, 4, 5);
like image 167
Suren Srapyan Avatar answered Sep 28 '22 08:09

Suren Srapyan


That's a parameter list rather than a single expression, so the comma operator does not apply - the array

[Model1.find({}), Model2.find({})]

is evaluated as the first argument passed to Promise.all, and the second argument is

Model3.find({})

But Promise.all only accepts one parameter, an iterable; the second parameter of Model3 is just ignored, and the result is just [Model1.find({}), Model2.find({})] mapped to their resolve values. Since the passed array only has two values, the Promise.all resolves to an array which also has only two values (index [2] is undefined).

If you had enclosed the Promise.all call in another set of parentheses:

const results = await Promise.all((
  [ Model1.find({}), Model2.find({}) ],
  Model3.find({})
))

then you would be invoking the comma operator, because everything inside the second set of parentheses would get evaluated as a single expression while the interpreter attempts to come up with a value for the first (and only) parameter to be passed to the Promise.all. (But Promise.all accepts an iterable, not a Promise, so the evaluation of the comma operator:

await Promise.all((
  Model3.find({})
))

would then result in an error:

TypeError: undefined is not a function
like image 33
CertainPerformance Avatar answered Sep 28 '22 06:09

CertainPerformance