简体   繁体   English

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

[英]How to write an asynchronous for-each loop in Express.js and mongoose?

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 : 现在,对于文档中返回的每个项目,我想执行另一个猫鼬查询,收集特定字段的计数,将它们全部收集在counts对象中,最后将其传递给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. 如果将res.render放在forEach循环之后,它将在计数查询完成之前执行。 However, if I include it in the loop, it will execute for each entry. 但是,如果我将其包括在循环中,它将为每个条目执行。 What is the proper way of doing this? 正确的做法是什么?

I'd recommend using the popular NodeJS package, async . 我建议使用流行的NodeJS包async It's far easier than doing the work/counting, and eventual error handling would be needed by another answer. 这比进行工作/计数要容易得多,并且另一个答案将需要最终的错误处理。

In particular, I'd suggest considering each ( reference ): 特别是,我建议考虑each参考 ):

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 ): 或者您可以使用map参考 ):

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. 如果同时请求太多,则可以使用mapLimiteachLimit函数来限制同时异步猫鼬请求的数量。

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?). forEach可能不是您最好的选择,除非您希望对getAllCount所有调用并行发生(也许您知道,但我不知道,或者就此而言,Node默认仍然是单线程的,不是它?)。 Instead, just keeping an index and repeating the call for each entry in docs until you're done seems better. 相反,只保留一个索引并针对docs每个条目重复调用,直到完成为止似乎更好。 Eg: 例如:

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

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. 这就是您将使用序列格式通过异步构造的内容。

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

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