6

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?

watery
  • 5,026
  • 9
  • 52
  • 92

2 Answers2

3

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);
Suren Srapyan
  • 66,568
  • 14
  • 114
  • 112
  • Both are good answers, I chose the other one because it more clearly mentions the comma operator is not involved. – watery Sep 05 '18 at 13:31
1

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
CertainPerformance
  • 356,069
  • 52
  • 309
  • 320
  • So... it all boils down to js [operator precedence](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Operator_Precedence) – watery Sep 05 '18 at 13:30