Proper Express error handling with promises and avoiding ERR_HTTP_HEADERS_SENT

Viewed 66

So I've been looking through a bunch of stackoverflow posts, articles online, and https://expressjs.com/en/guide/error-handling.html but can't seem to find the proper way to stop a promise and return the response to a user.

I currently have dependent async promises that Promise A needs to resolve before Promise B starts. If something goes wrong anywhere in there, I'd like to return different errors to the client using the res.json() method. Unfortunately, it seems no matter what I'm doing, after the res is sent to the client, then catch(next) is called which would then try to send the res to the client again with different headers. That leads to the Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client error.

I had also tried with just return statements but that seemed to just continue onto the next .then() which would pass whatever the return statement had.

index.js:

const errorFunc = (err, req, res, next) => {
  console.error(err);

  return res.status(500).json({ message: 'Internal error.' });
};
app.get('/scream/:screamId/like', FBAuth, likeScream, errorFunc);

The handler

exports.likeScream = (req, res, next) => {
  const likeDocument = db
    .collection('likes')
    .where('userHandle', '==', req.user.handle)
    .where('screamId', '==', req.params.screamId)
    .limit(1);

  const screamDocument = db.doc(`/screams/${req.params.screamId}`);

  return likeDocument
    .get()
    .then((data) => {
      if (!data.empty) {
        res
          .status(HttpStatus.BAD_REQUEST)
          .json({ error: 'User already liked scream' })
          .send();
        return Promise.reject(new Error('User already liked screams'));
      } else {
        return screamDocument;
      }
    })
    .then((doc) => {
      if (!doc.exists) {
        res.status(404).json({ error: 'Scream not found' }).send();
        return Promise.reject(new Error('Scream not found'));
      }
      const likeCount = doc.data().likeCount++;
      return db.doc(`/screams/${req.params.screamId}`).update({ likeCount });
    })
    .then(() => {
      const likeDetails = {
        userHandle: req.user.handle,
        screamId: req.params.screamId,
      };
      return db.collection('likes').add(likeDetails);
    })
    .then(() => {
      return res.json({ message: 'Scream liked successfully.' });
    })
    .catch(next);
};

Note that most of this code was from following the https://github.com/hidjou/classsed-react-firebase-functions/blob/master/functions/handlers/screams.js tutorial for learning firebase and react. That code there seems to have this issue too.

So overall, what's the proper way to send out res.sendStatus(XXX) and stop an ongoing Promise? Thanks in advance.

0 Answers
Related