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

时间:2017-12-11 15:32:44

标签: javascript node.js mongoose npm foreach

我遇到了一些问题。这是代码:

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

情况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);

所以在情境A中,当我控制日志时,我得到的是,FoundRiders是一个充满对象的数组。在情况B中,当我把console.log放在循环外面时,我的roundRiders数组是完全空的......

怎么回事?

1 个答案:

答案 0 :(得分:4)

正如其他人所说,您的数据库代码是异步的。这意味着循环内部的回调会在循环结束很久之后的某个时间调用。有多种方法可以为异步循环编程。在您的情况下,最好转移到数据库的promise接口,然后开始使用promises来协调多个数据库调用。你可以这样做:

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

这使用mongoose数据库的.exec()接口来运行查询并返回一个promise。然后,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()`当出现错误时。

如果你想忽略在数据库中找不到的任何车手,而不是因为错误而中止,那么你可以这样做:

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

为了帮助说明这里发生了什么,这是一种更老式的监控方式,当所有数据库回调完成时(使用手动计数器):

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

维护计数器以跟踪多个异步请求的业务是Promise.all()内置的。

上面的代码假定您要并行化代码并一起运行查询以节省时间。如果要序列化查询,那么可以在ES6中使用await并使用for循环使循环“等待”每个结果(这可能会减慢速度)。以下是您将如何做到这一点:

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

请注意,虽然这看起来像你可能在其他语言中使用的同步代码更多,但它仍然使用异步概念,lookForRiders()函数仍然返回一个使用{{1 }}。这是Javascript中的一个新功能,它使某些类型的异步代码更容易编写。