如何在Express.js和mongoose中编写异步for-each循环?

时间:2022-11-30 14:28:54

I have a function that returns an array of items from MongoDB:

我有一个函数从MongoDB返回一个项目数组:

var getBooks = function(callback){
    Comment.distinct("doc", function(err, docs){
            callback(docs);
        }
    });
};

Now, for each of the items returned in docs, I'd like to execute another mongoose query, gather the count for specific fields, gather them all in a counts object, and finally pass that on to res.render:

现在,对于在docs中返回的每个项目,我想执行另一个mongoose查询,收集特定字段的计数,将它们全部收集到count对象中,最后将其传递给res.render:

getBooks(function(docs){
    var counts = {};
    docs.forEach(function(entry){
        getAllCount(entry, ...){};
    });      
});

If I put res.render after the forEach loop, it will execute before the count queries have finished. However, if I include it in the loop, it will execute for each entry. What is the proper way of doing this?

如果我在forEach循环之后放入res.render,它将在计数查询完成之前执行。但是,如果我将它包含在循环中,它将为每个条目执行。这样做的正确方法是什么?

3 个解决方案

#1


1  

forEach probably isn't your best bet here, unless you want all of your calls to getAllCount happening in parallel (maybe you do, I don't know — or for that matter, Node is still single-threaded by default, isn't it?). Instead, just keeping an index and repeating the call for each entry in docs until you're done seems better. E.g.:

forEach可能不是你最好的选择,除非你想让所有调用getAllCount并行发生(也许你这样做,我不知道 - 或者就此而言,Node默认仍然是单线程的,不是它?)。相反,只需保留索引并重复调用文档中的每个条目,直到完成为止。例如。:

getBooks(function(docs){
    var counts = {},
        index = 0,
        entry;

    loop();

    function loop() {
        if (index < docs.length) {
            entry = docs[index++];
            getAllCount(entry, gotCount);
        }
        else {
            // Done, call `res.render` with the result
        }
    }
    function gotCount(count) {
        // ...store the count, it relates to `entry`...

        // And loop
        loop();
    }
});

If you want the calls to happen in parallel (or if you can rely on this working in the single thread), just remember how many are outstanding so you know when you're done:

如果你想让这些调用并行发生(或者如果你可以依赖于这个在单个线程中工作),那么就记住有多少是未完成的,所以你知道什么时候完成:

// Assumes `docs` is not sparse
getBooks(function(docs){
    var counts = {},
        received = 0,
        outstanding;

    outstanding = docs.length;
    docs.forEach(function(entry){
        getAllCount(entry, function(count) {
            // ...store the count, note that it *doesn't* relate to `entry` as we
            // have overlapping calls...

            // Done?
            --outstanding;
            if (outstanding === 0) {
                // Yup, call `res.render` with the result
            }
        });
    });      
});

#2


3  

I'd recommend using the popular NodeJS package, async. It's far easier than doing the work/counting, and eventual error handling would be needed by another answer.

我建议使用流行的NodeJS软件包async。它比工作/计数容易得多,另一个答案需要最终的错误处理。

In particular, I'd suggest considering each (reference):

特别是,我建议考虑每个(参考):

getBooks(function(docs){
    var counts = {};
    async.each(docs, function(doc, callback){
        getAllCount(entry, ...);         
        // call the `callback` with a error if one occured, or 
        // empty params if everything was OK. 
        // store the value for each doc in counts
    }, function(err) {
       // all are complete (or an error occurred)
       // you can access counts here
       res.render(...);
    });      
});

or you could use map (reference):

或者您可以使用地图(参考):

getBooks(function(docs){
    async.map(docs, function(doc, transformed){
        getAllCount(entry, ...);         
        // call transformed(null, theCount);
        // for each document (or transformed(err); if there was an error); 
    }, function(err, results) {
       // all are complete (or an error occurred)
       // you can access results here, which contains the count value
       // returned by calling: transformed(null, ###) in the map function
       res.render(...);
    });      
});

If there are too many simultaneous requests, you could use the mapLimit or eachLimit function to limit the amount of simultaneous asynchronous mongoose requests.

如果同时发出的请求太多,您可以使用mapLimit或eachLimit函数来限制同步异步mongoose请求的数量。

#3


-1  

In fact, getAllCount on first item must callback getAllCount on second item, ...

实际上,第一项上的getAllCount必须在第二项上回调getAllCount,...

Two way: you can use a framework, like async : https://github.com/caolan/async

两种方式:你可以使用一个框架,比如async:https://github.com/caolan/async

