Async/Await 在 Express 中间件中

Async/Await in Express Middleware

我无法理解如何在使用 async/await 的 Express 中正确编写中间件,但在执行后不会在以太坊中留下 Promise。我阅读了大量的博客和 Whosebug 帖子,似乎对于在 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();
}));

我知道这使得不必在所有 aysnc 路由处理程序中使用 try..catch 逻辑成为可能,并且它确保由 (async (req) 编辑的 Promise return , res, next) => {}) 函数已解决,但我的问题是我们正在 returning 来自 asyncHandler 的 Promise.resolve() 调用的 Promise:

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

永远不要在这个 returned Promise 上调用 then()。使用此模式是因为我们不依赖中间件函数的任何 returned 值吗?只 return Promise 并且从不调用它们的 then() 来获取它们的价值是否可以,因为在 Express 中没有从中间件 return 编辑的有意义的值?

我知道 async/await 允许我们处理异步代码并轻松处理 returned 值,但在 Express 中间件中,我们只剩下顶级异步,它解决了到一个 Promise,然后我们用 Promise.resolve() 解析它,但它仍然解析为一个 Promise...

另外,我知道这个问题有 3rd 方解决方案,你可以使用另一个框架,比如 Koa。我只是想了解如何在 Express 中正确执行此操作,因为我对使用 Node 进行后端开发还比较陌生,并且想只专注于 Express,直到我掌握了基础知识。

我的初步解决方案是只在非中间件函数中使用 async/await,然后在实际中间件中调用 returned Promise 上的 then(),这样我就可以确保我没有做任何顽皮的事情,例如:

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

这对我来说很好,但我一直看到到处都是 asyncWrapper 代码。我是不是想多了?

And never calling then() on this returned Promise. 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?

是的,如果你只想跟踪它是否被拒绝,因为它处理了自己的成功完成,但你需要单独处理一个错误,那么你可以只使用 .catch() 这是有效的你在做什么。这很好。


如果我经常这样做,我要么切换到像 Koa 这样的 promise 友好框架,要么添加我自己的 promise-aware 中间件注册。例如,这里有一个 Express 的附加组件,可以为您提供 promise 感知的中间件:

// 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);
}

然后,要使用这个 promise-aware 中间件注册,您只需像这样注册它:

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

而且,任何被拒绝的承诺都会自动为您处理。


这是一个更高级的实现。将其放入名为 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;

然后,在你的项目中,而不是这个:

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

使用这个:

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

然后,您可以自由使用这些方法中的任何一种,它们会自动处理从路由返回的被拒绝的承诺:

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

在您创建的应用程序对象或您创建的任何路由器对象上。此代码修改了原型,因此您在加载此模块后创建的任何应用程序对象或路由器对象将自动拥有所有这些承诺感知方法。

你做的绝对没问题。但是对于那些想太多的人来说,有一个简单的解决方案。只需重写 asyncHandler.

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

我们不需要在 asyncHandler 上使用 Promise.resolve()。由于 fn 是一个 async 函数,因此它 returns 是一个承诺。如果函数内部有错误,我们可以 catch() 承诺。

这里我们没有从 asyncHandler 函数返回任何东西,因为我们不需要。

您可以为此使用 lib express-async-errors。它可以毫无问题地修补 express