Express JS: Issue with chain promises halting at return statement in JavaScript

In my express app, I have a utility function that controls the promise then chain based on a specific condition. However, when I attempt to end the chain using a return statement, Express throws an error stating that headers cannot be set after they are sent. Here is the code snippet:

function submit_reset_password_request(request, response) {
  if (request.session.id) {
    return response.json({ error: true, message: 'password reset cannot be requested during an active session' });
  }

  let { email } = request.body;
  let user, reset_request;
  if (email) {
    email = email.toLowerCase().trim();
  }
  if (!email) {
    return response.json({ error: true, message: 'input is required' });
  }

  models.Users.findOne({ where: { email } })
  .then(user_result => {
    if (!user_result) {
      return response.json({ error: true, message: 'No account found with that email' });
    }
    user = user_result.dataValues;
    return models.ResetPasswordRequests.findOne({ where: { user_email: user.email } })
  })
  .then(request_result => {
    if (request_result) {
      return response.json({ error: true, message: 'A password reset has already been requested for this email' });
    }
    return models.ResetPasswordRequests.create({ user_email: user.email })
  })
  .then(new_reset_request => {
    reset_request = new_reset_request.dataValues;

    // send reset request email
    let host = request.get('host');
    let link = host.endsWith('/') ? (host + 'search') : (host + '/search');
    let email_subject = 'Epsity - Password reset requested';
    let email_html = templateEngine.PasswordReset_EMAIL({ user, reset_request, link });
    return sendgrid_manager.send_email(null, request.session.you.email, email_subject, email_html);
  })
  .then(email_result => {
    return response.json({ success: true, message: 'A password reset request has been sent to the provided email!' });
  })
  .catch(error => {
    console.log(error);
    return response.json({ error, message: 'Could not submit reset password request...' });
  });
}

Despite the if statements intended to stop the chain and send a response, it continues executing regardless (although the response can still be seen on the client side). The cause of this behavior is unclear.

Below is the error message received:

Unhandled rejection Error: Can't set headers after they are sent.
    at validateHeader (_http_outgoing.js:494:11)
    at ServerResponse.setHeader (_http_outgoing.js:501:3)
    at ServerResponse.header (C:\Users\Waite-Ryan-M\Desktop\_my-apps\rmw-epsity\node_modules\express\lib\response.js:767:10)
    at ServerResponse.send (C:\Users\Waite-Ryan-M\Desktop\_my-apps\rmw-epsity\node_modules\express\lib\response.js:170:12)
    at ServerResponse.json (C:\Users\Waite-Ryan-M\Desktop\_my-apps\rmw-epsity\node_modules\express\lib\response.js:267:15)
    at models.Users.findOne.then.then.then.then.catch.error (C:\Users\Waite-Ryan-M\Desktop\_my-apps\rmw-epsity\server\routers\main\methods\post.js:221:21)
    at tryCatcher (C:\Users\Waite-Ryan-M\Desktop\_my-apps\rmw-epsity\node_modules\bluebird\js\release\util.js:16:23)
    at Promise._settlePromiseFromHandler (C:\Users\Waite-Ryan-M\Desktop\_my-apps\rmw-epsity\node_modules\bluebird\js\release\promise.js:512:31)
    at Promise._settlePromise (C:\Users\Waite-Ryan-M\Desktop\_my-apps\rmw-epsity\node_modules\bluebird\js\release\promise.js:569:18)
    at Promise._settlePromise0 (C:\Users\Waite-Ryan-M\Desktop\_my-apps\rmw-epsity\node_modules\bluebird\js\release\promise.js:614:10)
    at Promise._settlePromises (C:\Users\Waite-Ryan-M\Desktop\_my-apps\rmw-epsity\node_modules\bluebird\js\release\promise.js:690:18)
    at _drainQueueStep (C:\Users\Waite-Ryan-M\Desktop\_my-apps\rmw-epsity\node_modules\bluebird\js\release\async.js:138:12)
    at _drainQueue (C:\Users\Waite-Ryan-M\Desktop\_my-apps\rmw-epsity\node_modules\bluebluebirdird/js/release/async.js:131:9)
    at Async._drainQueues (C:\Users\Waite-Ryan-M\Desktop\_my-apps\rmw-epsity\node_modules\bluebird\js\release\async.js:147:5)
    at Immediate.Async.drainQueues (C:\Users\Waite-Ryan-M\Desktop\_my-apps\rmw-epsity\node_modules\bluebird\js\release\async.js:17:14)
    at runCallback (timers.js:789:20)
    at tryOnImmediate (timers.js:751:5)
    at processImmediate [as _immediateCallback] (timers.js:722:5)

