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

Converting a table into div elements and subsequently reverting it back to its original table format

[STOP DOWNVOTING: NEW AND IMPROVED] Discovering a simple technique on stackoverflow to transform tables into divs has been quite enlightening. By assigning classes to each tag, such as: <table class="table"> the process of converting from table to ...

Error in code - message gets sent immediately instead of waiting for timeout to occur

There seems to be an issue with the code where the message is sent instantly instead of waiting for the specified timeout duration. Based on the code, it should wait for the time mentioned in the message. I'm puzzled as to why it's not functioni ...

The list attribute in AngularJS seems to be malfunctioning when used in Internet Explorer 11

My current issue involves using the 'find' method within a scope. While it works perfectly fine in Chrome, there seems to be compatibility issues with Internet Explorer 11. How can I resolve this and make it work smoothly on IE 11? $scope.NameLi ...

Learn how to utilize Vue 3 to access properties that have been passed down from a parent component, even if they

Hey there, hope everything is going well. I'm familiar with react.js, but when I gave vue a try, things felt a bit different. In react, it's easy to access props passed from the parent in the child component without much hassle. However, in vue, ...

Dynamic Searching in ASP.NET Core MVC Select Component

I need assistance with implementing a dynamic search feature on my Login page. Here's the scenario: When a user enters a username, let's say "Jh" for Jhon, I want to display a select list next to the login form that lists all the usernames from t ...

Trouble arises when attempting to create a two-column layout using Material UI's <Grid> component

My goal is to create a two-column layout where each column takes up half of the screen and has equal height. To better illustrate, take a look at this image. The code I've tried so far is not working as expected: import React from "react"; import { ...

JSON payload with an array that includes nested JSON objects

I am struggling with JSON objects and need to create a JSN structure similar to the following: { name: 'root', children: [ name: 'son1', children: [....] ] } Could someone please guide me on how to construct it using Java ...

Issue with facebook button in reactJS where onClick() event is not being triggered

I'm facing an issue where my onClick handler is not working with the Facebook button from https://developers.facebook.com/docs/facebook-login/web/login-button/. However, it works fine with a regular div element. Any thoughts on why the onClick event ...

The ng-model is not properly syncing values bidirectionally within a modal window

I am dealing with some html <body ng-controller="AppCtrl"> <ion-side-menus> <ion-side-menu-content> <ion-nav-bar class="nav-title-slide-ios7 bar-positive"> <ion-nav-back-button class="button-icon ion-arrow-le ...

Implementing associations with ES6 syntax and utilizing a default created Model (ensuring it is not associated with another Model

What am I doing? I am attempting to establish an association with my Folder model, using the default model created by CLI. It's worth noting that I am trying to associate the model with itself, so Folder.hasMany.Folder. What am I trying to achieve? ...

Client-side validation with Jquery is failing to function properly

Currently, I am experimenting with the jquery.validate.unobtrusive.js plugin to dynamically generate form fields. Here is an example of how I'm creating a textarea field: var message = $("<textarea id='test'></textarea>"); $(mes ...

In TypeScript, both 'module' and 'define' are nowhere to be found

When I transpile my TypeScript using "-m umd" for a project that includes server, client, and shared code, I encounter an issue where the client-side code does not work in the browser. Strangely, no errors are displayed in the browser console, and breakpoi ...

Modifying column layout within an HTML webpage

I'm seeking advice on modifying a code. I'd like to keep it the same as it is now, but with one change - I want to decrease the width of the main video box and add another one beside it with an iframe code for a chatbox. Here's the current c ...

Unusual case of missing lines while reading a file using readline.createInterface()

const readline = require('readline') const fs = require('fs/promises'); (async function() { await fs.writeFile('/tmp/input.txt', [...Array(100000).keys()].join('\n')) await fs.writeFile('/tmp/other.tx ...

Transform a protractor screenshot into a PDF file

I'm currently working on a small Protractor code that captures screenshots, but my goal is to save these screenshots as PDF files. Below you can find the code snippet I have written. await browser.get(url); const img = await browser.takeScreenshot(); ...

Looking to alter the CSS of an ID element when hovering over a link on your website?

Irrespective of the positioning of the links in the html, a simple hover effect can trigger changes like switching images or altering backgrounds anywhere on the website. The ideal solution would involve a straightforward method without the need for Javas ...

React does not allow objects as child elements. Instead, render a collection of children by using an array

Encountering an error with this React class Error: Objects are not valid as a React child (found: object with keys {_id, name}). If you meant to render a collection of children, use an array instead. Is there anything amiss here? I am passing the movies ...

Using the HTTP DELETE method in Node.js

Is there a specific configuration required before sending DELETE requests to a node.js application? I am able to send GET, POST, and PUT requests successfully, but for some reason DELETE requests are not functioning. When I try DELETE http://localhost:80 ...

Accessing the current state outside of a component using React Context

As I delve into creating a React application, I find myself in uncharted territory with hooks and the new context API. Typically, I rely on Redux for my projects, but this time I wanted to explore the context API and hooks. However, I'm encountering s ...

Issue encountered when trying to remove an event while a dialog is closed in a React useEffect

While working with my open dialog, I attempted to include a 'key-down' event. Unfortunately, the event continues to trigger even after the dialog is closed. To address this issue, I encapsulated the event handling function within the useEffect h ...