NodeJS中UnhandledPromiseRejectionWarning与ERR_HTTP_HEADERS_SENT问题求助
Hey there, let's break down why you're seeing that ERR_HTTP_HEADERS_SENT warning and how to fix it.
The Root Cause
Your issue comes from uncontrolled code execution paths due to nested async callbacks. Here's exactly what's happening:
- When
ModelRegistrationToken.findOnefinds an active token for the email, you enter theif(user)block. - Inside that block, you run
ModelUser.findOne— this is asynchronous, so your code doesn't wait for it to finish before moving on. - While that inner query is running, the rest of your code (creating and saving a new registration token) continues to execute.
- Later, when the inner query completes, you send a response (
res.status(400).json(...)), but by then your code has already sent another response from thenewRegistrationToken.save().then(...)call.
You're sending two responses to the same request, which triggers the warning. Also, your inner ModelUser.findOne catch only logs the error without sending a response, which would leave requests hanging if that query fails.
Fix 1: Correct Callback Flow with Returns
We can fix this by ensuring that when we hit the if(user) branch, we return the inner promise to stop the rest of the code from executing. We'll also add proper error handling for that inner query:
router.post('/create', (req, res) => { const { errors, isValid } = validateRegistrationToken(req.body); if(!isValid) return res.status(400).json(errors); ModelRegistrationToken.findOne({ email: req.body.email, expiresAt: { $gte: Date.now() } }) .then(existingToken => { // If an active token exists, handle this case and exit if(existingToken){ return ModelUser.findOne({email: existingToken.email}) .then(registeredUser => { if(registeredUser) { 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.error(err); return res.status(500).json(err); }); } // Only run this if no active token exists const newRegistrationToken = new ModelRegistrationToken({ email: req.body.email, expiresAt: new Date(Date.now() + Number.parseInt(config.REG_TOKEN_EXPIRATION_PERIOD) * 24 * 60 * 60 * 1000) }); return newRegistrationToken.save() .then(savedToken => res.status(200).json(savedToken)) .catch(err => res.status(500).json(err)); }) .catch(err => res.status(500).json(err)); });
Fix 2: Refactor to Async/Await (Recommended)
Async/await makes async code linear and much easier to reason about, eliminating callback hell and these kinds of execution path bugs. Here's a cleaner version:
router.post('/create', async (req, res) => { try { const { errors, isValid } = validateRegistrationToken(req.body); if(!isValid) { return res.status(400).json(errors); } // Check for existing active registration token const existingToken = await ModelRegistrationToken.findOne({ email: req.body.email, expiresAt: { $gte: Date.now() } }); if(existingToken) { // Check if the user is already registered const registeredUser = await ModelUser.findOne({email: existingToken.email}); if(registeredUser) { 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'}); } // Create and save new registration 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) }); const savedToken = await newRegistrationToken.save(); return res.status(200).json(savedToken); } catch (err) { console.error(err); return res.status(500).json(err); } });
Key Improvements in Both Fixes
- Early returns: As soon as we send a response, we return to stop any further code execution in the route.
- Proper error handling: All async errors are caught and converted into proper 500 responses, so requests never hang.
- Controlled execution paths: We only create/save a new token if no active token exists, eliminating the duplicate response issue.
内容的提问来源于stack exchange,提问作者Flashcap

