简体   繁体   English

Express 中间件中的异步/等待

[英]Async/Await in Express Middleware

I'm having trouble understanding how to properly write middleware in Express that utilizes async/await, but doesn't leave a Promise floating in the ether after it's execution.我无法理解如何在 Express 中正确编写使用 async/await 的中间件,但在执行后不会让 Promise 漂浮在以太中。 I've read a ton of blogs and StackOverflow posts, and it seems like there is some consensus around using the following pattern in async/await middleware:我已经阅读了大量的博客和 StackOverflow 帖子,似乎对于在 async/await 中间件中使用以下模式达成了一些共识:

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 makes it possible to not have to use try..catch logic in all of your aysnc route-handlers, and that it ensures that the Promise returned by the (async (req, res, next) => {}) function is resolved, but my issue is that we are returning a Promise from the asyncHandler's Promise.resolve() call:我知道这使得不必在所有 aysnc 路由处理程序中使用 try..catch 逻辑成为可能,并确保 (async (req, res, next) => {}) 返回的 Promise function 已解决,但我的问题是我们正在从 asyncHandler 的 Promise.resolve() 调用返回 Promise:

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

And never calling then() on this returned Promise.并且永远不要在返回的 Promise 上调用 then()。 Is this pattern used because we aren't relying on any returned value from middleware functions?使用这种模式是因为我们不依赖中间件函数的任何返回值吗? Is it OK to just return Promises and never call then() on them to get their value, since there is no meaningful value returned from middleware in Express?是否可以只返回 Promises 而从不调用 then() 来获取它们的值,因为 Express 中的中间件没有返回有意义的值?

I get that async/await allows us to deal with the async code and work with the returned values easily, but in Express middleware we are left with that top-level async, which resolves to a Promise, which we then resolve with Promise.resolve(), but which still resolves to a Promise...我知道 async/await 允许我们处理异步代码并轻松处理返回值,但在 Express 中间件中,我们只剩下顶级异步,它解析为 Promise,然后我们使用 Promise.resolve 解析(),但仍解析为 Promise...

Also, I understand that there are 3rd party solutions to this issue, and you could just use another framework like Koa.另外,我知道这个问题有 3rd 方解决方案,你可以使用另一个框架,比如 Koa。 I just want to understand how to do this properly in Express, as I'm still relatively new to backend development with Node and want to focus solely on Express till I get the fundamentals down.我只是想了解如何在 Express 中正确执行此操作,因为我对使用 Node 进行后端开发还比较陌生,并且希望只专注于 Express,直到我掌握了基础知识。

My tentative solution has been to use async/await only in non-middleware functions, and then just call then() on the returned Promises in the actual middleware, so that I can be sure I'm not doing anything naughty, like so:我暂定的解决方案是仅在非中间件函数中使用 async/await,然后在实际中间件中对返回的 Promises 调用 then() ,这样我就可以确定我没有做任何顽皮的事情,例如:

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

Which is fine with me, but I keep see the asyncWrapper code all over the place.这对我来说很好,但我一直在到处看到 asyncWrapper 代码。 I'm over-thinking this right?我想太多了对吧?

And never calling then() on this returned Promise.并且永远不要在返回的 Promise 上调用 then()。 Is this pattern used because we aren't relying on any returned value from middleware functions?使用这种模式是因为我们不依赖中间件函数的任何返回值吗?

Is it OK to just return Promises and never call then() on them to get their value, since there is no meaningful value returned from middleware in Express?是否可以只返回 Promises 而从不调用 then() 来获取它们的值,因为 Express 中的中间件没有返回有意义的值?

Yes, if all you want to track is whether it was rejected or not because it handles its own successful completion, but you need to handle an error separately, then you can just use .catch() which is effectively what you're doing.是的,如果您只想跟踪它是否被拒绝,因为它处理自己的成功完成,但您需要单独处理错误,那么您可以使用.catch()这实际上是您正在做的事情。 This is fine.这可以。