Or create yourself the callback chain. It's fun to write the first time.

或者创建自己的回调链。第一次写这个很有趣。

edit The goal is to have a mechanism that proceed like we write.

编辑目标是建立一个像我们写的那样继续运行的机制。

getAllCountFor(1, function(err1, result1) {
    getAllCountFor(2, function(err2, result2) {
        ...
            getAllCountFor(N, function(errN, resultN) {
                res.sender tout ca tout ca
            });
    });
});

And that's what you will construct with async, using the sequence format.

这就是您将使用序列格式使用异步构建的内容。

#1


1  

forEach probably isn't your best bet here, unless you want all of your calls to getAllCount happening in parallel (maybe you do, I don't know — or for that matter, Node is still single-threaded by default, isn't it?). Instead, just keeping an index and repeating the call for each entry in docs until you're done seems better. E.g.:

forEach可能不是你最好的选择,除非你想让所有调用getAllCount并行发生(也许你这样做,我不知道 - 或者就此而言,Node默认仍然是单线程的,不是它?)。相反,只需保留索引并重复调用文档中的每个条目,直到完成为止。例如。:

getBooks(function(docs){
    var counts = {},
        index = 0,
        entry;

    loop();

    function loop() {
        if (index < docs.length) {
            entry = docs[index++];
            getAllCount(entry, gotCount);
        }
        else {
            // Done, call `res.render` with the result
        }
    }
    function gotCount(count) {
        // ...store the count, it relates to `entry`...

        // And loop
        loop();
    }
});

If you want the calls to happen in parallel (or if you can rely on this working in the single thread), just remember how many are outstanding so you know when you're done:

如果你想让这些调用并行发生(或者如果你可以依赖于这个在单个线程中工作),那么就记住有多少是未完成的,所以你知道什么时候完成:

// Assumes `docs` is not sparse
getBooks(function(docs){
    var counts = {},
        received = 0,
        outstanding;

    outstanding = docs.length;
    docs.forEach(function(entry){
        getAllCount(entry, function(count) {
            // ...store the count, note that it *doesn't* relate to `entry` as we
            // have overlapping calls...

            // Done?
            --outstanding;
            if (outstanding === 0) {
                // Yup, call `res.render` with the result
            }
        });
    });      
});

#2


3  

I'd recommend using the popular NodeJS package, async. It's far easier than doing the work/counting, and eventual error handling would be needed by another answer.

我建议使用流行的NodeJS软件包async。它比工作/计数容易得多,另一个答案需要最终的错误处理。

In particular, I'd suggest considering each (reference):

特别是,我建议考虑每个(参考):

getBooks(function(docs){
    var counts = {};
    async.each(docs, function(doc, callback){
        getAllCount(entry, ...);         
        // call the `callback` with a error if one occured, or 
        // empty params if everything was OK. 
        // store the value for each doc in counts
    }, function(err) {
       // all are complete (or an error occurred)
       // you can access counts here
       res.render(...);
    });      
});

or you could use map (reference):

或者您可以使用地图(参考):

getBooks(function(docs){
    async.map(docs, function(doc, transformed){
        getAllCount(entry, ...);         
        // call transformed(null, theCount);
        // for each document (or transformed(err); if there was an error); 
    }, function(err, results) {
       // all are complete (or an error occurred)
       // you can access results here, which contains the count value
       // returned by calling: transformed(null, ###) in the map function
       res.render(...);
    });      
});

If there are too many simultaneous requests, you could use the mapLimit or eachLimit function to limit the amount of simultaneous asynchronous mongoose requests.

如果同时发出的请求太多,您可以使用mapLimit或eachLimit函数来限制同步异步mongoose请求的数量。

#3


-1  

In fact, getAllCount on first item must callback getAllCount on second item, ...

实际上,第一项上的getAllCount必须在第二项上回调getAllCount,...

Two way: you can use a framework, like async : https://github.com/caolan/async

两种方式:你可以使用一个框架,比如async:https://github.com/caolan/async

Or create yourself the callback chain. It's fun to write the first time.

或者创建自己的回调链。第一次写这个很有趣。

edit The goal is to have a mechanism that proceed like we write.

编辑目标是建立一个像我们写的那样继续运行的机制。

getAllCountFor(1, function(err1, result1) {
    getAllCountFor(2, function(err2, result2) {
        ...
            getAllCountFor(N, function(errN, resultN) {
                res.sender tout ca tout ca
            });
    });
});

And that's what you will construct with async, using the sequence format.

这就是您将使用序列格式使用异步构建的内容。