简体   繁体   English

Javascript / NodeJS:在forEach循环中推送值后,数组为空

[英]Javascript/NodeJS: Array empty after pushing values in forEach loop

I got a little bit of a problem. 我有点问题。 Here is the code: 这是代码:

Situation A: 情况A:

var foundRiders = [];

            riders.forEach(function(rider){
                Rider.findOne({_id: rider}, function(err, foundRider){
                    if(err){
                        console.log("program tried to look up rider for the forEach loop finalizing the results, but could not find");
                    } else {
                        foundRiders.push(foundRider);
                        console.log(foundRiders);
                    }
                });
            });

Situation B 情况B

var foundRiders = [];

            riders.forEach(function(rider){
                Rider.findOne({_id: rider}, function(err, foundRider){ 
                    if(err){
                        console.log("program tried to look up rider for the forEach loop finalizing the results, but could not find");
                    } else {
                        foundRiders.push(foundRider);
                    }
                });
            });
            console.log(foundRiders);

So in Situation A when I console log I get that foundRiders is an array filled with objects. 因此,在情况A中,当我控制台日志时,我发现foundRiders是一个充满对象的数组。 In situation B when I put the console.log outside the loop, my roundRiders array is completely empty... 在情况B中,当将console.log放在循环之外时,我的roundRiders数组完全为空...

How come? 怎么会?

As others have said, your database code is asynchronous. 正如其他人所说,您的数据库代码是异步的。 That means that the callbacks inside your loop are called sometime later, long after your loop has already finishes. 这意味着循环内的回调会在循环完成后很长时间以后再调用。 There are a variety of ways to program for an async loop. 有多种方式可以为异步循环编程。 In your case, it's probably best to move to the promise interface for your database and then start using promises to coordinate your multiple database calls. 在您的情况下,最好移至数据库的Promise接口,然后开始使用Promise协调多个数据库调用。 You can do that like this: 您可以这样做:

Promise.all(riders.map(rider => {
    return Rider.findOne({_id: rider}).exec();
})).then(foundRiders => {
    // all found riders here
}).catch(err => {
    // error here
});

This uses the .exec() interface to the mongoose database to run your query and return a promise. 这使用猫鼬数据库的.exec()接口来运行查询并返回.exec() Then, riders.map() builds and returns an array of these promises. Then, 然后, riders.map() builds and returns an array of these promises. Then, riders.map() builds and returns an array of these promises. Then, Promise.all() monitors all the promises in the array and calls .then() when they are all done or .catch()` when there's an error. riders.map() builds and returns an array of these promises. Then, Promise.all() monitors all the promises in the array and calls when they are all done or调用.then(), when they are all done or在出现错误时调用.catch()`。


If you want to ignore any riders that aren't found in the database, rather than abort with an error, then you can do this: 如果要忽略数据库中找不到的任何骑手,而不是因错误而中止,则可以执行以下操作:

Promise.all(riders.map(rider => {
    return Rider.findOne({_id: rider}).exec().catch(err => {
        // convert error to null result in resolved array
        return null;
    });
})).then(foundRiders => {
    foundRiders = foundRiders.filter(rider => rider !== null);
    console.log(founderRiders);
}).catch(err => {
    // handle error here
});

To help illustrate what's going on here, this is a more old fashioned way of monitoring when all the database callbacks are done (with a manual counter): 为了帮助说明此处发生的情况,这是一种监视所有数据库回调何时完成(带有手动计数器)的老式方法:

riders.forEach(function(rider){
    let cntr = 0;
    Rider.findOne({_id: rider}, function(err, foundRider){
        ++cntr;
        if(err){
            console.log("program tried to look up rider for the forEach loop finalizing the results, but could not find");
        } else {
            foundRiders.push(foundRider);
        }
        // if all DB requests are done here
        if (cntr === riders.length) {
            // put code here that wants to process the finished foundRiders
            console.log(foundRiders);
        }
    });
});

The business of maintaining a counter to track multiple async requests is what Promise.all() has built in. Promise.all()内置了维护计数器以跟踪多个异步请求的业务。


The code above assumes that you want to parallelize your code and to run the queries together to save time. 上面的代码假定您要并行化代码并一起运行查询以节省时间。 If you want to serialize your queries, then you could use await in ES6 with a for loop to make the loop "wait" for each result (this will probably slow things down). 如果要序列化查询,则可以在ES6中使用带有for循环的await使每个结果的循环“等待”(这可能会减慢速度)。 Here's how you would do that: 这是您要执行的操作:

async function lookForRiders(riders) {
    let foundRiders = [];
    for (let rider of riders) {
        try {
            let found = await Rider.findOne({_id: rider}).exec();
            foundRiders.push(found);
        } catch(e) {
            console.log(`did not find rider ${rider} in database`);
        }
    }
    console.log(foundRiders);
    return foundRiders;
}

lookForRiders(riders).then(foundRiders => {
    // process results here
}).catch(err => {
    // process error here
});

Note, that while this looks like it's more synchronous code like you may be used to in other languages, it's still using asynchronous concepts and the lookForRiders() function is still returning a promise who's result you access with .then() . 请注意,虽然这看起来像您在其他语言中可能已经习惯的更加同步的代码,但它仍在使用异步概念,并且lookForRiders()函数仍返回一个诺言,您可以使用.then()访问该结果。 This is a newer feature in Javascript which makes some types of async code easier to write. 这是Javascript中的新功能,使某些类型的异步代码更易于编写。

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

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