0
\$\begingroup\$

So I'm wondering how to properly handle errors in JavaScript when utilizing promises. I'm currently throwing errors for everything so that I can break out of the promise - if I can't find something, if there was a missing param, an operation failed, etc.

It seems wrong to be throwing an exception for everything like this, but this is the best thing I've come up with so far after much thought. I am sure to catch all of my thrown errors in the routes which passes them on to my middleware.

Is there a better way?

// SERVICE / MODEL LOGIC
getUserByEmail() {
  if (!this._model.email) {
    throw new VError('Missing email.');
  }

  return UserModel.findOne({ email: this._model.email }).exec()
    .then((result) => {
      if (!result) {
        throw new VError(errors.NOT_FOUND, 'Could not find user.');
      }

      return result;
    })
    .catch((error) => {
      throw new VError(error, 'Could not find user.');
    });
}

// ROUTE
router.get('/users/:email', (req, res, next) => {
  const model = new UserModel({ email: req.params.email });

  res.setHeader('Content-Type', 'application/json');
  return new UserService(model).getUserByEmail()
    .then(result => res.status(200).send(result))
    .catch(error => next(error));
});

// MIDDLEWARE
const errorHandling = (error, req, res, next) => 
  res.status(VError.info(error).status || 500).send(error.message);

\$\endgroup\$
4
\$\begingroup\$

There's a big problem here. getUserByEmail() can either return a promise or throw synchronously which is a bad design and very unfriendly for the caller. Instead, you want any synchronous errors to be returned as a rejected promise.

The beginning of your code should be changed from this:

// SERVICE / MODEL LOGIC
getUserByEmail() {
  if (!this._model.email) {
    throw new VError('Missing email.');
  }

to this:

// SERVICE / MODEL LOGIC
getUserByEmail() {
  if (!this._model.email) {
    return Promise.reject(new VError('Missing email.'));
  }

The other throw statements are what you do from a .then() handler to turn the promise chain into a rejected promise. You could also do return Promise.reject(...), but throw works just as well.

\$\endgroup\$
  • \$\begingroup\$ Really appreciate the comment - so then()'s will handle the exception and reject the promise for free, but in my plain func I need to specifically return a promise. I'm not sure I understand the effect this has - I assume it hijacks the event loop? \$\endgroup\$ – ohiock Feb 2 '18 at 5:37
  • 2
    \$\begingroup\$ @ohiock - Yes, .then() handlers have their own exception handling and when they catch an exception, they turn the promise chain into a rejected promise with the exception reason as the reject reason. This is a major feature or promises and is referred to as "throw safe". \$\endgroup\$ – jfriend00 Feb 2 '18 at 5:42

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.