javascriptmongodbmongodb-stitch

Push inside forEach with query not working properly


I'm working with mongodb stitch/realm and I'm trying to modify objects inside an array with a foreach and also pushing ids into a new array.

For each object that i'm modifying, I'm also doing a query first, after the document is found I start modifying the object and then pushing the id into another array so I can use both arrays later.

The code is something like this:

exports = function(orgLoc_id, data){
    var HttpStatus = require('http-status-codes');  
    // Access DB
    const db_name = context.values.get("database").name;
    const db = context.services.get("mongodb-atlas").db(db_name);
    const orgLocPickupPointCollection = db.collection("organizations.pickup_points");
    const orgLocStreamsCollection = db.collection("organizations.streams");
    const streamsCollection = db.collection("streams");
      
    let stream_ids = [];    
  
    data.forEach(function(stream) {
  
      return streamsCollection.findOne({_id: stream.stream_id}, {type: 1, sizes: 1}).then(res => { //if I comment this query it will push without any problem
        if(res) {
          let newId = new BSON.ObjectId();
          stream._id = newId;
          stream.location_id = orgLoc_id;
          stream.stream_type = res.type;
          stream.unit_price = res.sizes[0].unit_price_dropoff;
          stream._created = new Date();
          stream._modified = new Date();
          stream._active = true; 
    
          stream_ids.push(newId);
                              
        }
      })
    })    
    console.log('stream ids: ' + stream_ids);

    //TODO

  };

But when I try to log 'stream_ids' it's empty and nothing is shown. Properties stream_type and unit_price are not assigned.

I've tried promises but I haven't had success


Solution

  • It's an asynchronous issue. You're populating the value of the array inside a callback. But because of the nature of the event loop, it's impossible that any of the callbacks will have been called by the time the console.log is executed.

    You mentioned a solution involving promises, and that's probably the right tack. For example something like the following:

    exports = function(orgLoc_id, data) {
      // ...
      let stream_ids = [];
      const promises = data.map(function(stream) {
        return streamsCollection.findOne({ _id: stream.stream_id }, { type: 1, sizes: 1 })
          .then(res => { //if I comment this query it will push without any problem
            if (res) {
              let newId = new BSON.ObjectId();
              // ...
              stream_ids.push(newId);
            }
          })
      })
    
      Promise.all(promises).then(function() {
        console.log('stream ids: ' + stream_ids);
    
        //TODO
        // any code that needs access to stream_ids should be in here...
      });
    };
    

    Note the change of forEach to map...that way you're getting an array of all the Promises (I'm assuming your findOne is returning a promise because of the .then).

    Then you use a Promise.all to wait for all the promises to resolve, and then you should have your array.

    Side note: A more elegant solution would involve returning newId inside your .then. In that case Promise.all will actually resolve with an array of the results of all the promises, which would be the values of newId.