Node.js 检测两个猫鼬查找何时完成

Posted

技术标签:

【中文标题】Node.js 检测两个猫鼬查找何时完成【英文标题】:Node.js detect when two mongoose find are finished 【发布时间】:2017-01-21 01:24:27 【问题描述】:

我正在尝试使用 library 使用自动完成功能初始化两个输入。当我加载我的页面时,我会触发一个 Ajax 来初始化两个输入文本。

但我不知道如何检测我的所有 mongoose 查找何时完成。

这是我的服务器端代码:

app.post('/init/autocomplete', function(req, res)
    var autocomplete = 
        companies: [],
        offices: []
    ;

    // Find all companies
    Company.find(, function(err, companies) 
        if (err) throw err;

        companies.forEach(function(company) 
            autocomplete.companies.push(value: company.name)
        );

        console.log('One');
    );

    // Find all offices
    Office.find(, function(err, offices) 
        if (err) throw err;

        offices.forEach(function(office) 
            autocomplete.offices.push(value: office.name)
        );

        console.log('Two');
    );

    console.log('Three');

    // res.json(autocomplete);
);

我知道 find 方法是异步的。这就是为什么我按这个顺序看到我的 console.log() :

Three
One
Two

Company.findOffice.find 完成时,我该如何触发console.log('Three');

我想在最后一个位置看到console.log('Three');

编辑:

我想我可以这样做:

app.post('/init/autocomplete', function(req, res)
    var autocomplete = 
        companies: [],
        offices: []
    ;

    // Find all companies
    Company.find(, function(err, companies) 
        if (err) throw err;

        companies.forEach(function(company) 
            autocomplete.companies.push(value: company.name)
        );

        // Find all offices
        Office.find(, function(err, offices) 
            if (err) throw err;

            offices.forEach(function(office) 
                autocomplete.offices.push(value: office.name)
            );

            res.json(autocomplete);
        );
    );
);

但我不知道这是否是好方法。也许使用 promise 会更好?我愿意接受所有建议。

【问题讨论】:

使用 Promise,用 Promise 可以很轻松的解决并列问题 【参考方案1】:

Mongoose 内置了对 Promise 的支持,这些 Promise 提供了一种干净的方式来等待使用 Promise.all 完成的多个异步查询操作:

// Tell Mongoose to use the native Node.js promise library.
mongoose.Promise = global.Promise;

app.post('/init/autocomplete', function(req, res)
    var autocomplete = 
        companies: [],
        offices: []
    ;

    // Call .exec() on each query without a callback to return its promise.
    Promise.all([Company.find().exec(), Office.find().exec()])
        .then(results => 
            // results is an array of the results of each promise, in order.
            autocomplete.companies = results[0].map(c => (value: c.name));
            autocomplete.offices = results[1].map(o => (value: o.name));
            res.json(autocomplete);
        )
        .catch(err => 
            throw err; // res.sendStatus(500) might be better here.
        );
);

【讨论】:

很好,但是猫鼬如何处理它的内部错误?出现错误时它会中断吗?还是将所有内容返回结果?实际上,在某些情况下,我更愿意收集所有结果,并让客户端处理哪个查询成功或失败 @vdj4y 如果查询失败,则拒绝承诺并调用catch 处理程序。在需要以不同方式处理部分成功的情况下,您不会使用Promise.all。您基本上是在用Promise.all 说“全有或全无”。 我明白了,有时候,它会很好。我认为如果手动创建 Promise,我可以选择 resolve(err) 并立即返回。但这应该是我在其他情况下的首选代码。谢谢 感谢您的回答。这对我的情况来说是个好主意 .map() 比 forEach 有更好的性能?我从来没有见过它,它看起来很不错【参考方案2】:

使用Promise。有一些方法可以控制并联和串联。而且您的代码往往更具可读性。我处理并行的方法是先执行异步部分,然后在收集到结果后执行同步部分。

app.post('/init/autocomplete', function(req, res)
    // Find all companies
    // the query action below is not executed, just return PromiseObject for now
    var findCompanies = new Promise((resolve, reject) => 
      Company.find(, function(err, companies) 
          if (err) reject(err);
          resolve(companies)                   
       );
    )

    // Find all offices
    // the query action below is not executed, just return PromiseObject for now
    var findOffices = new Promise((resolve, reject) => 
      Office.find(, function(err, offices) 
          if (err) reject(err);
          
          resolve(offices)
      );
    )
    
    // this is the execution part, in OOP world, you can think this is main()
    // execute and wait until each action(Promise Object) is complete before finally returning an array.
    return Promise.all([findCompanies, findOffices])
                  .then(array => 
                       console.log(array) // [ [companies] , [offices]  ]
                       //the difficult async part is done, with just few lines
                                                                           
                       console.log(array[0]) // [companies] array of companies
                       console.log(array[1]) // [offices] array of offices
                      
                       // now you can safely manipulate using forEach.
                       // basically, this part is for the synchronous action               
                       
                        var autocomplete = ;

                        array[0].forEach(function(company) 
                            autocomplete.companies.push(value: company.name)
                        );
                        array[1].forEach(function(office) 
                            autocomplete.office.push(value: office.name)
                        );
                       res.json(autocomplete)
                  )
);

