1

I have an advanced Mongo query.

Where I try to count distinct values for a number of fields.

First I have to iterate over three values, which are entries n my Mongoose schema, and then I have to count each distinct value for each of the respective field, and return them as a string.


const mappedStuff = this.featureFields.map(async field => {
      return new Promise(async (resolve, reject) => {
        const distinctValues = await this.gameModel.distinct(field);
        return distinctValues.map(async entry => {
          console.log(
            `${field}, ${entry},  ${await this.gameModel.count({
              [field]: entry,
            })}`,
          ); //Logs out correct value 
          resolve(
            `${field}, ${entry},  ${await this.gameModel.count({
              [field]: entry,
            })}` as string, //resolves instantly and does not return the correct value
          );
        });
      });
    });
    console.log(Promise.all(mappedStuff));
    return Promise.all(mappedStuff);

The console.log works fine, and I just want to return that value, I have tried pushing it to a list outside, but does not work, because I have an await inside of the string.

Therefore I tried wrapping the entire thing in a promise, but this does not solve the issue either. Does anybody have a solution

Kristoffer Tølbøll
  • 3,157
  • 5
  • 34
  • 69
  • What is your desired output? `Promise.all()` doesn't wait for all promises deeply without using `Promise.all` again in the second layer of promises. Also, creating a new promise in an async function is redundant. – evelynhathaway Jun 03 '21 at 21:08

1 Answers1

4

You did the right thing wrapping the outer array with Promise.all(), but you forgot to do the same thing with the inner array, where you have distinctValues.map(//.... Additionally, the Promise directly inside the outer map seems superfluous.

const mappedStuff = this.featureFieldsMap.map(async (field) => {
  const distinctValues = await this.gameModel.distinct(field);
  return await Promise.all(distinctValues.map(async (entry) => {
    // make life easier by not interpolating the asynchronous result.
    const count = await this.gameModel.count({ [field]: entry });
    return `${field}, ${entry}, ${count}`;
  }));
});
return Promise.all(mappedStuff);
Daniel Gimenez
  • 18,530
  • 3
  • 50
  • 70
  • 3
    +1, though you can always return a Promise from within an `async` function (and an `async` function is going to return a Promise anyway) so you can avoid `return await` in favor of `return`. The Promise implementation does the unwrapping. – Jeff Bowman Jun 03 '21 at 21:12
  • 1
    It was anti-pattern to return a Promise in a async context, but I got really frustrated with this! I should have though of mapping the `distinctValues` and wrapping the result in `Promise.all()` inside of the map function. Thank you @Daniel Gimenez – Kristoffer Tølbøll Jun 03 '21 at 21:16
  • 1
    @JeffBowman, so this makes the `await` before the return redundant. Similar to this? https://stackoverflow.com/questions/38708550/difference-between-return-await-promise-and-return-promise – Kristoffer Tølbøll Jun 03 '21 at 21:19
  • 1
    @Ating Correct. The outer map is an async lambda, and there's no try/catch, so `return await Promise.all(...)` works but is no better than `return Promise.all(...)`. I agree it's an antipattern to _create an explicit Promise_ in an async context, so I agree with Daniel in removing it, but directly returning a Promise from a helper like `Promise.all` is perfectly reasonable. – Jeff Bowman Jun 03 '21 at 21:22