Flashcap
Flashcap

Reputation: 141

NodeJS UnhandledPromiseRejectionWarning with promises closed

I ran into a problem while implementing a registration system that uses tokens in order to prevent random users from creating multiple accounts.

The code runs until the very end, responses work as expected, but I get a warning afterwards, that says: UnhandledPromiseRejectionWarning: Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client

It says the problem happens at line 44, which is a catch statement for the mongoose save method.

newRegistrationToken.save()
            .then(newRegistrationToken => res.status(200).json(newRegistrationToken))
            .catch(err => res.status(500).json(err));

I have been searching for the solution, the answer always was that a catch statement is missing, resulting in this warning. However, in my code, I don't see any missing catch statements, so I am a bit lost here.

Here is the whole route:

router.post('/create', (req, res) => {
    const { errors, isValid } = validateRegistrationToken(req.body);
    if(!isValid) return res.status(400).json(errors);

    // Searching for an active token associated with the e-mail address given
    console.log("search for active token");
    ModelRegistrationToken.findOne({
        email: req.body.email,
        expiresAt: { $gte: Date.now() }
    }).then(user => {
        // if user found
        if(user){
            // check if the user is already registered
            console.log("search for registered user");
            ModelUser.findOne({email: user.email}).then((registered) => {
                if(registered)
                    return res.status(400).json({email: 'There\'s a user already registered with the e-mail address given'});
                return res.status(400).json({email: 'The address given already has an active registration token'});
            }).catch(err => console.log(err));
        }
        
        // Otherwise create a new token
        const newRegistrationToken = new ModelRegistrationToken({
            email: req.body.email,
            expiresAt: new Date(Date.now() + Number.parseInt(config.REG_TOKEN_EXPIRATION_PERIOD) * 24 * 60 * 60 * 1000)
        });
        console.log("save token");
        newRegistrationToken.save()
            .then(newRegistrationToken => res.status(200).json(newRegistrationToken))
            .catch(err => res.status(500).json(err));
        
    }).catch(err => res.status(500).json(err));
});

I can't seem to figure this out, so I would appreciate your help on this one..

Upvotes: 1

Views: 1143

Answers (1)

CertainPerformance
CertainPerformance

Reputation: 370759

You have:

if (user) {
  // do stuff and call res.json
}
// do other stuff and call res.json

If user exists, res.json will be called twice. Try returning at the bottom of the if (user) block:

if (user) {
  // check if the user is already registered
  console.log("search for registered user");
  ModelUser.findOne({
    // ...
  }).catch(err => console.log(err));
  return; // <---------------------------------
}

// Otherwise create a new token
const newRegistrationToken = new ModelRegistrationToken({
  email: req.body.email,
  expiresAt: new Date(Date.now() + Number.parseInt(config.REG_TOKEN_EXPIRATION_PERIOD) * 24 * 60 * 60 * 1000)
});
// ...

I'd also recommend sending status 500 if the inner findOne happens to error, else the response will hang: turn

}).catch(err => console.log(err));

into something like

}).catch(err => res.status(500).json(err));

like you're doing elsewhere.

Upvotes: 1

Related Questions