0

我对异步编程还有些陌生,我一直想知道是否有更好的方法来完成我正在尝试做的事情。

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 语句中包含所有这些数据。我想如果我继续使用这种方法,我将需要更多的嵌套函数。有没有更好的方法来做到这一点?

4

2 回答 2

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
于 2013-02-11T03:46:55.783 回答
1

正如 JohnnyHK 建议的那样,一个好的异步库将帮助您解决这个问题。我还建议Caolan 的 asyncauto您可以使用和的组合解决几乎任何异步问题forEach,尽管如果运行时是一个问题,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“最终”函数。

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

于 2013-02-10T18:03:37.683 回答