Search code examples
javascriptnode.jsasync-awaites6-promise

Unable to understand asynchronous functions in NodeJS


I have spent 6 hours already trying to figure out how to do this in NodeJS.

I am using NodeJS with Express and MongoDB.

I have a database that has two collections viz. Listings and Categories. Each listing has a "category" which is an ID that maps to a category inside the Categories collection.

What I want to do is, fetch all the listings, then loop through all of them, and get the category title from the Categories collection using the category id.

This is what I had for getting all the listings:

const listings_raw = [];

await db.collection("listings").find().forEach(listing => listings_raw.push(listing));

The code above works fine. The trouble I am having is with this:

const listings = [];

listings_raw.forEach(async listing => {
    const category_id = listing.category;

    const category_title = await db.collection('categories').findOne({_id: objectId(category_id)});

    listing.category_title = category_title;

    listings.push(listing);
});

response.send(listings);

The response.send(listings); gets executed before the listings_raw.forEach completes.

I want to send the data only after all listings have been populated with the category titles.

After spending 6 hours, I have come up with the following hack which I am sure is nasty!

const listings_raw = [];

const em = new events.EventEmitter();

await db.collection("listings").find().forEach(listing => listings_raw.push(listing));

const listings = [];

let counter = 0;

listings_raw.forEach(async (listing) => {
    const category_id = listing.category;

    const category = await db.collection('categories').findOne({_id: objectId(category_id)});

    listing.category_title = category.title;

    listings.push(listing);

    if (counter === listings_raw.length - 1) {
        em.emit('listings:processing:done');
    }

    counter++;
});

em.on('listings:processing:done', () => {
    response.send(listings);
});

Please, can someone explain or guide me on how this should be done in JavaScript?

Basically, I am not able to figure out how to know if all promises have been resolved or not.

Thank you!


Solution

  • The listings_raw.forEach function executes synchronously on the array, even though you are then performing an asynchronous operation within that.

    Promise.all will allow you to await for the result of an array of promises. Therefore you can .map the listings to an array of promises which return the updated listing.

    const listings = await Promise.all(listings_raw.map(async listing => {
        const category_id = listing.category;
    
        const category_title = await db.collection('categories').findOne({_id: dependencies.objectId(category_id)});
    
        listing.category_title = category_title;
    
        return listing;
    });
    
    response.send(listings);