1

I am working on a project that involves updating the entire mongoDB collection everyday. I am making multiple asynchronous calls so I decide to use async/await and try/catch. My code looks like this:

const updateMongoData = async () => {
  try {
    const data = await getData(); //This is the new data that I am using to update Mongo docs
    const countries = await GlobalData.find();
    countries.forEach(async (row) => {
      const res = await Global.updateOne(
        { country: row.country },
        {
          //Use data
          lastUpdated: Date.now(),
        }
      );
    });
  } catch (err) {
    console.log(err);
  }
};

Everything works fine except if I make a syntax error e.g Dated.now() instead of Date.now(). This is will give my an error saying

(node:8248) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). To terminate the node process on unhandled promise rejection, use the CLI flag `--unhandled-rejections=strict` (see https://nodejs.org/api/cli.html#cli_unhandled_rejections_mode). (rejection id: 368)

I tried inserting another try catch instead my forEach and moved the forEach inside and this seems to catch the syntax error. Why does this work? And what is the cleanest way to do this?

GlobalData.find()
  .then(countries => { 
    countries.forEach(async (row) => {
     try{ 
       const res = await Global.updateOne(
        { country: row.country },
        {
          //Use data
          lastUpdated: Date.now(),
        }
      );
     }
     catch(err){
       console.log(err);
     } 
   })
 }).catch(err => console.log(err));
Zabi Babar
  • 354
  • 2
  • 9
  • You [cannot use `forEach` for asynchronous code](https://stackoverflow.com/a/37576787/1048572). – Bergi May 28 '20 at 07:46

1 Answers1

3

Your try catch is unable to catch the ReferenceError thrown by changing Date to Dated because catch requires a call to the implicit async/await Promise chain's reject handler. Errors thrown inside Array.prototype.forEach will not see that handler.

You can think about Array.prototype.forEach as a method that starts off a whole slew of runaway executions; none of which are meant to be awaited from the parent scope

You can use Promise.all and Array.prototype.map to catch the error instead.

const updateMongoData = async () => {
  try {
    const data = await getData(); //This is the new data that I am using to update Mongo docs
    const countries = await GlobalData.find();
    await Promise.all(countries.map(async (row) => {
      const res = await Global.updateOne(
        { country: row.country },
        {
          //Use data
          lastUpdated: Dated.now(),
        }
      );
    }))
  } catch (err) {
    console.log('yo', err);
  }
};

updateMongoData() // > yo ReferenceError: Dated is not defined

Your second example works because the try catch block is sharing the same scope as the Array.prototype.forEach block. If you moved that try catch statement outside, however, it will not see the error.

There is another way using a project I started. Contributions are welcome.

const { pipe, fork, map, tryCatch } = require('rubico')

const updateGlobalByCountry = (country, data) => Global.updateOne(
  { country },
  { lastUpdated: Date.now() }, // <- use data
)

const updateMongoData = tryCatch(
  pipe([
    fork({
      data: getData, // () => data
      countries: GlobalData.find, // () => countries
    }), // () => ({ data, countries })
    ({ data, countries }) => map(
      country => updateGlobalByCountry(country, data)
    )(countries), // ({ data, countries }) => updatedCountries
  ]),
  err => console.log(err),
)
richytong
  • 2,387
  • 1
  • 10
  • 21