Express 中间件中的异步/等待

Dan*_*der 7 javascript middleware express async-await es6-promise

我无法理解如何在 Express 中正确编写使用 async/await 的中间件,但在执行后不会让 Promise 漂浮在以太坊中。我已经阅读了大量博客和 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();
}));
Run Code Online (Sandbox Code Playgroud)

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

Promise
  .resolve(fn(req, res, next))
  .catch(next)
Run Code Online (Sandbox Code Playgroud)

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

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

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

我的尝试性解决方案是只在非中间件函数中使用 async/await,然后在实际中间件中对返回的 Promise 调用 then(),这样我就可以确定我没有做任何顽皮的事情,如下所示:

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

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

jfr*_*d00 7

并且永远不要在这个返回的 Promise 上调用 then()。使用这种模式是因为我们不依赖中间件函数的任何返回值吗?

只返回 Promises 并且从不调用 then() 来获取它们的值是否可以,因为 Express 中的中间件没有返回有意义的值?

是的,如果您只想跟踪它是否因为它自己成功完成而被拒绝,但您需要单独处理错误,那么您可以使用.catch()它有效地执行您正在做的事情。这可以。


如果我经常这样做,我要么切换到像 Koa 这样的承诺友好框架,要么添加我自己的承诺感知中间件注册。例如,这是 Express 的一个附加组件,它为您提供了承诺感知中间件:

// 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);
}
Run Code Online (Sandbox Code Playgroud)

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

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

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


这是一个更高级的实现。把它放在一个名为的文件中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;
Run Code Online (Sandbox Code Playgroud)

然后,在您的项目中,而不是这样:

const express = require('express');
app.get(somePath, someFunc);
Run Code Online (Sandbox Code Playgroud)

用这个:

const express = require('./express-p.js');
app.getP(somePath, someFunc);
Run Code Online (Sandbox Code Playgroud)

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

 .useP()
 .allP()
 .getP()
 .postP()
 .deleteP()
 .optionsP()
Run Code Online (Sandbox Code Playgroud)

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


S.N*_*kib 6

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

const asyncHandler = fn => (req, res, next) => {
     fn(req, res, next)
     .catch(next);
}                                  
     
Run Code Online (Sandbox Code Playgroud)

我们不需要Promise.resolve()asyncHandler. 由于fn是一个async函数,因此它返回一个承诺。catch()如果函数内部出现错误,我们可以做出这样的承诺。

在这里,我们不会从asyncHandler函数返回任何内容,因为我们不需要。