If I was doing this a lot, I'd either switch to a promise-friendly framework like Koa or I'd add-on my own promise-aware middleware registration.如果我经常这样做,我会切换到像 Koa 这样的承诺友好型框架,或者我会添加自己的承诺感知中间件注册。 For example, here's an add-on to Express that gives you promise-aware middleware:例如,下面是 Express 的一个附加组件,它为您提供了 Promise-aware 中间件:

// promise aware middleware registration
// supports optional path and 1 or more middleware functions
app.useP = function(...args) {
    function wrap(fn) {
        return async function(req, res, next) {
            // catch both synchronous exceptions and asynchronous rejections
            try {
                await fn(req, res, next);
            } catch(e) {
                next(e);
            }
        }
    }
    
    // reconstruct arguments with wrapped functions
    let newArgs = args.map(arg => {
        if (typeof arg === "function") {
            return wrap(arg);
        } else {
            return arg;
        }
    });
    // register actual middleware with wrapped functions
    app.use(...newArgs);
}

Then, to use this promise-aware middleware registration, you would just register it like this:然后,要使用这个可感知承诺的中间件注册,您只需像这样注册它:

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

And, any rejected promise would automatically be handled for you.而且,任何被拒绝的 promise 都会自动为您处理。


Here's a more advanced implementation.这是一个更高级的实现。 Put this in a file called express-p.js :把它放在一个名为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) {
                // catch both synchronous exceptions and asynchronous rejections
                try {
                    await fn(req, res, next);
                } catch(e) {
                    next(e);
                }
            }
        }

        // reconstruct arguments with wrapped functions
        let newArgs = args.map(arg => {
            if (typeof arg === "function") {
                return wrap(arg);
            } else {
                return arg;
            }
        });
        // register actual middleware with wrapped functions
        this[verb](...newArgs);
    }
}

// modify prototypes for app and router
// to add 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;

Then, in your project, instead of this:然后,在您的项目中,而不是这样:

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

use this:用这个:

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

Then, you can freely use any of these methods and they automatically handle rejected promises returned from routes:然后,您可以自由使用这些方法中的任何一种,它们会自动处理从路由返回的被拒绝的 Promise:

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

On either an app object you create or any router objects you create.在您创建的应用程序 object 或您创建的任何路由器对象上。 This code modifies the prototypes so any app object or router objects you create after you load this module will automatically have all those promise-aware methods.此代码修改原型,因此您在加载此模块后创建的任何应用程序 object 或路由器对象将自动具有所有这些承诺感知方法。

What you are doing is absolutely Ok.你在做什么是绝对好的。 But for those who overthink, there is a simple solution.但是对于那些想太多的人来说,有一个简单的解决方案。 Just re-write the asyncHandler .只需重新编写asyncHandler

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

We don't need to use Promise.resolve() on the asyncHandler .我们不需要在 asyncHandler 上使用asyncHandler Promise.resolve() Since fn is an async function, it returns a promise.由于fnasync function,因此它返回 promise。 We can just catch() that promise if there is an error inside the function.如果 function 内部有错误,我们可以只catch()那个 promise。

And here we are not returning anything from asyncHandler function since we don't need to.在这里我们没有从asyncHandler function 返回任何东西,因为我们不需要。

You can use lib for this express-async-errors .您可以将 lib 用于此express-async-errors It patchs express with no problems.它修补express没有问题。

S.Nakib has a great answer. S.Nakib 有一个很好的答案。 I included the asyncHandler in the utils folder of my project which I use across all files and use it in the async middlewares我将 asyncHandler 包含在我的项目的 utils 文件夹中,我在所有文件中使用它并在异步中间件中使用它

utils.js contains utils.js 包含

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

other.js contains other.js 包含

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

app.js contains app.js 包含

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

This is worked for me as I'm working with routes and just a few middlewares are async这对我有用,因为我正在使用路由并且只有几个中间件是异步的

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM