Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Handling Errors (Rejections) in async/await inside Array#map

Node 8.1.2, I have a structure where one file is calling another file's function in a map. In a real example I would use Promise.all on the map but that's not the question here. Here is the structure:

A.js:

const { b } = require('./B')

function expressStuff (req, res, next) {
  things.map(thing => {
    return b(thing)
  }))

  return res.status(200).json(...)
}

B.js:

// Thing -> Promise<Object>
function b (thing) {
  return ThingModel.update(...) // this returns a Promise but FAILS and throws an errror
}

module.exports = { b }

OK. So in function b I try to get some async data (from a database). It fails and throws an Uncaught Promise Rejection.

How to make deal with it?

I tried multiple solutions:

A1.js:

const { b } = require('./B')

function expressStuff (req, res, next) {
  things.map(thing => {
    try {
      return b(thing)
    } catch (err) {
      return next(err)
    }
  }))

  return res.status(200).json(...)
}

But that is still uncaught.

A2.js:

const { b } = require('./B')

function expressStuff (req, res, next) {

  try {
    things.map(thing => {
      return b(thing)
    }))
  } catch (err) {
    return next(err)
  }

  return res.status(200).json(...)
}

Still unhandled. I tried using Promise.all, I tried double try-catch blocks (since I thought the one inside map might be returning next from the to the map result and not actually from expressStuff function. Still nothing.

The closes I got to the answer was handling the error but then code wouldn't wait for it to be thrown and both res.status() and next would work resulting in race conditions and cannot set headers after they are sent errors.

All I want to do is for the function b to throw an error but catch it in the expressStuff so I can rethrow custom UnprocessableEntityError and pass it to next. It seems like error from file B is not bubbling up to the map where it is called.

How do I do it?

EDIT:

The only way I can make this rejection handled is try-catching it in the B.js. But if I try to rethrow an error/return it - nothing. Error is swallowed. If I try to console.log it - it will be logged though.

DETAILS:

Thanks to marked answer I refactored my actual code and made it to work perfectly.

function expressStuff (res, req, next) {
  try {
    await Promise.all(things.map(async thing => {
      if (ifSomething()) {
        await b(thing)
      }
    }))
  } catch (err) {
    return next(new MyCustomError('My Custom Error Message'))
  }

  return res.status(200).json(...)
}
like image 454
Tomasz Gałkowski Avatar asked Jul 11 '17 07:07

Tomasz Gałkowski


1 Answers

Handling rejections with try/catch works only in async functions when you await the promise - which you haven't attempted yet.

You could do either

async function expressStuff (req, res, next) {
  var results;
  try {
    results = await Promise.all(things.map(b)); // throws when any of the promises reject
  } catch (err) {
    return next(err) // handle error
  }
  return res.status(200).json(...)
}

or (like Wait until all ES6 promises complete, even rejected promises)

function expressStuff (req, res, next) {
  const resultPromises = things.map(async (thing) => {
    try {
      return await b(thing); // throws when the promise for this particular thing rejects
    } catch (err) {
      return defaultValue; // handle error - don't call `next` here
    }
  });
  …
  return res.status(200).json(...)
}
like image 119
Bergi Avatar answered Oct 19 '22 05:10

Bergi