ExpressJS refuses to wait for my promise to be fulfilled

I'm in the process of creating a search-page on my server. Whenever the endpoint is accessed and the user waits for the search function to deliver the results and display them on the page, Express somehow redirects to the 404 handler instead. An error message pops up indicating:

Error: Can't set headers after they are sent.

I can't figure out what I'm doing wrong here.

router.get("/", async (req, res) => {
    try {
        const queryString = req.query.q;

        const user = helper.checkAndGetUser(req, res);

        let s = String(queryString), searchedTags = [""];
        if(s.indexOf(",") > -1){
            searchedTags = s.replace(" ", "").split(",");
        }

        const options = {
            "query": {tags: {$all: searchedTags}, _forSale: true}
        };

        const results = await Search.search(options).then(result => result).catch(err => {
            throw err;
        });

        //After calling this res.render, it goes to the 404 splat-route.
        return res.render("partial/search.pug", {user: user, search: {
            query: queryString,
            results: results
        }});

        //If I use res.send for debugging, it goes before the splat-route as follows:
        return res.send(results);
    } catch(err) {
        next(err);
    }
});

module.exports = router;

I've included the router like so:

const search = require("./search.js");
app.use("/search", search);

Then there's the 404 splat-route:

app.get("*", async (req, res, next) => {

    const user = helper.checkAndGetUser(req, res);

    res.status(404);
    res.render("partial/404.pug", {user: user});
});

To clarify: My question is how do I ensure that the res.render function is called just like the res.send function?

UPDATE [2017-10-05]: As I progressed with another section of the site, which had a similar endpoint, I noticed that using res.send with the promise result worked fine, but not when using res.render. Once again, the 404-handler took over. Any idea if this could be an issue with Express?

Answer №1

If you find yourself trying to modify res after it has already been sent, it means that additional code is being called after res.render() or a response has already been given before that point.

To prevent this issue, use return res.render(...) to exit the function immediately after rendering, avoiding any further processing that may interfere with the response.

The error handling mechanism also requires attention. Make sure your error handler includes (req, res, next) and utilizes return next(err) to pass errors to your error middleware for proper handling.

Here is an example of how I like to structure async/await functions in Express:

// organize your routes as shown below

app.get('/route', async (req, res, next) => {
    try {
        const data = 'example'
        const payload = await something(data)
            .then((result) => createPayload(result))

        return res.render('route', { payload })
    } catch (e) {
        return next(e)
    }
})

// Catch all unmatched routes
app.get('*', async (req, res, next) => {
    return res.status(404).render('error/404')
})

// Error Handling
app.use(async (err, req, res, next) => {
    res.status(500).render('error/500')
    throw err
})

Note: When calling next() without any parameter, it signifies no error and proceeds to the next middleware. Passing anything triggers the error middleware with the value as the error parameter. Maintain consistency with the usage of return when using res.send/render() to avoid header conflicts.

UPDATE:

Anomalies might arise from having a callback within your .then() statement. It's unclear where the err would originate, considering that resolved promises go into the .then() function as result. Consider revising or removing this part:

try {
    let results = [];
    await Search.search(options).then(result => {
        results = result;
    }, err => {
        throw err;
    });

    console.log("res.render");
    return res.render("partial/search.pug", {user: user, search: {
        query: string,
        results: results
    }});
} catch(err) {
    next(err);
}

Below is an updated version utilizing async/await syntax:

router.get("/", async (req, res, next) => {

    try {
        const queryString = req.query.q;
        const user = helper.checkAndGetUser(req, res);

        let s = String(queryString), searchedTags = [""];
        if (s.indexOf(",") > -1) {
            searchedTags = s.replace(" ", "").split(",");
        }
        const options = {
            "query": { tags: { $all: searchedTags }, _forSale: true }
        };

        const results = await Search.search(options)
            .then(data => data)
            .catch(err => { throw 'Problem occurred in index route:' + err });

        return res.render("partial/search.pug", {
            user: user, search: {
                query: string,
                results: results
            }
        });
    } catch (err) {
        return next(err);
    }
});

module.exports = router;

Error handler:

app.use((err, req, res, next) => {

    const user = helper.checkAndGetUser(req, res);

    res.status(404);
    res.render("partial/404.pug", {user: user});
});

Referencing the Express documentation:

Remember to define error-handling middleware functions with four arguments instead of three: (err, req, res, next).

app.use(function (err, req, res, next) {
  console.error(err.stack)
  res.status(500).send('Something broke!')
})

http://expressjs.com/en/guide/error-handling.html

Your current situation with the error handler appears to be causing issues as it behaves like a regular middleware rather than activating only when next() is triggered with input. Ensure the presence of the err parameter in the middleware function to address this concern.

The Default Error Handler

Express furnishes a built-in error handler to manage encountered errors. This default mechanism handles errors not addressed by custom middleware, sending them to the client sans stack trace display in production environments. Additionally, errors occurring post-response initiation prompt closure by the default error handler.

Integrating a custom error handler necessitates delegating to Express's default mechanisms once headers have been transmitted to the client.

I suggest placing the splat route

app.get('*', async (req, res, next) => {})
preceding the error-handling middleware at the end of your route list. This ensures capture of any unmatched paths and directs clients to the designated 404 page while preserving the error-handler's ability to process errors passed through next(err).

Consider implementing the following authentication failure safeguard within auth-required routes as an initial check:

if (!req.person) return res.status(403).render('error/403')

Experiment with these practices individually to gauge effectiveness before deciding on implementation for your scenarios.

Answer №2

After spending several days carefully reviewing the code, I finally identified a problem in the checkAndGetUser function. When this function ran without the user being signed in, it executed faster than the async call to the database, leading to the triggering of the splat endpoint and displaying the 404 page.

