You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

NodeJS中UnhandledPromiseRejectionWarning与ERR_HTTP_HEADERS_SENT问题求助

Fixing "Cannot set headers after they are sent to the client" in your registration token route

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:

  1. When ModelRegistrationToken.findOne finds an active token for the email, you enter the if(user) block.
  2. Inside that block, you run ModelUser.findOne — this is asynchronous, so your code doesn't wait for it to finish before moving on.
  3. While that inner query is running, the rest of your code (creating and saving a new registration token) continues to execute.
  4. 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 the newRegistrationToken.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));
});

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.09 12:42:36