具有默认功能的NodeJS中的数据聚合

时间:2017-01-11 14:45:12

标签: node.js mongodb asynchronous promise

我想将NodeJS中的MongoDB数据与promisified函数进行聚合。 带转储的脚本在https://github.com/network-spy/lego

小描述:数据库中有2个集合:“zip”和“restaurants”。 “zip”包含位置的邮政编码,“餐馆”包含有关带有邮政编码的餐馆的信息。所以脚本应该创建新的集合“stat”并用以下文档填充它: {“zip_code”:“01002”,“餐馆”:[餐馆名单]}

问题是在“zip”集合中有29353个文档,但在脚本处理后,我得到了“stat”集合,包含29026个文档(有时文档数量可能会发生变化)。

我想这是因为我的JS代码中某处的同步断了。您能看一下我的代码,并建议如何修复它吗?

const MongoClient = require('mongodb').MongoClient;
const mongoDbUrl = 'mongodb://127.0.0.1:27017/world';

MongoClient.connect(mongoDbUrl, function(err, db) {
    if (err) {
        console.log(err);
        return;
    }
    console.log("Connected to server.");
    clearStat(db).then(
        result => {
            console.log(result);
            processZips(db).then(
                result => {
                    console.log(result);
                    closeMongoDBConnection(db);
                },
                error => {
                    console.log(error);
                    closeMongoDBConnection(db);
                }
            );
        },
        error => {
            console.log(error);
            closeMongoDBConnection(db);
        }
    );
});

let closeMongoDBConnection = (db) => {
    db.close();
    console.log("Disconnected from server.");
};

let clearStat = (db) => {
    return new Promise((resolve, reject) => {
        db.collection('stat').deleteMany({}, function(err, results) {
            if (err) {
                reject(err);
            }
            resolve('Stat data cleared');
        });
    });
};


let processZips = (db) => {
    return new Promise((resolve, reject) => {
        db.collection('zip').find({}, {"_id":1}).each((err, zipCode) => {
            if (zipCode == null) {
                resolve('Zips precessed');
            } else if (err) {
                reject(err);
            } else {
                findRestaurantsByZip(db, zipCode._id).then(
                    result => {
                        insertToStat(db, zipCode._id, result).then(
                            result => {
                                console.log('Inserted: ');
                                console.dir(result);
                            },
                            error => {
                                reject(error);
                            }
                        );
                    },
                    error => {
                        reject(error);
                    }
                );
            }
        });
    });
};

let findRestaurantsByZip = (db, zipCode) => {
    return new Promise((resolve, reject) => {
        db.collection('restaurant').find({"address.zipcode": zipCode}).toArray((err, restaurants) => {
            if (err) {
                reject(err);
            }
            resolve(restaurants);
        });
    });
};

let insertToStat = (db, zip, restaurants) => {
    return new Promise((resolve, reject) => {
        let statDocument = {};
        statDocument.zip_code = zip;
        statDocument.restaurants = restaurants;
        db.collection('stat').insertOne(statDocument).then(
            result => {
                resolve(statDocument);
            },
            error => {
                reject(error);
            }
        );
    });
};

1 个答案:

答案 0 :(得分:1)

首先,简化您的processZips功能。这在功能上与您的代码相同,但使用Promise链而不是嵌套的Promises

let processZips = (db) => new Promise((resolve, reject) => 
    db.collection('zip').find({}, {"_id":1}).each((err, zipCode) => {
        if (zipCode == null) {
            resolve('Zips precessed');
        } else if (err) {
            reject(err);
        } else {
            findRestaurantsByZip(db, zipCode._id)
            .then(result => insertToStat(db, zipCode._id, result))
            .then(result => console.log('Inserted: ', result))
            .catch(error => reject(error));
        }
    })
);

问题可能是(我无法测试任何内容)您在processZips处理结束时解析.each承诺。这会“触发”关闭数据库的.then。但是,由于异步查找/插入代码,很可能其中一些当时正在“进行中”。我并不自称能够很好地了解mongodb,所以我不知道在处理仍处于活动状态时关闭数据库会是什么 - 似乎这就是为什么你输出数据是“短”的原因

所以,有两种方法可以解决这个问题

1 - 逐个处理每个zipCode,即每个find / insert等待前一个完成,然后在最后一个zipCode完成后解析

let processZips = (db) => {
    // set p to a resolved Promise so the first find/insert will kick off
    let p = Promise.resolve();
    return new Promise((resolve, reject) => 
        db.collection('zip').find({}, {"_id":1}).each((err, zipCode) => {
            if (zipCode == null) {
                // wait for last insert to complete before resolving the Promise
                resolve(p.then(() => resolve('Zips precessed'))); // see note 1, 2
            } else if (err) {
                reject(err);
            } else {
                // wait for previous insert to complete before starting new find/insert
                p = p
                .then(() => findRestaurantsByZip(db, zipCode._id))
                .then(result => insertToStat(db, zipCode._id, result))
                .then(result => console.log('Inserted: ', result)); // see note 1
            }
        })
    );
};

使用此代码,只要查找/插入拒绝,就不会再执行查找/插入

2 - 以“并行”方式处理每个代码,即启动所有查找/插入,然后在所有 zipCode完成后解析

let processZips = (db) => {
    // create an array for all the find/insert Promises
    let p = [];
    return new Promise((resolve, reject) => 
        db.collection('zip').find({}, {"_id":1}).each((err, zipCode) => {
            if (zipCode == null) {
                // wait for all find/insert to complete before resolving this Promise
                resolve(Promise.all(p).then(() => 'Zips precessed')); // see note 1, 2
            } else if (err) {
                reject(err);
            } else {
                p.push(findRestaurantsByZip(db, zipCode._id)
                    .then(result => insertToStat(db, zipCode._id, result))
                    .then(result => console.log('Inserted: ', result))
                ); // see note 1
            }
        })
    );
};

第二种方法的一个警告是,与原始代码一样,如果其中一个查找/插入失败,则不会停止后续的查找/插入处理。

您会注意到与原始代码相比,似乎缺少错误处理。此代码使用promises的2个“功能”。

  1. 拒绝将“流过”承诺链,
  2. 如果您使用被拒绝的承诺解决承诺,则与拒绝承诺相同。