I might consider implementing the async/await syntax as a potential solution.

Answer №1

One issue arises when you find yourself within a .then block nested inside a chain of Promises, as the return value is not always of concern to the Promise itself. Consider this snippet:

.then(user_result => {
  if(!user_result) {
    return response.json({ error: true, message: 'No account found by that email' });
  }
  user = user_result.dataValues;
  return models.ResetPasswordRequests.findOne({ where: { user_email: user.email } })
})

The interpreter treats both the return response.json and return models.Reset... in the same way - passing them along to the succeeding .then without breaking the chain. To address this, you can either extract the functions into named functions for better control flow (albeit more verbose), or utilize async/await, which enables a similar behavior with explicit termination upon return and handling asynchronous calls via await.

A refactor example using async/await:

async function submit_reset_password_request (request, response) {
  if(request.session.id) {
    return response.json({ error: true, message: 'password reset cannot be requested during an active session' });
  }
  let { email } = request.body;
  if(email) {
    email = email.toLowerCase().trim();
  }
  if(!email) {
    return response.json({ error: true, message: 'input is required' });
  }

  try {
    const user_result = await models.Users.findOne({ where: { email } });
    if(!user_result) {
      return response.json({ error: true, message: 'No account found by that email' });
    }
    const user = user_result.dataValues;
    const request_result = await models.ResetPasswordRequests.findOne({ where: { user_email: user.email } });
    if(request_result) {
      return response.json({ error: true, message: 'A password reset has already been requested for this email' });
    }
    const new_reset_request = await models.ResetPasswordRequests.create({ user_email: user.email });
    const reset_request = new_reset_request.dataValues;

    // send reset request email
    const host = request.get('host');
    const link = host.endsWith('/') ? (host + 'search') : (host + '/search');
    const email_subject = 'Epsity - Password reset requested';
    const email_html = templateEngine.PasswordReset_EMAIL({ user, reset_request, link });
    const email_result = await sendgrid_manager.send_email(null, request.session.you.email, email_subject, email_html);
    return response.json({ success: true, message: 'A password reset request has been sent to the provided email!' });
  } catch(error) {
    console.log(error);
    return response.json({ error, message: 'Could not submit reset password request...' });
  }
}

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

What is the reason for the clearInterval consistently functioning after the completion of the function?

Just starting out in web programming and currently delving into javascript and jquery ajax.. This snippet represents my javascript code. The refresh variable controls an interval for updating the chat by fetching chats from the database and displaying the ...

This code snippet results in the property being unrecognized

I recently wrote this block of code and encountered an error while trying to run the alert function. It keeps telling me that 'this.words' is not defined. I suspect the issue lies within the jQuery portion, as I am able to access the array where ...

Steps for resetting data() on a route without parameters:

Having trouble restarting a route on a new editor I have a specific route /editor as well as /editor?_id=dasd448846acsca The /editor route consists of a simple form with empty inputs, while the /editor?_id=dasd448846acsca route has the same component bu ...

What is the method for inserting indicators between yAxis values in a yAxis ChartJs graph?

I have a line chart displaying the link below: https://i.sstatic.net/yUAHf.png There is a noticeable gap between 0-0.7 on the chart. I am looking to add an indicator similar to the one shown in this image: https://i.sstatic.net/pRjsY.png The desired ou ...

What causes MongoDB to modify my UTC time zone from UTC+2:00 to UTC+0 when utilizing Mongoose in conjunction with moment-timezones.js?

MongoDB recently updated their UTC time from UTC+2:00 to UTC+0. Despite attempting to include the option "{ forceServerObjectId: true }", I still encountered issues. My code relies on moment-timezones.js for creating dates, here's a snippe ...