I suspect that the reason the splat endpoint was not triggered when using res.send instead of res.render is due to the efficiency of the res.send function, as it does not need to process any HTML content like the render call does.

A special thanks to @agm1984 for sharing valuable insights about the Express framework. If anyone else encounters a similar issue, be sure to refer to his helpful post.

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

A regular expression route does not include req.params in the passed data

My Express router includes two routes that look like this: router.get("/v|verses", (req, res) => ... router.get("/v|verses/:book", (req, res) => .... When I try to access /verses/john, why does it match the first route and return an empty object f ...

Connecting an admin dashboard to a MERN e-commerce application: A step-by-step guide

In the process of developing an e-commerce platform, I find myself using React.js for the frontend and Node.js/Express.js for the backend. My current challenge lies in creating a seamless dashboard to manage items within the app. One possible solution wo ...

Unable to modify array state with data from another state

Two state objects are at play here. The first is personnel, which consists of an array of 1-object arrays structured like this: [[{}],[{}],[{}],[{}],...]. The second state object is rowItems, where the goal is to extract all objects from the inner arrays o ...

Error: $this.text is throwing a TypeError and is not working as a function

Upon examining the code below: var $this = $(this).children('div.submenu1').children('a.subtile')[0], title = $this.text(), name = $this.attr('node').val; An error is encountered: Uncaught TypeError: $this.text is not a fun ...

Node API is failing to insert user data into MongoDB

I'm currently developing a Restful API using Node.js and storing data in Mongodb, focusing on the user registration API. app.js apiRoutes.post('/signup', function(req, res) { if (!req.body.name || !req.body.password) { res.json({suc ...

Retrieve and showcase every image belonging to a specific user using Node.js and EJS, pulling the data

As a newcomer to Node.js, I am currently working on displaying all the images of a logged-in user from my database onto my EJS file. I have written the necessary code, but I am facing an issue with properly displaying all the images. I have attempted to us ...

Utilize jQuery and AJAX to refresh functions after each AJAX call for newly added items exclusively

I have encountered an issue with my jQuery plugins on my website. Everything functions smoothly until I load new elements via AJAX call. Re-initializing all the plugins then causes chaos because some are initialized multiple times. Is there a way to only i ...

Using Kendo Grid to Transfer Data Between Grid Cells

Recently, I encountered a problem in my Kendo MVC project where I needed to drag a product code from one Kendo Grid to another when the cell value was empty. Here's the scenario: Grid A contains products ordered, but the vendor sending the list has i ...

The Less compiler (lessc) encounters an issue on a fresh operating system. ([TypeError: undefined is not a function])

After setting up my new development environment on Windows 10, I encountered an issue with less. Following the instructions on lesscss.org, I installed less using: npm install -g less The installation process completed without any errors. However, when ...

Broken links detected in the Full Page Navigation menu on a one-page website

The hyperlinks on this particular page seem to be malfunctioning despite the fact that the li.a tags are correctly targeting specific section IDs. Markup: <header> <a href="#0" class="nav_icon"><i></i></a> </header> ...

Exploring Symfony2 controller integration with Javascript arguments

I am currently working on a project with Symfony2 and I have a question regarding receiving arguments from a template in a controller. My goal is to take the value of the argument and store it in the database. The argument's value will be generated by ...

Is it possible to request/scrape pages from the client side?

Let me present the issue at hand: I am currently managing a web application that serves as a notification system which updates frequently. This application is operational on several local computers, each of which solely display information without any inp ...

Apply an opacity setting of 0.5 to the specific segment representing 30% of the scrollable div

I have a scrollable container for displaying messages. I would like to apply an opacity of 0.5 to the content in the top 30% of the container, as shown in this image: https://i.stack.imgur.com/NHlBN.png. However, when I tried using a background div with a ...

What is the recommended method for writing JavaScript scripts with AJAX in a Rails application? How can URLs be incorporated into the script effectively?

When incorporating AJAX into my Rails application, I encounter difficulties when specifying the URL for the request within a script. While it is recommended to use helpers like my_resource_path instead of manually writing paths, these helpers do not functi ...

I'm encountering an issue with my React master component not being able to locate the

I am having trouble importing a component in my APP.js file. I have been attempting to bring MainComponent into the app.js component, but I am facing difficulties in fetching the component. Any assistance in resolving this issue would be greatly apprecia ...

How come the gridApi.on.edit.beginCellEdit function in angular-ui-grid does not immediately refresh the dropdown options after being updated?

I am encountering a similar issue as described in this post Regarding the assignment of ui grid value drop-down box before beginCellEdit event fires in Angular However, I have noticed a slight difference. Even after updating the editDropdownOptionArray, th ...

Error TS2346: The parameters provided do not match the signature for the d3Service/d3-ng2-service TypeScript function

I am working with an SVG file that includes both rectangular elements and text elements. index.html <svg id="timeline" width="300" height="100"> <g transform="translate(10,10)" class="container" width="280" height="96"> <rect x ...

Is there a more efficient method for implementing server side rendering in a Next.js application?

Currently, I am implementing server-side rendering in my Next.js application. This specific component is responsible for returning HTML content. I'm wondering if there are more efficient methods available? import Feature from 'components/home/Fea ...

There seems to be an issue with the React Native FlatList: It appears that there is no overload matching this call and some

I am currently learning React Native and attempting to create a basic chat room application. I am facing an issue with the FlatList component that I can't seem to resolve. Even though I have provided both the data prop and renderItem prop to the FlatL ...

Here is a unique version: "A guide on centering a carousel item in jquery upon being clicked."

Does anyone know how to center the item I click in a carousel? I've searched high and low for a solution but couldn't find a clear answer. Can someone please assist me with this? This is what I have tried so far: http://jsfiddle.net/sp9Jv/ Here ...