Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Node.js detect when two mongoose find are finished

I'm trying to initialize two input with autocomplete with this library. When I load my page, I will trigger an Ajax to initialize two input text.

But I don't know how I can detect when all my mongoose find are completed.

Here is my server side code :

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

I know than the find method is async. That is why I see my console.log() in this order :

Three
One
Two

How can I do to trigger console.log('Three'); when the Company.find and Office.find are finished ?

I want to see the console.log('Three'); at the last position.

Edit :

I think I can do this way :

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

But I don't know if it's the good way. Maybe using promise will be better ? I'm open for all suggestions.

like image 326
John Avatar asked Sep 13 '16 12:09

John


2 Answers

Mongoose has built-in support for promises which provide a clean way to wait for the completion of multiple async query operations with 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.
        });
});
like image 53
JohnnyHK Avatar answered Sep 28 '22 02:09

JohnnyHK


use Promise. There are ways to control parallel and series. and your code tends to be much readable. My method of dealing with parallel is to execute the async part first, then when the result have been collected, do the synchronous part.

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

the code above, findCompanies and FindOffices are Promise Object store in variable(it is not yet executed). Next, with Promise.all(), we run all the Promise Objects, and it will wait until each action is complete before finally returning an array.

the returned array contains another two array. The sequence of this array is the same as the sequence of actions you pass to Promise.all()

if you findOffices first, then findCompanies. It will return [[offices],[companies]] Promise.all([findOffices, findCompanies]) will return [[offices], [companies]]

vice versa

Promise.all([findCompanies, findOffices]) will return [[companies], [offices]]

like image 45
vdj4y Avatar answered Sep 28 '22 00:09

vdj4y