0

In my mongoDB database I have a Posts model, which contains array of references to a Comments model.

For one of the get requests in my Express application I have to respond with an array of all the Posts in the database with each Post containing an array of all the comments it has

The method I chose to do this is, get all the Posts using find, which returned an array of all the posts, each post containing array of references to the comments it has, according to my model.

To resolve those references I chose the populate method provided by mongoose, which returns the actual objects from the database against the references it contains, which was pretty straightforward for a post

await post.populate("comments").execPopulate();

here post is the single post instance and after this operation the post instance contains all its comment references resolved into comment objects

but to do that individually for every post I tried two methods, where allPosts is the array of all posts I get after the find query

  1. using the above execPopulate operation, create an array of all promises, without regard for whether they were resolved, then using Promise.all() perform further operation after all were resolved
let promiseArray = allPosts.map( (post) => {
    post.populate("comments").execPopulate();
});


Promise.all(promiseArray).then((posts) => {

    // this map is another extra irrelevant operation I performed to 
    // format the response array in a particular way, where
    // I had to remove extra fields in both the posts array
    // as well as the populated comments array

    
    let responseArray = posts.map((post) => {
        return {
            comments: filterComments(post.comments),
            _id: post._id,
            title: post.title,
            commentcount: countComments(post.comments),
        };
    });

    return res.send(responseArray);
});

  1. the other method I tried was to set up a while loop, where I wait for every post to be resolved individually in every iteration, then push the resolved and formatted post object in an array(reponse array) and after completion send the response array
let responseArray = [];
let length = allPosts.length;
let counter = allPosts.length;

while(counter) {

    // get the first post because counter is reverse
    let post = allPosts[length - counter]

    // the population operation where every post array
    // is populated with its comments
    await post.populate('comments').execPopulate();

    // push the filtered object to another array
    responseArray.push({
        comments        : filterComments(post.comments),
        _id             : post._id,
        title           : post.title,
        commentcount    : countComments(post.comments),
    })

    counter --;
}

return res.send(responseArray);

the application took thrice the time to respond to the same set of data of around 150 or so posts when using while loop to resolve individual promises, and was faster when I stored all promises in array and then resolved them in Promise.all(), I don't know the JS event loop very much in detail so wanted to know if there's something causing this

TL;DR :

I have a list of async operations to perform on every element in an array, I tried:

  1. iterate through the array storing all the unresolved promises in an array, then doing further operation after using Promise.all() on the array of promises

  2. iterate through the array using while loop, performing the async operation for every element on every iteration

the while loop takes almost thrice the time, resolution on Promise.all() takes

Mayur
  • 150
  • 7
  • 1
    `allPosts.map( (post) => { post.populate("comments").execPopulate(); });` would not produce an array of promises, but an array of `undefined`. So `Promise.all([undefined, undefined, /* .... */, undefined])` will basically resolve immediately. – VLAZ Dec 10 '20 at 12:37
  • you should remove the parenthesis `{}` at your map method – bill.gates Dec 10 '20 at 12:38
  • 1
    At any rate, with the loop, you wait for each operation to finish before moving to the next one. Which will finish first - setting three timers for 1 minute each at once and waiting for all to finish, or setting a timer for one minute, once it's done doing it again, and then a third time? – VLAZ Dec 10 '20 at 12:39
  • no they were not resulting in undefined but the array on console.log gave, an array of Promise() – Mayur Dec 10 '20 at 12:44
  • `await` in a loop is running all your populate calls sequentially. Of course that's slower! – Bergi Dec 10 '20 at 14:51

1 Answers1

-1

I am not an expert Javascript developer but as much as I know.

Event Loops does not run on a single thread so when using Promise.all() promise gets executed at the same time. Generally, using Promise.all() runs requests "async" in parallel. Execution in parallel save time and that's the reason Promise.all() is faster than await for each promise.

Note: promises will execute once they created. in your code, you have created promises in the map method.

Promise.all just wait for each promise to get fulfilled.

on the contrary async/ await create a promise wait for it then create another.

However, you could have directly populate comments of all the posts. using the below code, which is faster than both solution

const PostsWithComments = await PostsModel.find({}).populate("comments").exec()

JustRaman
  • 1,101
  • 9
  • 11
  • yeah I tried that, it was fine for one post, but there is an array of posts that I need to do this for, so the only way to do that was in some loop, and I used while loop to do it – Mayur Dec 10 '20 at 12:49
  • 2
    `Promise.all` doesn't induce parallelism or multithreading. A "promise" is also not a "task", it's a pending value. The task might not even be carried out in the JS engine at all. For example, accessing the database, means you make a request and wait to get the result from it, no processing. – VLAZ Dec 10 '20 at 12:49
  • 1
    @Mayur populate does work for an array of posts too. the snippet should work for multiple posts too. you don't need to do post-operation for that. mongoose will do for you. – JustRaman Dec 10 '20 at 12:52
  • @RamanShekhawat ohh, never thought about doing that for the whole array at once, guess I'll try that and check, regardless irrespective of this problem I am still intrigued as to why the promises were slower in being resolved in the while loop – Mayur Dec 10 '20 at 12:56
  • This is not accurate information. – Pointy Dec 10 '20 at 13:01
  • OK, let me be clear - the entire second paragraph is wrong. 1. For the most part, there is a single main thread unless you opt into multithreading with something like workers. 2. Promises don't get "executed", an execution results in a promise. Promises aren't tasks. 3. Related, `Promise.all` doesn't "run" anything. It is a mechanism for waiting multiple promises. Once you have a promise, it's already "running" - there is something, somewhere that is working on it and you'll get the value it produces in the future. That's what a promise is - a placeholder for that value. – VLAZ Dec 10 '20 at 13:02
  • @VLAZ my apologies updated the answer. – JustRaman Dec 10 '20 at 13:14