快速中间件中的异步/等待

时间:2020-04-07 18:24:39

标签: javascript express async-await es6-promise middleware

我无法理解如何在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();
}));

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

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

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

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

此外,我了解到存在针对此问题的第三方解决方案,您可以使用Koa之类的其他框架。我只想了解如何在Express中正确执行此操作,因为我对使用Node进行后端开发还比较陌生,并且只想专注于Express,直到我掌握基础知识为止。

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

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

这对我很好,但是我到处都看到asyncWrapper代码。我想得太对了吗?

2 个答案:

答案 0 :(得分:4)

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

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

是的,如果您要跟踪的只是它是否由于成功完成而被拒绝,但是您需要单独处理错误,那么您可以使用.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);
}

然后,要使用此可识别承诺的中间件注册,您只需像这样注册它:

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()

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

答案 1 :(得分:0)

您在做什么绝对可以。但是对于那些想太多的人,有一个简单的解决方案。只需重写asyncHandler

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

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

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