先发制人:如何避免Node中的深度回调层次结构? (使用Express)

时间:2013-03-24 02:15:34

标签: javascript node.js callback express

我刚开始尝试使用node(使用Express构建一个带有MySql数据库的简单网站)。

我基本上运行了Express提供的应用程序结构(这对于这个问题无关紧要)。我有一个文件routes/index.js,它会导出每当我的主页发出请求时被点击的index函数。 index.js的内容是:

var db = require('../db');                                                      

exports.index = function(req, res){                                             
    db.getConnection(function(err, connection) {                                
        connection.query('SELECT * FROM test_table', function (err, rows) {     
            var templateVariables = {                                           
                title: 'Index Page',                                            
                response: rows[0].text                                          
            };                                                                  
            res.render('index', templateVariables);                             
        });                                                                     
        connection.end();                                                                                     
    });                                                                                                       
};   

这显然是一个非常初步和轻量级的示例,但是在索引页面的这个特定GET请求中,已经存在一组3深度的回调函数。每个callbuck必须存在于“parent”的回调中,因为它取决于结果(在顺序执行的语言/环境中,这将是显而易见的和微不足道的)。

我的问题是,在构建更复杂且可能非常大的应用程序时,如何避免出现大量嵌套回调函数的问题?当你对逻辑有顺序依赖时,这当然就是这种情况。我知道Node的理念是异步,但是当等待来自数据库的数据并说我们正在运行5个单独的查询时,那么呢?我们只是将单个多语句查询编写为原子单元吗?虽然这个问题不是数据库所独有的。

4 个答案:

答案 0 :(得分:2)

这里有关于这个问题的一般性讨论: http://callbackhell.com/

此外,许多人使用像Async这样的模块来管理流量控制问题。

答案 1 :(得分:2)

由于您使用Express提及,您可以使用next()作为回调的替代方法。

app.get('/',function first(req,res,next){
   res.write('Hello ');
   res.locals.user = req.user;
   next();
   //Execute next handler in queue
});

app.get('/',function second(req,res,next){
   res.write('World!');
   //Use res.locals.user
   res.end();
});

//response shows Hello World!

路由处理程序使用额外的参数next并按给定的顺序执行,直到其中一个返回响应。 next根本不接受任何参数,或者将错误作为参数。您可以在res.locals

中设置要传递给下一个函数的变量

答案 2 :(得分:1)

使用PromiseFuture库,例如Q(在npm上可用)。

引用Q的自述文件,承诺让你转过身来:

step1(function (value1) {
    step2(value1, function(value2) {
        step3(value2, function(value3) {
            step4(value3, function(value4) {
                // Do something with value4
            });
        });
    });
});

进入这个:

Q.fcall(step1)
.then(step2)
.then(step3)
.then(step4)
.then(function (value4) {
    // Do something with value4
}, function (error) {
    // Handle any error from step1 through step4
})
.done();

我见过回调地狱的其他解决方案引入了一些看似倒退的权衡。异步操作形成较大操作之间的自然逻辑边界,因此如果您考虑函数或以其他方式模块化这些边界,您将获得微步代码。

答案 3 :(得分:1)

我喜欢做的一种方式就是这样......

exports.index = function(req, res) {
  var connection

  db.getConnection(gotConnection)

  function gotConnection(err, _c) {
    connection = _c                                
    connection.query('SELECT * FROM test_table', gotData)
  }

  function gotData(err, rows) { 
    connection.end();        
    var templateVariables = {                                           
      title: 'Index Page', 
      response: rows[0].text     
    }
    res.render('index', templateVariables);
  }
});

你也应该总是在你的代码中处理错误,我假设你把它们遗漏了,以便在这里更容易阅读代码。