0

I'm calling mongoose query inside of another mongoose query. When I push results to a array, When ever I check it at last it is empty. After seacrching a lot, Found that issue is this executes asynchronously. But can't find how to fix the issue. My code is as follows.

    Bus.find().exec(function(err,buses) {

        if(err)
            console.log(err);
        if (buses[0] != null){
            const cords = [];
            buses.forEach( function (bus) {
                // console.log(bus);
                    Position.findOne({"busId": bus.busId},{}, {sort : {'time' : -1}}, function (err, position) {
                        cords.push(position);
                        console.log(cords);
                        // console.log(position);
                    });
                    console.log(cords);
                },
                function (err) {
                    if (err){
                        console.log(err,"Errrrrrrrrrrrrr");
                    }
                });
            console.log(cords);
            res.json({cords: cords});
            }

1 Answer 1

2

Well, there are a number of problems with your code, but chief among them is the fact that you cannot save or act upon values you receive inside a callback to anything outside of that callback. Your example has (rewritten for clarity):

var result = []
arry.forEach(function(opt) {
  async.call(args, function(err,value) {
    result.push(value)
  })
})
// result is empty here!

Which cannot work as you expect because you can not know when the inner callbacks complete.

By definition, callbacks are triggered some time in the future, and since you cannot know when, you must do all computation using the result passed to a callback in the callback itself!

Doing otherwise will give you inconsistent results.

UPDATED - Re comment

(Note: hastily typed on iPad during train ride, will fix later if needed.)

The best way would be to use Promises to aggregate the results. Here's a naive example:

/* 
 * given a value and an optional array (accum),
 * pass the value to the async func and add its result to accum
 * if accum is not an array, make it one
 * return accum
 */

var do_something = (value, accum) => {
  // on first pass, accum will be undefined, so make it an array
  accum = Array.isArray(accum) ? accum : []
  return new Promise((resolve, reject) => {
    async_func(value, (err, res) => {
      if(err) {
        reject(err)
      }
      accum.append(res)
      resolve(accum)
    })
  })
}   

/* 
 * for each member of input array, apply do_something
 * then operate on accumulated result.
 */

Promise.map(input, do_something)
  .then(results => {
    // results will contain the accumulated results from all
    // the mapped operations
  })
  .catch(err => {
    throw err
  })

UPDATED - per comment

Using callbacks only, you can achieve the same result using:

const inputs = [...] // array of inputs to pass to async function

const expected_num_of_results = inputs.length

let results = []

const onComplete = (results) => {
  // do something with completed results here
  console.log(`results: ${results}`);
}

const onResult = (err, res) => { // callback to async_func
  if(err) {
    throw new Error(`on iteration ${results.length+1}: ${err}`)
  }

  results.push(res) // save result to accumulator

  if( results.length >= expected_num_of_results) { // are we done?
    onComplete(results) // process results
  }
}

// example async func - REPLACE with actual async function
const async_func = (val,cb) => {
  // call callback with no error and supplied value multiplied by 2
  cb(null,val*2)
}

// wrapper that takes one value 
// and calls async_func with it and predefined callback
const do_async = (value) => { 
  async_func(value, onResult)
}

// process inputs
inputs.forEach(do_async)

So with:

const inputs = [1,2,3,4,5]

will print:

results: 2,4,6,8,10
Sign up to request clarification or add additional context in comments.

1 Comment

Can you give me a example of how to do that thing with callbacks! If could that will be a great help!

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.