简体   繁体   English

在Node.js中处理异步循环的最佳模式

[英]The best pattern for handling async looping in Node.js

I'm new to Node and trying to ensure that I'm using sane designs for a JSON-driven web app. 我是Node的新手,并试图确保我使用理智的设计来构建JSON驱动的Web应用程序。

I've got a bunch of data stored in Redis and am retrieving it through node, streaming out the results as they come from Redis. 我有一堆数据存储在Redis中,我正在通过节点检索它,将结果流出来,因为它们来自Redis。 Here's a good example of what I'm doing: 这是我正在做的一个很好的例子:

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(",");
            });
        });
    });
});

Essentially I'm getting set of keys from Redis and then requesting each one, streaming out the result into semi-manually created JSON (the strings coming out of Redis are already in JSON). 基本上我从Redis获取一组密钥,然后请求每个密钥,将结果流式传输到半手动创建的JSON(Redis中的字符串已经是JSON)。 Now this works nicely, but I can't help thinking that the i == replies.length-1 is a little untidy? 现在这很好用,但我不禁想到i == replies.length-1有点乱?

I could do all this with mget in Redis, but that isn't really the point I'm trying to get it; 我可以用Redis中的mget完成所有这些,但这并不是我想要得到它的重点; it's how best to handle async looping with forEach, streaming the output and gracefully closing off the connection with res.end with the looping is done. 这是如何最好地处理与forEach的异步循环,流式输出和优雅地关闭与res.end的连接与循环完成。

Is this the best way, or is there a more elegant pattern I could follow? 这是最好的方式,还是我可以遵循更优雅的模式?

The above code might not do what you expect. 上面的代码可能无法达到预期效果。 You're kicking off each .get() in sequence, but they might not call back in sequence — so the results could stream out in any order. 你按顺序启动每个.get() ,但它们可能不会按顺序回调 - 所以结果可以按任何顺序流出。 If you want to stream the results instead of collecting them in memory, you need to .get() in sequence. 如果要流式传输结果而不是在内存中收集它们,则需要按顺序使用.get()

I think that caolan's async library makes a lot of this easier. 我认为caolan的异步库使这很容易。 Here's one way you could use it to get each item in sequence (warning, untested): 这是您可以使用它来按顺序获取每个项目的一种方法(警告,未经测试):

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(']');
            }
        });
    });
});

If you don't care about the order, just use async.forEach() instead. 如果您不关心订单,请改用async.forEach()

If you wouldn't mind collecting the results and want them to return in sequence, you could use async.map() like this (warning, also untested): 如果您不介意收集结果并希望它们按顺序返回,您可以像这样使用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(',') + ']');
            }
        });
    });
});

You can use the async library, it provides some handy methods for looping, such as forEach: 您可以使用异步库,它提供了一些方便的循环方法,例如forEach:

forEach(arr, iterator, callback) forEach(arr,iterator,callback)

Applies an iterator function to each item in an array, in parallel. 将迭代器函数并行应用于数组中的每个项目。 The iterator is called with an item from the list and a callback for when it has finished. 使用列表中的项目调用迭代器,并在完成时调用它。 If the iterator passes an error to this callback, the main callback for the forEach function is immediately called with the error. 如果迭代器将错误传递给此回调,则会立即调用forEach函数的主回调并显示错误。

Note, that since this function applies the iterator to each item in parallel there is no guarantee that the iterator functions will complete in order. 注意,由于此函数并行地将迭代器应用于每个项目,因此无法保证迭代器函数将按顺序完成。

Example

// 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
});

but I can't help thinking that the i == replies.length-1 is a little untidy? 但我不禁想到i == replies.length-1有点凌乱?

I've heard a lot of people say that. 我听说很多人都这么说。 This is how I would do it by hand: 我就是这样做的:

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(",");
      });
    });
  });
});

Obviously doing it by hand isn't too pretty, which is why people have it abstracted into a library or some other function. 显然,手工制作它并不太漂亮,这就是人们将它抽象成图书馆或其他功能的原因。 But like it or not, that is how you do an async parallel loop. 但无论喜欢与否,这就是你如何进行异步并行循环。 :) :)

You can use the async library mentioned before to hide the nasty innards. 您可以使用之前提到的异步库来隐藏讨厌的内脏。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM