2011-12-20 38 views
6

我是Node新手,并试图确保我为JSON驱动的Web应用程序使用了理智的设计。在Node.js中处理异步循环的最佳模式

我有一堆存储在Redis中的数据,并且正在通过节点检索它,当它们来自Redis时,将结果流出。下面是我在做什么一个很好的例子:

app.get("/facility", function(req, res) { 
    rc.keys("FACILITY*", function(err, replies) { 
     res.write("["); 
     replies.forEach(function (reply, i) { 
      rc.get(reply, function(err, reply) { 
       res.write(reply); 
       if (i == replies.length-1) { 
        res.write("]"); 
        res.end(); 
       } 
       else 
        res.write(","); 
      }); 
     }); 
    }); 
}); 

基本上我就要设置从Redis的键,然后要求每一个,流出来的结果为半手动创建JSON(串出来的Redis已经在JSON中)。现在,这很好地工作,但我不禁认为,我== respond.length-1有点不整齐?

我可以在Redis中用mget完成所有这些工作,但这并不是我试图获得它的重点;它是如何最好地处理forEach的异步循环,流式传输输出并正常关闭与res.end的连接,循环完成。

这是最好的方式,还是有更优雅的模式,我可以遵循?

+0

深嵌套函数回调我会使用异步.js库。 – BRampersad 2011-12-20 17:45:12

回答

6

上述代码可能无法达到您的预期。您将按顺序开始每个.get(),但它们可能不会按顺序回呼 - 因此结果可能会以任何顺序流出。如果您想流式传输结果而不是将其收集到内存中,则需要按顺序.get()

我认为caolan’s async library使这很容易。这里是你可以用它来获取序列中的每个项目的一种方式(警告,未经测试):

app.get("/facility", function(req, res) { 
    rc.keys("FACILITY*", function(err, replies) { 
     var i = 0; 
     res.write("["); 
     async.forEachSeries(replies, function(reply, callback){ 
      rc.get(reply, function(err, reply) { 
       if (err){ 
        callback(err); 
        return; 
       } 
       res.write(reply); 
       if (i < replies.length) { 
        res.write(","); 
       } 
       i++; 
       callback(); 
      }); 
     }, function(err){ 
      if (err) { 
       // Handle an error 
      } else { 
       res.end(']'); 
      } 
     }); 
    }); 
}); 

如果你不关心的顺序,只需使用async.forEach()代替。

如果你不介意收集的结果,并希望他们在序列回报,你可以使用async.map()像这样(警告,还未经测试):

app.get("/facility", function(req, res) { 
    rc.keys("FACILITY*", function(err, replies) { 
     async.map(replies, rc.get.bind(rc), function(err, replies){ 
      if (err) { 
       // Handle an error 
      } else { 
       res.end('[' + replies.join(',') + ']'); 
      } 
     }); 
    }); 
}); 
+0

太棒了;感谢代码;我尝试了异步库,它完美的工作。顺序无关紧要,但地图解决方案看起来更优雅,所以我可以坚持这一点。 – 2011-12-21 17:09:04

+0

我想解决map函数调用中的rc.get.bind(rc)参数是如何工作的;这是一个很好的做法。你能否解释一下关于它的工作原理? – 2011-12-21 17:28:55

+0

@mjs [bind](https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Function/bind)是ECMAScript 5的一部分,它返回一个函数的副本,该函数“绑定”到特别是'这个'值。在这种情况下,这意味着当async.map调用'get()'时,它将具有'rc'作为它的'this'值。 – s4y 2011-12-21 20:27:37

3

可以使用async库,它提供了一个循环的一些方便的方法,如的forEach:

的forEach(ARR,迭代器,回调)

在应用迭代函数的每个项目数组,并行。 迭代器在列表中调用一个项目,当它完成时调用 。如果迭代器将错误传递给此回调函数,forEach函数的主回调函数立即调用 并调用该错误。

请注意,由于此函数将迭代器应用于并行的每个项目,因此不能保证迭代器函数将按顺序完成 。

// assuming openFiles is an array of file names and saveFile is a function 
// to save the modified contents of that file: 

async.forEach(openFiles, saveFile, function(err){ 
    // if any of the saves produced an error, err would equal that error 
}); 
+0

我会看看那个图书馆;感谢指针。 – 2011-12-21 17:07:47

1

,但我不禁想到了我== replies.length-1是有点乱糟糟的?

我听说很多人都这么说。这是我会怎么做手工:

app.get("/facility", function(req, res, next) { 
    rc.keys("FACILITY*", function(err, replies) { 
    if (err) return next(err); 
    var pending = replies.length; 
    res.write("["); 
    replies.forEach(function (reply) { 
     rc.get(reply, function(err, reply) { 
     res.write(reply); 
     if (!--pending) { 
      res.write("]"); 
      return res.end(); 
     } 
     res.write(","); 
     }); 
    }); 
    }); 
}); 

手工显然这样做是不是太漂亮了,这就是为什么人们有它抽象成一个图书馆或其他一些功能。但是喜欢与否,这就是你如何做一个异步并行循环。 :)

您可以使用前面提到的async库来隐藏讨厌的内脏。

+0

这是一个更好的方法;感谢那。 – 2011-12-21 17:10:35