How to wait for nested forEach statements with mongodb queries inside to return function

Viewed 793

I am having a hard time figuring out the asynchronous behaviour of nested forEach() functions with node.js. I am not sure where I should create and return a promise and so on.

I have tried wrapping all the logic of the getFeed() function into a return new Promise(), but I think resolve is called before the nested forEach()'s are done.

friends = user_info.friends;

function GetFeed() {
    let post_ids = [];
    try {
        friends.forEach(async function(friend_id) {
        const posts = await db.collection('users').findOne(
              { _id: ObjectId(friend_id) },
              { projection: { posts: 1 }}
        );
        posts.posts.forEach(function(post_id) {
              console.log(post_id);
              post_ids.push(post_id);
           });
        });
        return new Promise((resolve, reject) => {
            resolve(post_ids);
        });
    } catch (err) {
         return res.status(500).json({ error: 'Internal server error, unable to collection post ids from the database.' });
    }
}

GetFeed()
  .then((post_ids) => {
        console.log(post_ids);
        return res.status(200).json({ post_ids: post_ids });
   })

the console.log() statements reveal that the GetFeed().then() executes before the console.log() statement in the inner forEach loop. I expect that the GetFeed().then() console.log would wait for the nest forEach logic to finish and then console.log the result.

3 Answers

From the docs:

forEach() executes the callback function once for each array element; unlike map() or reduce() it always returns the value undefined.

Meaning you cant wait for it as you can never get a promise to return from it.

You should use a for or while loop or a library like bluebird to iterate over an array and return promises.

You can use a promise array an await them.

friends = user_info.friends;

async function GetFeed() {
    let post_ids = [];


    let postsPromiseArray = await Promise.all(
        friends.map(friend_id => {
            return db.collection('users').findOne(
                { _id: ObjectId(friend_id) },
                { projection: { posts: 1 } }
            );
        })
    )

    postsPromiseArray.forEach(posts => {
        posts.posts.forEach(function (post_id) {
            post_ids.push(post_id);
        });
    })

    return post_ids;
}

GetFeed()
    .then((post_ids) => {
        console.log(post_ids);
        return res.status(200).json({ post_ids: post_ids });
    })

I think this could help you to get a desired output.

friends = user_info.friends;

    async function GetFeed() {
        let post_ids = [];
        try {
           await friends.forEach(async function(friend_id) {
                const posts = await db.collection('users').findOne(
                    { _id: ObjectId(friend_id) },
                    { projection: { posts: 1 }}
                );
                posts.posts.forEach(function(post_id) {
                    console.log(post_id);
                    post_ids.push(post_id);
                });
            });
            return post_ids;
        } catch (err) {
            return res.status(500).json({ error: 'Internal server error, unable to collection post ids from the database.' });
        }
    }

    var post_ids = GetFeed()
    console.log(post_ids);
    return res.status(200).json({ post_ids });
Related