1
  let shouldHavePaid = 0;

  demographicsArray.map((country) => {
    if (country.checked == true) {
      Price.findOne({ country: country._id }).then((priceRes) => {
        if (priceRes) {
          shouldHavePaid = shouldHavePaid + priceRes.priceSMS * country.count;
        } else {
          shouldHavePaid = shouldHavePaid + 0.1 * country.count; //Default price for unlisted countries
        }
      });
    }
  });

  console.log(`Finish: ${shouldHavePaid}`);

I want the console.log at the end to execute after the map, but it fires before the map is finished. I am expecting this output because as far as I know map should be sync and not async. I believe that the request to the DB messes it up? what would you suggest here?

  • 3
    1. Please don't use `.map` for a simple iteration. Use a loop or `.forEach` for that. 2. `.map` *is* a synchronous operation, but you are launching asynchronous ones inside it. You either need to make all async and use `Promise.all` and wait for all of the completion (parallel) or convert to a regular loop and `await` every iteration (sequential). You can also `reduce` to a single sequential operation but I'd probably go with the loop. – VLAZ Jun 29 '20 at 15:21
  • Thanks, and why do you suggest not to use map for simple iterations? For the ability to use await? – The pro coder not really xd Jun 29 '20 at 15:46
  • 1
    [It's the wrong tool](https://stackoverflow.com/questions/56903693/is-performing-a-mapping-operation-without-using-returned-value-an-antipattern/) and it's misleading. Mapping operations are 1:1 transformations. Using it for anything else is sending the wrong message to anybody who would be looking at the code in the future, *including yourself*. `.map` is an idiom in JavaScript and many other languages. There are many other *better* idioms for common array operations. Check my profile information. – VLAZ Jun 29 '20 at 15:51

2 Answers2

5

You can use Promise.all in combination with await:

await Promise.all(demographicsArray.map(async (...)=>{
    if (...) {
        await Prize... 
        if (priceRes) {
            ....
        }
    }
})

console.log(...)

Await can be used in async functions to run the next line only after the promise completes. Promise.all() allows you to wait for a whole array of promises. Marking the callback as async makes it return a promise, that can be used by the Promise.all(). It also allows you to use await inside the function so you don't have to use .then().

mousetail
  • 7,009
  • 4
  • 25
  • 45
0

Well, you are saying it your self pretty much

I am expecting this output because as far as I know map should be sync and not async. I believe that the request to the DB messes it up?

Your Price.findOne(expr) returns a promise, so this part is async, so this is the root of your problem.

For your console.log to come at the end, and without making restructs to your code, just put it after the actuall record if fetched and the data compared!

 let shouldHavePaid = 0;

  demographicsArray.map((country) => {
    if (country.checked == true) {
      Price.findOne({ country: country._id }).then((priceRes) => {
        if (priceRes) {
          shouldHavePaid = shouldHavePaid + priceRes.priceSMS * country.count;
        } else {
          shouldHavePaid = shouldHavePaid + 0.1 * country.count; //Default price for unlisted countries
        }
  console.log(`Finish: ${shouldHavePaid}`);
      });
    }
  });
MKougiouris
  • 2,821
  • 1
  • 16
  • 19