在函数中使用多个回调结果的更优雅的方法?

时间:2013-02-10 16:51:20

标签: node.js asynchronous callback

我对异步编程还有点新意,我一直想知道是否有更好的方法来完成我想要做的事情。

exports.content = function(req, res){
  OPID = req.params.id;

  titles.findOne({postID: OPID}, function (err, post) { //function #1
    if (err) throw(err); 

    readComment(OPID, function(comment){ //function #2

      branchFilter.getBranches(function(branches){ //function #3

        res.render('content', {title: post.title, content: post.body, OPID: post.postID, comments: comment, branches: branches});
      })
    });
  });
};

在这个例子中,我有三个嵌套函数,它使用回调来从其他模块中检索数据。我需要在res.render语句中包含所有这些数据。我想如果我继续这种方法,我将需要更多的嵌套函数。有没有更好的方法呢?

2 个答案:

答案 0 :(得分:2)

例如,有一个只获取数据的函数

exports.content = function(req, res){
   getData(req, function(err, data) { 
   // trivial error handling
   if (err) {
      console.dir('error getting your data', err);
      return res.redirect('/');
   }
   res.render('content', data)
}

// cb is a callback function
function getData(req, cb) {
  OPID = req.params.id;
  titles.findOne({postID: OPID}, function (err, post) { //function #1
    if (err) { return cb(err); }
    readComment(OPID, function(err, comment){ //function #2 (with error handling)
      if (err) { return cb(err); }
      branchFilter.getBranches(function(err, branches){ //function #3
        if (err) { return cb(err); }
        var output = {
          title: post.title, 
          content: post.body, 
          OPID: post.postID, 
          comments: comment, branches: branches
        }
        return cb(null, output);
      });
    });
  });
}

看看http://callbackhell.com/给出了一个非常好的概述,即如何使用写清洁代码 回调。从该站点复制以下内容

命名您的职能

这是一些(杂乱的)浏览器javascript,它使用浏览器请求向服务器发出AJAX请求:

var form = document.querySelector('form')
form.onsubmit = function(submitEvent) {
  var name = document.querySelector('input').value
  request({
    uri: "http://example.com/upload",
    body: name,
    method: "POST"
  }, function(err, response, body) {
    var statusMessage = document.querySelector('.status')
    if (err) return statusMessage.value = err
    statusMessage.value = body
  })
}

此代码有两个匿名函数。让我们给出名字!

var form = document.querySelector('form')
form.onsubmit = function formSubmit(submitEvent) {
  var name = document.querySelector('input').value
  request({
    uri: "http://example.com/upload",
    body: name,
    method: "POST"
  }, function postResponse(err, response, body) {
    var statusMessage = document.querySelector('.status')
    if (err) return statusMessage.value = err
    statusMessage.value = body
  })
}

正如您所看到的,命名函数非常简单,并为您的代码做了一些不错的事情:

  • 使代码更易于阅读
  • 当异常发生时,您将获得引用实际函数名称的堆栈跟踪而不是"匿名"
  • 允许您保持代码浅,或者不深入嵌套,这让我想到了下一点:

保持代码浅显

在最后一个例子的基础上,让我们更进一步,摆脱代码中正在进行的三级嵌套:

function formSubmit(submitEvent) {
  var name = document.querySelector('input').value
  request({
    uri: "http://example.com/upload",
    body: name,
    method: "POST"
  }, postResponse)
}

function postResponse(err, response, body) {
  var statusMessage = document.querySelector('.status')
  if (err) return statusMessage.value = err
  statusMessage.value = body
}

document.querySelector('form').onsubmit = formSubmit

这样的代码看起来不那么可怕,以后更容易编辑,重构和破解。

组件化!

这是最重要的部分:任何人都能够创建模块(AKA库)。引用Isaac Schlueter(node.js项目):"编写每个做一件事的小模块,并将它们组装成其他更重要的模块。如果你不去那里,你就无法进入回调地狱。"

让我们从上面取出样板代码,然后通过将其分成几个文件将其转换为模块。由于我在浏览器和服务器上编写JavaScript,我将展示一种既适用又兼顾的方法,但仍然很简单。

这是一个名为formuploader.js的新文件,它包含我们之前的两个函数:

function formSubmit(submitEvent) {
  var name = document.querySelector('input').value
  request({
    uri: "http://example.com/upload",
    body: name,
    method: "POST"
  }, postResponse)
}

function postResponse(err, response, body) {
  var statusMessage = document.querySelector('.status')
  if (err) return statusMessage.value = err
  statusMessage.value = body
}

exports.submit = formSubmit

答案 1 :(得分:1)

正如JohnnyHK建议的那样,一个好的异步库可以帮助你解决这个问题。我还建议Caolan's async。您可以使用autoforEach的组合解决几乎所有异步问题,但如果运行时出现问题async可能不是最佳选择。我会按如下方式重写您的代码:

exports.content = function(req, res){
  OPID = req.params.id;

  async.auto({
    post: function(next) {
      titles.findOne({postID: OPID}, next);
    },
    comment: function(next) {
      readComment(OPID, function(comment){ next(null, comment); });
    },
    branches: function(next) {
      branchFilter.getBranches(function(branches){ next(null, branches); });
    }
  }, function(err, results) {
    if(err) throw(err);
    res.render('content', {title: results.post.title, content: results.post.body, OPID: results.post.postID, comments: results.comment, branches: results.branches});
  });
};

auto所做的是获取函数字典(可选依赖于字典中的其他函数 - 请参阅异步自述文件以获取更多信息)并将结果作为字典传递给第二个参数,即“最终”功能

如果任何函数将非空err传递给其回调(第一个参数),它将停止调用其他函数并立即将errnull结果传递给“最终”功能。

请注意我必须在branchescomment函数中传递匿名函数。这是因为auto与此async库中的其他函数一样,期望其回调的第一个参数是错误值,第二个参数是结果。