Using async/await with Middleware in Express

I'm struggling to grasp the concept of writing middleware in Express that uses async/await without leaving a floating Promise after execution. Despite reading numerous blogs and StackOverflow posts, it appears that there is a common pattern for utilizing async/await middleware:

const asyncHandler = fn => (req, res, next) =>
  Promise
    .resolve(fn(req, res, next))
    .catch(next)

app.use(asyncHandler(async (req, res, next) => {
  req.user = await User.findUser(req.body.id);
  next();
}));

I understand that this approach eliminates the need for try..catch logic in all async route-handlers, and ensures the resolution of Promises returned by (async (req, res, next) => {}) function. However, my concern lies with returning a Promise from the asyncHandler's Promise.resolve() call:

Promise
  .resolve(fn(req, res, next))
  .catch(next)

We never call then() on this returned Promise. Is this pattern used because we don't depend on any return values from middleware functions? Is it acceptable to simply return Promises without calling then() to access their value since middleware in Express doesn't typically return meaningful results?

While async/await simplifies managing asynchronous code and handling return values, the top-level async in Express middleware still results in a Promise, which is then resolved using Promise.resolve(), but still ends up as a Promise...

Although third-party solutions exist for this issue and alternative frameworks like Koa can be used, I am focused on understanding the proper way to handle this in Express as I am new to backend development with Node and want to master the fundamentals.

My current approach involves using async/await only outside of middleware functions, and then chaining then() in actual middleware calls to ensure proper handling, such as:

app.use((req, res, next) => {
  User.findUser(req.body.id)
    .then(user => {
      req.user = user;
      next();
    })
    .catch(next)
});

This method works fine for me, but I keep encountering the asyncWrapper code frequently. Am I overcomplicating things here?

Answer №1

Is the pattern of never calling then() on the returned Promise intentional? Could this be because middleware functions in Express do not return any meaningful value?

Would it be acceptable to simply return Promises without chaining then() to access their value, considering that middleware in Express does not provide any significant return value?

Absolutely, if your main concern is whether the Promise was rejected or not upon successful completion and you are handling errors separately using .catch(), then what you are doing is completely fine.


If this pattern is recurring for you, one possible solution would be switching to a promise-friendly framework like Koa, or alternatively implementing your own promise-aware middleware registration in Express. Below is an example of how you could create promise-aware middleware registration:

// Example of promise aware middleware registration
// Supports optional path and one or more middleware functions
app.useP = function(...args) {
    function wrap(fn) {
        return async function(req, res, next) {
            // Handling both synchronous exceptions and asynchronous rejections
            try {
                await fn(req, res, next);
            } catch(e) {
                next(e);
            }
        }
    }
    
    let newArgs = args.map(arg => {
        if (typeof arg === "function") {
            return wrap(arg);
        } else {
            return arg;
        }
    });
    
    app.use(...newArgs);
}

To utilize this promise-aware middleware registration, you can register it as follows:

app.useP(async (req, res, next) => {
  req.user = await User.findUser(req.body.id);
  next();
});

The above code automatically handles any rejected promises for you.


For a more advanced implementation, save the following code snippet in a file named express-p.js:

const express = require('express');

// Promise-aware handler substitute
function handleP(verb) {
    return function (...args) {
        function wrap(fn) {
            return async function(req, res, next) {
                // Handling both sync exceptions and async rejections
                try {
                    await fn(req, res, next);
                } catch(e) {
                    next(e);
                }
            }
        }

        let newArgs = args.map(arg => {
            if (typeof arg === "function") {
                return wrap(arg);
            } else {
                return arg;
            }
        });
        
        this[verb](...newArgs);
    }
}

// Modifying prototypes for app and router
// Adding useP, allP, getP, postP, optionsP, deleteP variants
["use", "all", "get", "post", "options", "delete"].forEach(verb => {
    let handler = handleP(verb);
    express.Router[verb + "P"] = handler;
    express.application[verb + "P"] = handler;
});

module.exports = express;

Instead of simply writing:

const express = require('express');
app.get(somePath, someFunc);

You should include the modified module as shown below:

const express = require('./express-p.js');
app.getP(somePath, someFunc);

This will allow you to easily make use of these methods which automatically handle rejected promises from routes:

 .useP()
 .allP()
 .getP()
 .postP()
 .deleteP()
 .optionsP()

These alterations extend to any app object or router objects generated after loading this module, equipping them with all promise-aware methods discussed above.

Answer №2

It's totally fine what you're doing right now. However, for those who tend to overthink things, there is a straightforward solution. Simply modify the asyncHandler.

const asyncHandler = fn => (req, res, next) => {
     fn(req, res, next)
     .catch(next);
}                                  
     

There's no need to use Promise.resolve() within the asyncHandler. Since fn is an async function, it inherently returns a promise. By just using catch(), we can handle any errors that may occur within the function.

Furthermore, in this case, we are not required to return anything from the asyncHandler function as it is unnecessary.

Answer №3

It seems like S.Nakib has a top-notch solution. I made sure to add the asyncHandler in the utils directory of my project, allowing me to easily access it in various files and use it for async middlewares.

The file utils.js houses the following code snippet:

const asyncHandler = fn => (req, res, next) => {
 fn(req, res, next).catch(next)
}

In another file named other.js, you can find this asynchronous function:

async function someAuthAsyncFunction(req, res, next) {
  await callToOtherService()
  next()
}

