【问题标题】:Async/await in Nodejs + MongooseNodejs + Mongoose 中的异步/等待
【发布时间】:2020-01-31 00:22:49
【问题描述】:

我是 Promises 和 async/await 编程的新手,我不确定我是否理解正确。我正在使用 Express、Mongoose 和 MongoDB 在 Nodejs 中创建 API。 我看过很多关于如何处理异步性的教程,但它们都是关于路由和数据库查询在同一个文件中的 NodeJs 项目。 示例:

const asyncMiddleware = fn =>
  (req, res, next) => {
    Promise.resolve(fn(req, res, next))
      .catch(next);
};

router.get('/users/:id', asyncMiddleware(async (req, res, next) => {
    const something = await getSomethingFromDb({ id: req.params.id })
    res.json(something);
}));

但是,为了清楚起见,我已将路由与控制器分开,但我严重怀疑我是否正确地完成了它。这是我的代码:

router.js

const asyncMiddleware = fn =>
  (req, res, next) => {
    Promise.resolve(fn(req, res, next))
      .catch(next);
};

router.get('/something/:id', asyncMiddleware(async (req, res, next) => {
    const answer = await somethingController.findById(req, res, next)
}));

controller.js

exports.findById = async (req, res, next) => {
    const something = await Something.findById(req.params.id).exec();
    res.send(something);
};

我尝试使用 console.log() 来检查打印的内容,但我意识到,由于等待部分,整段代码将等待查询完成。 这实施得好吗?如何测试?

版本: NodeJs v10.16.3 猫鼬 v5.7.1

【问题讨论】:

  • 您可以(并且也许应该)将其简化为 router.get('/something/:id', asyncMiddleware(somethingController.findById));
  • 如果我这样做,它是否作为异步函数处理?我以为我总是必须在一开始就指定异步函数@Bergi

标签: javascript node.js express mongoose async-await


【解决方案1】:

就我个人而言,我会坚持对我的异步函数使用 try-catch,而不是使用中间件。这可能类似于以下内容:

module.exports.view = async function(req, res, next) {
  try {
    var something = await Something.findById(req.params.id).orFail();
    res.send(something);
  } catch (err) {
    next(err);
  }
};

那么在你的路由器中它就是:

router.get('/something/:id', somethingController.view);

这样做将允许您在 next(err) 将其发送到全局错误处理程序之前,操纵和处理该控制器操作的特殊错误情况。

现在我不确定这是否是您所期待的,但您可能想在查询时致电.orFail()。这样,如果找不到具有指定 ID 的“某物”,则会引发错误。否则something 变量将为空。但这完全取决于您希望 API 如何运行。

另外,当你定义一个async函数时,基本上就是说该函数返回一个Promiseawait 语句基本上告诉程序暂停函数,直到承诺得到解决。在此示例中,await 将等待数据库返回结果,然后再继续控制器操作。 Express 将神奇地处理等待您的 async 路由方法和中间件。

【讨论】:

    【解决方案2】:

    首先,您不需要“asyncMiddleware”。让我举一个完整的例子来说明如何分离路由和控制器,同时保持控制器异步:

    控制器

    exports.findById = async (req, res, next) => {
        try{
           const something = await Something.findById(req.params.id).exec();
           res.send(something);
        }catch(err){
           return res.status(500).send({
            message: err.message
          })
        }   
    };
    

    您应该将 async 调用包装在 try/catch 块中。

    路线

    然后你可以像这样在你的路由中调用你的控制器:

    router.get('/:id', Controller.findByID)
    

    就是这样。您不需要在您的路线上拨打任何额外的async 电话。

    如果你有中间件你想添加你的路由,你可以这样做:

    //for single middleware
    router.get('/:id',somethingMiddle,Controller.findByID)
    
    //for multiple middleware
    router.get('/:id',[somethingMiddle, anotherMiddle],Controller.findByID)
    

    如果这有帮助,请告诉我

    【讨论】:

    • 首先非常感谢您的回答。话虽如此,我已经读到中间件用于避免在每个函数中都有一个 try/catch 实例。这样你就可以把它放在路由中而忘记它,而代码看起来更有条理和简单。为什么不建议这样做?
    • 而且,我还看到一些文章没有使用 mongoose db 查询末尾的“.exec()”。然而,mongoosejs.com/docs/promises.html 说它是成熟的承诺所必需的。 Mongoose 查询默认是异步的吗?还是实际需要 .exec()?
    • 默认情况下它们不是异步的,您必须将查询放入异步函数然后await 结果。正如文档所说,您可以使用.exec() 来获得成熟的承诺,如果您愿意,可以使用.then().catch
    • 我明白,但我的问题是:如果我不使用 .exec() 或 .then() 会发生什么?它们是否仍然是异步的,因为函数是用 async 声明的,我正在等待结果?这就是应用异步所需的全部内容吗?
    • 所以,1. 请记住,当您使用await 时,您正在等待承诺的结果。 2. 当您使用 async 声明并等待承诺的响应时,它是异步的,并且要使查询成为完整的承诺,您可以使用 .exec()
    猜你喜欢
    • 1970-01-01
    • 2018-06-11
    • 1970-01-01
    • 2020-03-31
    • 1970-01-01
    • 1970-01-01
    • 2021-04-16
    • 2019-07-20
    • 2020-11-05
    相关资源
    最近更新 更多