上面的代码,findCompanies 和 FindOffices 是 Promise 对象存储在变量中(它还没有执行)。接下来,使用 Promise.all(),我们运行所有的 Promise 对象,它会等到每个动作完成后才最终返回一个数组。

返回的数组包含另外两个数组。这个数组的顺序和你传递给 Promise.all() 的动作顺序是一样的

如果您先找到办公室,然后再找到公司。它将返回 [[offices],[companies]] Promise.all([findOffices, findCompanies]) 将返回 [[offices], [companies]]

反之亦然

Promise.all([findCompanies, findOffices]) 将返回 [[companies], [offices]]

【讨论】:

非常好的答案,谢谢。所以我可以用 array[0].name 在company.name 上做我的 foreach 吗?这个数组将包含我的 mongodb json 对象?并且最好使用 Native Promise 或 bluebird ? array[0] is [companies] ,你可以做 array[0].forEach( (company) => //push to autocomplete ) 数组的序列是你传递给 Promise.all() 的序列,如果你传递 [offices, Companies] 它将返回 [[offices], [companies] ]。反之亦然 我必须删除 return 关键字,因为我有这个错误:var findCompanies = return new Promise(function(resolve, reject)SyntaxError: Unexpected token return。为了创建 forEach 循环,我必须使用 companiesoffices 数组初始化我的自动完成变量,否则循环将停止。 哦,是的,对不起,我忘了它是一个变量而不是一个函数。将更新代码【参考方案3】:

使用 Promises,您将拥有更清晰的代码来维护而不会产生太多开销,使用 Mongoose 您有两个选择。使用本机 Mongoose 承诺,这对于基本用例来说很好。但是 Mongoose 的承诺目前已被弃用,因为会出现警告。

所以要切换到原生 Promises:

// Native JS Promise
mongoose.Promise = global.Promise;

或者到更高级的Bluebird Promise 库:

// Bluebird
mongoose.Promise = require('bluebird');

带有原生 JS 承诺的 MVCE 和 Bluebird 已被注释掉。

const mongoose = require('mongoose');
const Schema = mongoose.Schema;
const assert = require('assert');

mongoose.connect("mongodb://localhost:33023/test_1");

// Native JS Promise
mongoose.Promise = global.Promise;

// Bluebird
// mongoose.Promise = require('bluebird');


// Schema creation
var Company = mongoose.model('Company', new Schema(
  name: String
));

var Office = mongoose.model('Office', new Schema(
  name: String
));


var acme = new Company(
  name: 'ACME'
);

var HR = new Office(
  name: 'Human Resources'
);

acme
  .save()
  .then(HR.save());

var query = Company.find();
assert.equal(query.exec().constructor, global.Promise);
// assert.equal(query.exec().constructor, require('bluebird'));

query.exec()
.then(function(results) 
  console.log(results);
)
.then(Office.find()
.then(function(results) 
  console.log(results);
));

Mongoose Promises 的文档。

【讨论】:

Promise.all() 在这种情况下也很有用。 感谢您的回答。我从不在节点 js 中使用 promise,这将是我的第一次。 @vdj4y 的答案和您的答案有两种方法。有什么区别? 基本上Promise.all 在所有承诺都解决后返回一个承诺。如果任何传入的 Promise 被拒绝,所有 Promise 立即以被拒绝的 Promise 的值拒绝,丢弃所有其他 Promise,无论它们是否已解决。所以如果你想要这种行为是要走的路。在我的情况下,您可以处理流程中的拒绝。 另外我使用的是同步方法而不是@vdj4y,它使用了一种异步方法,这对性能有好处,但你会失去对执行流程的控制。一个很好的解释:***.com/questions/28066429/… 在我的情况下,我更喜欢异步。我需要的不是我所有的承诺都是决心。但我记住了你的回答,因为我认为这对我将来真的有帮助。谢谢【参考方案4】:

可以使用你指定的方法,但是对于大量的异步操作,它会导致callback from hell。为了避免这种情况,最好以顺序和有序的方式编写代码。在您的情况下,您可以使用 async 之类的库来实现此目的,因为 mongoose 不会返回 Promise Object。有关异步库的更多信息,您可以参考此链接。

async.series([
    function(callback) 
        // do some stuff ...
        console.log('one');
        callback(null, 'one');
    ,
    function(callback) 
        // do some more stuff ...
        console.log('two');
        callback(null, 'two');
    
],
// optional callback
function(err, results) 
    // results is now equal to ['one', 'two']
    console.log('three');
);

只有在任务一和二完成后才会写入第三个日志。 series 函数一一运行任务。您可以使用eachSeries 并行运行所有任务。

【讨论】:

【参考方案5】:

我对 mongoose 很陌生,但我遇到了类似的问题,我通过使用 cursor() 解决了它

let cursor = Company.find().cursor();
    cursor.on('data', function(doc)
        //do something with doc
    )


    cursor.on('close', function() 
        console.log("done");
    )

【讨论】:

以上是关于Node.js 检测两个猫鼬查找何时完成的主要内容,如果未能解决你的问题,请参考以下文章

在 Node.js 中创建一个持久的 bash shell 会话,知道命令何时完成,并读取和修改源/导出的变量

为啥在 Node.js 中使用猫鼬模式

node.js 的猫鼬外键问题

Node.js:根据条件渲染猫鼬元素

如何按 ID node.js 过滤猫鼬集合

在猫鼬中,我如何按日期排序? (node.js)