8

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(...)
}

1 Answers1

7

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(...)
}
Bergi
  • 630,263
  • 148
  • 957
  • 1,375
  • Ah, yes. That's a great suggestion! But this still has a problem of `Can't set headers after they are sent.`, because `return next(err)` and `return res.status(200)` are both trying to execute. Any idea on that? – Tomasz Gałkowski Jul 11 '17 at 08:08
  • @TomaszGałkowski Are you sure you have both returns in the same function, like in my answer? – Bergi Jul 11 '17 at 08:10
  • Yes, positive. It's an async function that `awaits` for one thing and later does that map. After the map it returns 200. If an error occurs within the map it should return `next` with my custom error - and send 422. Which it tries to do now, thanks to you. But that's where the double headers seem to come from. – Tomasz Gałkowski Jul 11 '17 at 08:13
  • You must not call `next` within the `map` callback, it might happen multiple times in there. And if you `return` after `next()`, it won't execute `res.status()`. Please post the updated code that is causing the error – Bergi Jul 11 '17 at 08:16
  • Actually that last comment made me reformat the code and find the error. I had one more `if` clause inside the `map` so I moved the `await Promise.all()` to take in the whole map. I will post the details in the question and mark your response as answer as it perfectly answered my initial question. – Tomasz Gałkowski Jul 11 '17 at 08:19