Node.js Express 中的异步代码

Posted

技术标签:

【中文标题】Node.js Express 中的异步代码【英文标题】:Asynchronous code in Node.js Express 【发布时间】:2017-02-04 03:43:28 【问题描述】:

我正在尝试使用 Node.js Express 创建 MVC 模式,但这在执行异步代码时似乎是一项非常不可能的任务。

例如:我从模型中的 NeDB 数据库中获取结果,如下所示:

controllers/database.js

// Database management stuff with NeDB
var data = ;
db.findOne( _id: 1 , (error, doc) => 
    if (error) console.error(error);
    data = doc;

现在,我将在一个名为 dbcontroller.js 的控制器中使用它:

var nedb = require('../models/nedb.js');

module.exports.list = function(req, res, next) 
    res.render('listpage', 
        data: nedb.data,
    );

routes/routes.js 文件中:

var express = require('express');
var router = express.Router();
var dbcontroller = require('../controllers/dbcontroller.js');
// Other controllers...

router.get('/list', dbcontroller.list);
// Other route definitions...

module.exports = router;

很可爱的安排,不是吗? 现在,果然,NeDB 的 findOne() 函数是异步的,所以它会在 module.exports 之后执行,而那是不行的。想到的解决方案当然是将 module.exports 定义放在 findOne() 回调中!

db.findOne( _id: 1 , (error, doc) => 
    if (error) console.error(error);
    module.exports.data = data;

我确定这是某个地方的反模式,但它确实有效。或者是吗?真正的戏剧现在开始。

当它被控制器调用时,findOne() 函数也会在所有控制器逻辑之后完成,这意味着 nedb.data 将是未定义的。我现在必须做一些疯狂的特技......

database.js

module.exports.execute = function (callback) 
    db.findOne( _id: 1 , (error, doc) =>
    
        if (error) console.error(error);
        module.exports.data = doc;
        callback();
    );

dbcontroller.js

nedb.execute(export);

function export() 
    module.exports.list = function(req, res, next) 
        res.render('listpage', 
            data: nedb.data,
        );
    ;
;

而且情况变得更糟。 routes.js 文件现在找不到 dbcontroller.list,大概是因为它仍然是在 之后定义的路线要求它。现在,我是否也必须开始将路由定义放入回调中?

我的意思是,整个异步性似乎完全接管了我的代码结构。以前整洁的代码现在变得一团糟,因为我无法将代码放在它所属的地方。这只是 一个 异步函数,NeDB 有很多异步函数,虽然这很棒,但如果 findOne() 已经让我很头疼,我不知道我是否能处理它。

也许我真的别无选择,我必须弄乱我的代码才能让它工作?或者也许我错过了一些非常基本的东西?或者也许像 async 或 promises 这样的库会做我正在寻找的东西?你怎么看?

【问题讨论】:

【参考方案1】:

为了使异步代码工作,您需要通过回调、承诺或其他方式通知您何时完成。要使上述示例正常工作,您需要做的就是调用res.render,您确定data 已定义。为此,您可以从数据库获取中返回一个 Promise,或者向它传递一个回调。我建议使用 Promise,因为它们更容易使用,因为它们有助于避免 "callback hell",而且 Node 长期以来一直对它们提供原生支持。

controllers/database.js

module.exports.fetch = function() 
  return new Promise(function(resolve, reject) 
    db.findOne( _id: 1 , (error, doc) => 
      if (error) reject(error);
      resolve(doc);
   );

然后,在您的控制器中,您只需在呈现响应之前调用 fetch(或者您最终定义数据库获取代码)。

dbcontroller.js

var nedb = require('../models/nedb.js');

module.exports.list = function(req, res, next) 
  nedb.fetch().then((data) => 
    res.render('listpage', 
        data: nedb.data,
    );
  ).catch((err) =>  /* handle db fetch error */ );

要记住的是,express 不希望您立即致电 res.render。您可以执行大量异步代码,然后在完成后呈现响应。

【讨论】:

这并不是一个很好的例子,说明 Promise 比回调“容易得多”。你可以用更少的代码用回调做同样的事情。我没有 -1 你,但老实说,如果你放弃风格意见或证明为什么它更好,你的答案会更好。 如果你注意到这个问题,这不是关于回调与承诺的问题。我并没有试图证明承诺“容易得多”。但是 javascript 社区非常同意他们对“回调地狱”的帮助。这就是为什么他们有本地支持。问题是问什么异步方法会导致代码更简洁,我只是给出了我的意见。是的,很明显,如果你只有两个函数,回调将是更少的代码。但问题是关于为应用构建 MVC。 这就是为什么我没有 -1 你。我只是认为,如果不引入辩论并做出不受支持的断言,您的答案会更好。 我引用了这个问题。 “或者也许像 async 或 Promise 这样的库会做我正在寻找的东西?你怎么看?”针对这个简单的问题,我回答说“我建议使用 Promise”。如果我改用回调,并注意到我的代码与现代实践有多么不兼容,你会感到不安吗?只是说,我不觉得这是有成效的。 我并不难过,伙计,我只是建议一种方法来改进您的答案。如果你不想这样做,那就这样吧。但您似乎更容易接受我的建议,例如添加一个示例,说明为什么 Promise 方法更适合他的场景,或者忽略它,而不是对我的评论进行无效的辩护。我并没有说你对 Promises 的看法是错误的,只是如果你向这位新开发者展示为什么你的断言是正确的,你的例子会更好。【参考方案2】:

在回调函数中设置模块数据通常是不好的做法,因为require 会同步加载模块。 在模块之间传递 ES6 Promises 是消除过多回调需求的一种可能选择。

database.js

module.exports = new Promise(function (resolve, reject) 
    db.findOne( _id: 1 , (error, doc) => 
        if (error) reject(error);
        resolve(doc);
    );
);

dbcontroller.js

var nedb = require('../models/nedb.js');

module.exports.list = nedb.then(data => 
    return (req, res, next) =>  //return router callback function
        res.render('listpage',  data ) //ES6 shorthand object notation
    
).catch(/* error handler */);

可以在这个答案中找到进一步的指导:Asynchronous initialization of Node.js module

【讨论】:

我遇到的问题是 require()d 的代码以某种方式异步工作。我认为这不会发生,因为 require() 是同步的(如您向我展示的答案中所述),并且 node.js 在实际文件中启动代码之前加载整个模块确实很有意义......但仍然, database.js 中将数据加载到控制器的代码与控制器本身并行运行。我的 node.js 是否以某种方式损坏了? 我能解释的最佳方式:您的 database.js 正在导出一个 function 对象,该对象 同步 加载到 dbcontroller.js 中而不执行(否则nedb.execute(export) 行将返回 cannot find property 'execute' of undefined)。当database.js 函数通过dbcontroller.js 中的回调执行时,尽管已经定义了函数,但数据库获取仍然是异步。因此,跨模块需要多个回调/承诺。 我不能相信模块在当前代码之前完全加载,这太疯狂了。我想知道模块创建者如何避免让用户为他们的模块工作进行随机回调。

以上是关于Node.js Express 中的异步代码的主要内容,如果未能解决你的问题,请参考以下文章

带有 Express 异步 API 调用的 Node.JS

Node.js 异步等待快递

Node.js / Sequelize.js / Express.js - 如何插入多对多关联? (同步/异步?)

Node.js Express+Mongodb 项目实战

无法使用 express 和 multer 运行 node.js 服务器以在节点中上传文件

[js高手之路]Node.js+jade+express+mongodb+mongoose+promise实现todolist