Ray Phelan
Ray Phelan

Reputation: 31

forEach with NodeJS and Async

I am new to NodeJS(Express). I am trying to build an API that will list the "Rooms" of a Hotel, and "Photos" for each room.

What I am trying to do is: - get a list of all rooms by a hotel - loop through this list of rooms and add photos - return JSON with rooms and photos

My problem is that I can not seem to attach the "photos" result to the "rooms result.

async.waterfall([

    //  Function 1
    function(callback) {

        //  Find all Hotel Rooms
        Room.find({ 'hotel': req.params.hotel_id})
        .exec(function(err, rooms) {                
            callback(null, rooms);  // Send rooms to function 2
        });

    },
    //  Function 2
    function(rooms, callback) {             

        //  Loop through each room to get photos for each room
        rooms.forEach(function(room) {          

            //  Get photos for room
            RoomPhoto.find({ 'room': room._id})             
            .exec(function(err, photos) {   

                // I want to add these "photos" to the main "rooms" object, but attached to each individual "room"
                room.photos = JSON.stringify(photos);

            });

        })

        console.log(rooms)
        callback(null, rooms);
    }

], function(err, results) {

    if(err) console.log('err');
    res.json(results);

});

Any help would be greatly appreciated, as I have been hours trying to figure it out.

Thanks

Ray


I have updated my script to this, but the "photos" are still not going into the final "rooms" object. I have included the code and also the console output.

Code:

exports.index = function(req, res, next) {  

    Room.find({ 'hotel': req.params.hotel_id})
    .exec(function(err, rooms) {

        async.each(rooms, function(room, room_cb) {

            console.log('Room Name: ' + room.name);

            RoomPhoto.find({ 'room': room._id})
            .exec(function(err, photos) {

                console.log('Photos: ' + photos);

                room.photos = photos; 
                // photos should now be in the main "rooms" object right??
                // I can console.log the photos here to prove they exists
                room_cb();
            });

        }, function(err) {

            if(err) {
                console.log('Error: ' + err)
            }
            else {
                // But the photos are not in the final "rooms" object
                console.log('FINAL ROOMS OBJECT, why are the photos not here??');
                console.log(rooms);
                res.json(rooms);
            }
        })      
    });
};

Here is the console output for the above script:

Room Name: Test Room 1

Room Name: Test Room 2

Photos:

{ _id: 5acd3094e0026f38ca4b44bc,
  src: 'https://picsum.photos/200/300/?image=252',
  room: 5acd3094e0026f38ca4b44bb,
  __v: 0 }

Photos:

{ _id: 5acd30c8cec87777eefcd32d,
  src: 'https://picsum.photos/200/300/?image=252',
  room: 5acd30c8cec87777eefcd32c,
  __v: 0 }

FINAL ROOMS OBJECT

[ { _id: 5acd3094e0026f38ca4b44bb,
    name: 'Test Room 1',
    hotel: 5accd4e1734d1d55c3195c84,
    __v: 0 },
  { _id: 5acd30c8cec87777eefcd32c,
    name: 'Test Room 2',
    hotel: 5accd4e1734d1d55c3195c84,
    __v: 0 } ]

Upvotes: 3

Views: 336

Answers (4)

Muhammad Faizan
Muhammad Faizan

Reputation: 1789

Since you are new to node.js you may not be aware of the latest standards, and things you can use.. Here's how you can accomplish it without including async module at all...

const requestHandler = async (req, res, next) => {

    let rooms = await Room.find({ 'hotel': req.params.hotel_id})
        .exec()
        .catch(err => { 
            console.log(err);
            throw err;
        });

        //  Get photos for room
    let photos = await Promise.all(rooms.map(
        room => RoomPhoto.find({ 'room': room._id})
        .catch(err => { 
            console.log(err);
            throw err;
        });
    ))

    rooms = rooms.map((room, index) => {
         room = room.toObject();
         room.photos = JSON.stringify(photos[index]);
         return room;
    });
// Now your "rooms" array will have room objects, with photos populated.
    res.send(rooms);
});

To export it you can do it like this:

module.exports.index = requestHandler;

Upvotes: 0

Ray Phelan
Ray Phelan

Reputation: 31

I found something that works... For some reason, the MongoDB "_id" was causing a problem.

exports.index = function(req, res, next) {

Room.find({ 'hotel': req.params.hotel_id})
.exec(function(err, rooms) {

    rooms = JSON.parse(JSON.stringify(rooms).replace(/_id/g,"room_id"));    

    console.log(rooms);
    console.log('---------------------------');

    async.each(rooms, function(room, room_cb) {         

        RoomPhoto.find({ 'room': room.room_id})
        .exec(function(err, photos) {

            console.log('Room: ' + room.id);
            console.log('Photos: ' + photos);

            room.photos = photos;
            //console.log('Photos: ' + rooms[key].photos);

            room_cb(); 
        });

    }, function(err) {

        if(err) {
            console.log('Error: ' + err)
        }
        else {
            console.log('FINAL ROOMS OBJECT');

            rooms = JSON.parse(JSON.stringify(rooms).replace(/room_id/g,"_id"));                

            console.log(rooms);
            res.json(rooms);
        }
    })      
});

};

Upvotes: 0

stdob--
stdob--

Reputation: 29172

Since the function RoomPhoto is also asynchronous, then you need to wait until all the photos are loaded before calling the callback function. Like that:

//  Function 2
function(rooms, callback) {             
    // https://caolan.github.io/async/docs.html#each
    //  Loop through each room to get photos for each room
    async.each(rooms, function(room, room_cb) {          
        //  Get photos for room
        RoomPhoto.find({ 'room': room._id})             
        .exec(function(err, photos) {   
            room.photos = JSON.stringify(photos)
            room_cb()
        });
    }, function (err) {
        console.log(rooms)
        callback(null, rooms)
    })
}

Upvotes: 4

Adriaan Marain
Adriaan Marain

Reputation: 354

Try placing your second callback(null, rooms); line (the one near the end) under the room.photos = JSON.stringify(photos); line.

Because of the asynchronous nature of JavaScript, it is probably running before your query has even finished getting the photos.

You might also want to add some checks in case there are no photos for a room.

Upvotes: -1

Related Questions