【问题标题】:The best pattern for handling async looping in Node.js在 Node.js 中处理异步循环的最佳模式
【发布时间】:2012-01-24 15:49:09
【问题描述】:

我是 Node 新手,并试图确保我为 JSON 驱动的 Web 应用程序使用合理的设计。

我有一堆数据存储在 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 的字符串已经在 J​​SON 中)。现在这很好用,但我不禁认为 i == replies.length-1 有点不整洁?

我可以在 Redis 中使用 mget 来完成所有这些工作,但这并不是我真正想要实现的重点;这是使用 forEach 处理异步循环的最佳方式,流式传输输出并在循环完成后优雅地关闭与 res.end 的连接。

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

【问题讨论】:

  • 对于深层嵌套函数回调,我会使用 async.js 库。

标签: javascript node.js redis


【解决方案1】:

上面的代码可能不符合您的预期。您将按顺序启动每个 .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(',') + ']');
            }
        });
    });
});

【讨论】:

  • 太好了;感谢您的代码;我尝试了异步库,它运行良好。顺序无关紧要,但地图解决方案看起来要优雅得多,所以我可能会坚持下去。
  • 我正在尝试弄清楚 map 函数调用中的 rc.get.bind(rc) 参数是如何工作的;这是一种很好的方式。你能解释一下它是如何工作的吗?
  • @mjs bind 是 ECMAScript 5 的一部分,它返回一个“绑定”到特定 this 值的函数的副本。在这种情况下,这意味着当 async.map 调用 get() 时,它将以 rc 作为其 this 值。
  • @mjs 通常,函数中this 的值取决于你如何调用它——调用函数on 对象(如rc.get())会设置函数内部this 的值改为rc。这是在通话时确定的。如果您要执行var cb = rc.get; cb() 之类的操作(这是将其作为参数传递给async.map 的效果),它将在没有rc 的上下文的情况下运行。同样,如果您要执行var foo = {}; foo.get = rc.get; foo.get();get 将使用foo 作为其上下文,而不是rc,并且可能会表现得非常非常糟糕。
  • 感谢扩展的解释;欣赏它。
【解决方案2】:

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

forEach(arr, 迭代器, 回调)

将迭代器函数并行应用于数组中的每个项目。 使用列表中的项目和回调调用迭代器 当它完成时。如果迭代器将错误传递给 this 回调,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
});

【讨论】:

    【解决方案3】:

    但我不禁认为 i == 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 库来隐藏讨厌的内脏。

    【讨论】:

      猜你喜欢
      • 2015-02-11
      • 2015-02-27
      • 1970-01-01
      • 1970-01-01
      • 2012-04-04
      • 1970-01-01
      • 1970-01-01
      • 2011-11-10
      相关资源
      最近更新 更多