Conceal the pagination element within the Bootstrap aside on smaller screens

I am experiencing an issue with my web page layout. I have a DIV and an Aside element, inside the DIV there is a list with dynamic pagination. Everything seems to be functioning correctly, however on small devices, the Aside section is covering up the pa ...

What strategies can be implemented to avoid re-rendering in Angular 6 when the window is resized or loses focus?

I am currently working with a component in Angular 6.0.8 that consists of only an iframe element. Here is the code in page.component.html: <iframe [src]="url"> The logic for setting the URL is handled in page.component.ts: ngOnInit() { this.u ...

Having trouble with images not showing up on React applications built with webpack?

Hey there! I decided not to use the create react app command and instead built everything from scratch. Below is my webpack configuration: const path = require("path"); module.exports = { mode: "development", entry: "./index.js", output: { pa ...

How can I retrieve the GET parameters following the "?" in an Express application?

When dealing with queries like this, I am familiar with how to obtain the parameters: app.get('/sample/:id', routes.sample); In such scenarios, I can easily retrieve the parameter using req.params.id (for example, 2 in /sample/2). However, whe ...

creating a Vuejs button function that will add together two given numbers

Help needed with VueJs code to display the sum of two numbers. Seeking assistance in developing a feature that calculates the sum only when the user clicks a button. Any guidance would be greatly appreciated! <!DOCTYPE html> <html lang="en"> ...

Javascript - Incorporate a hyperlink into my Flickr Api image query

I am struggling with incorporating a link around the image generated by this request due to my limited API knowledge. Below is the current function responsible for displaying the album images. To see a functional version, please refer to the provided fidd ...

What should be placed in the viewRef: MapViewNativeComponentType parameter when utilizing React Native Maps in the current.fitToSuppliedMarkers function?

useEffect(() => { if(!origin || !destination) return; mapRef.current.fitToSuppliedMarkers(["origin", "destination"], { edgePadding: { top: 50, right: 50, bottom: 50, left: 50} }) }, [origin, destination]); I'm currently in t ...

File type cannot be determined on certain devices

I am currently utilizing the antd Upload component in React to handle file input. My specific scenario only allows certain types of files to be uploaded. To enforce this restriction, I am employing the beforeUpload prop of the Upload component to verify th ...

Utilizing External Libraries in SAPUI5 Extension Development

I am facing an issue while trying to integrate an external library into my UI5 project. Specifically, I am working with jsPDF but it could be any other library as well. I have included it in the manifest.json file in the following manner: "js": [{ ...

Tips for reverting a component back to its initial state in React when a prop is modified

I am dealing with a prop named props.currPage. This prop gets updated based on the button that is clicked. Whenever a button is clicked, I want a certain part of the component to reset to its initial state. Unfortunately, I am facing an issue where the "l ...

Verify the presence of a GET parameter in the URL

Working on a simple log in form for my website using Jade and ExpressJS. Everything is functioning correctly, except for one issue - handling incorrect log in details. When users input wrong information, they are redirected back to the log in page with a p ...

Tips for adjusting column width with flexbox intersections

I am currently working on a React web application that has minimal styling. I have divided the content into 3 columns, with the leftWrap being col-3, rightWrap being col-4, and the remaining width is for centerWrap. I want to apply flex ...

Using an if/else statement in a Jade Template to conditionally remove a select option

I'm a beginner with Jade and I've been assigned to add a select option to a webpage. Everything is working well, except for when the drop-down list is empty and the page is refreshed, an empty option element is created. I want to find a way to re ...

I've exhausted all my knowledge but still unable to get Tailwind to work

I've been troubleshooting Tailwind for the past 6 hours. Managed to set it up in a simpler Nextjs/React/Typescript project, but struggling to get it working in this larger codebase. I'm sure I'm missing something obvious, but I'm at a ...

Tips on avoiding quotation marks in a Less variable containing a color identifier

I'm currently working on an HTML/CSS project where I aim to establish classes for labels and texts based on their color. For instance: .text-red{ color: red; } .label-white{ color: white; } In order to achieve this, my approach involves cr ...