Search code examples
node.jsmongodbexpressmongoose

Delay task dependent multiple asynchronous MongoDB queries


I would like to pull arrays from multiple collections from my MongoDB database. The initial code assumes the arrays 'links' & 'sites' are substantiated. I would prefer to collect this array data elsewhere in my mongoDB database and draw from it when needed. My initial assumption was that I could collect from the database, before calling render...

// connect to database.
mongoose.connect(dbURI, { useNewUrlParser: true, useUnifiedTopology: true })
  .then((result) => app.listen(5000))
  .catch((err) => console.log(err))

// create database object.
const db = mongoose.connection;

// render the page.
app.get('/', (req, res) => {
  db.collection('links').find({...})
     .then((result) => {
         res.render('index', { title: "Home", result, sites })
     })
     .catch((err) => {
        console.log(err)
     })
});

The issue with this attempt is that I can only draw from one collection. I believe this is a good guarantee that I will not have issues waiting for the asynchronous tasks between MongoDB and my app, but I would like to know another way that I can guarantee that the information is there from separate collections before I continue. As an example of the incorrect way, I would do:

// connect to database.
mongoose.connect(dbURI, { useNewUrlParser: true, useUnifiedTopology: true })
  .then((result) => app.listen(5000))
  .catch((err) => console.log(err))

// create database object.
const db = mongoose.connection;
const links_res = db.collection('links').find({...})
const sites_res = db.collection('sites').find({...})

// render the page.
app.get('/', (req, res) => {
   res.render('index', { title: "Home", links_res, sites_res })
});

I do not think there is any guarantee that the information is accessible when render is called. How should I guarantee that?


Solution

  • Remember that db.collection('links').find({ ...}) gives you a promise, not a database result. You have to await or .then() that promise to get the actual result.

    You can use Promise.all() like this:

    const links_p = db.collection('links').find({ ...})
    const sites_p = db.collection('sites').find({ ...})
    
    // render the page.
    app.get('/', async (req, res) => {
        try {
            const [links_res, sites_res] = await Promise.all([links_p, sites_p]);
            res.render('index', { title: "Home", links_res, sites_res})
        } catch(e) {
            console.log(e);
            res.sendStatus(500);
        }
    });
    

    Note, you also need to fix your res.render() call which was not passing arguments correctly. The 2nd argument to res.render() should be an object with properties. You pass multiple pieces of data by creating multiple properties on that object.

    Since most incoming requests will arrive long after those two promises are resolved, the Promise.all() will typically be instant so you don't really need to worry about the fact that you're always waiting on the promise.

    But, a possible problem with this code is that the db query is static. The query is done once at server startup and never again updated. And, if you ever get an error on those initial queries, you aren't really handling it well.