Finally, within app.js, I utilized the asyncHandler as follows:

app.use(utils.asyncHandler(other.someAuthAsyncFunction))

This approach has served me well, especially when dealing with routes where only a handful of middlewares require asynchronous handling.

Answer №4

One solution is to utilize a library called express-async-errors. This library seamlessly integrates with express without any issues.

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 could be the reason for the data being retrieved but not showing up on the web page?

When fetching data from an API, I encounter a problem where the Loader displays but the data never shows up on the page. The inconsistency of this behavior is puzzling to me. This issue seems to be more prevalent on smartphones than on computers. useEffec ...

I am experiencing difficulty retrieving a specific item from my Sqlite database using the Express application

I'm in the process of developing an application where users have the ability to create posts and interact by commenting on them. The functionality for creating, updating, and deleting posts is working properly, as well as creating comments. When a us ...

Get the image resolution at the same time

I need a function that can take a URL pointing to an image as a parameter and synchronously provide the resolution of that image. The function should pause execution until the image is fully retrieved. Here is an example of what I'm looking for: func ...

How can we transfer parameters in JavaScript?

My vision may be a bit vague, but I'll try to explain it as best as I can. I want to implement multiple buttons that can toggle the visibility of a div (I have this functionality working already). Each button should carry two values (a number and a l ...

Issues with the event firing in the Polymer component for google-signin?

I recently set up a new starter-kit project using polymer-cli 1.7 and I'm attempting to incorporate Google authentication utilizing the google-signin element. Although the sign in button appears and works for signing in, the signedIn property isn&apo ...

After running the command "npx/npm create-react-app hello" to create a react app, I received the following message

Whenever I try to execute this command for creating a React app: C:\WINDOWS\system32> npm i create-react-app -g hello I receive the following message in my cmd prompt: npm WARN deprecated <a href="/cdn-cgi/l/email-protection" class="__cf ...

enhanced labeling for checkboxes in Vuetify

Below is a snippet of my straightforward code: <v-checkbox v-model="rodo" label="I consent to Terms and Conditions (click for more info)" :rules="termsRules" required ></v-checkbox> I am looking to keep the label simple with an option ...

Tips for passing a JavaScript array to an MVC controller

In my asp.net application, I have implemented a functionality where users can order food. Additionally, there is an admin page where admins can login and create a menu for a specific week. I have successfully developed the "Menu maker" feature where all me ...

"Differences between a .js server, webpack, and how to plan

I'm feeling a bit overwhelmed. I recently started delving into node.js with the MEAN stack after previously using webpack and browserify without fully grasping their concepts. What's really puzzling me is the following: Express starts a server ...

Ways to combine two collections into a single MongoDB query

In my database, there is a model called Param which is related to another model called Port. When I fetch data from the Param model, I can access all the properties of the related Port model. Among the many properties, I will highlight the following: //Por ...

Creating a POST Endpoint in Express JS

Hey there! Can someone help me out with creating a basic login script for an app using Express JS? I've been working on a POST function to handle this task, but unfortunately, when I try to echo back the parameters being passed (testing via Postman), ...

Transforming a d3 line chart into a bar chart

Beginner in D3 seeking assistance. I have successfully created a line chart that meets my requirements. Below is the code I used, with some modifications, mostly inspired by this source: <!DOCTYPE html> <meta charset="utf-8"> <sty ...

Articles related to Express.js - encountering issues when attempting to read properties from a null object

I'm trying to display related articles based on categories using Mongoose for querying data from MongoDB. However, when I attempt the code below, I encounter an error message stating "TypeError: Cannot read properties of null (reading 'category& ...

Submitting an array of objects via HTTP PUT

Struggling to find a way to update a list of Objects on MongoDB in one method call within the Submit button. Spent all day yesterday attempting, but it seems to be unsuccessful. The goal is to update all product sells in the database by entering values for ...

Generating views for individual models in a backbone collection

Currently, I am developing a small backbone.js application that simulates a library where CRUD operations can be performed. The core components of this application are the book model and the library collection (which stores books). var Book = Backbone.Mod ...

Issue encountered when attempting to execute Node.js Ubuntu project on a Mac operating system:

I am currently utilizing Node.js, Express.js, and sqlite3 to build a small website. Originally developed on Ubuntu, I have been attempting to make it compatible with Mac as well. However, upon checking out my project folder from the repository in Mac and ...

Ways to selectively deactivate client-side functionality

I have implemented a server-side rendered app with transitions, including a 404 error page that I placed in a lazy module to avoid increasing the size of loaded JavaScript. While this setup is functioning correctly, there is some flickering when the clien ...

Issue with using localStorage in NodeJs routes.js

I'm working on my NODEjs application using Express and I need to access the Country Code inside routes.js, but I'm facing difficulty accessing local storage within this file. Can someone suggest a solution for this issue? ...

Using dataloader on ammap effectively involves importing the necessary data into the platform

I've been attempting to implement the dataloader feature on ammap without success. Here is my current approach: var birth_map = AmCharts.makeChart( "il_bazinda_dogum_say_dagilim", { "type": "map", "data": { ...

Creating a phonecat application using Angular on a node server

As a newcomer to Angular, I am currently in the process of setting up the Angular phonecat application. To begin with, I downloaded the code from this location: https://docs.angularjs.org/tutorial/ After that, I installed Node.js on my system. Now, I a ...