1

I'm trying to print list of database names following their corresponding collection names using a nested loop with MongoDB Node.js Driver.

Database A:
Collection a
Collection b
Database B:
Collection f
Collection g
Database C:
Collection h
Collection j

but instead I get this:
Database A:
Database B:
Database C:
Collection a
Collection b
Collection f
Collection g
Collection h
Collection j

What am I doing wrong? Is this because listDatabases() and listCollections() sharing the same MongoClient connection and they can't be executed simultaneously or it is just promise thing?

const mongo = require('mongodb').MongoClient;

const url = 'mongodb://127.0.0.1:27017';

mongo.connect(url, {
    useNewUrlParser: true,
    useUnifiedTopology: true
}, (err, client) => {
    if (err) {
        console.log(err)
        return;
    }
    // print db names with their corresponding collections
    client.db().admin().listDatabases({ nameOnly: true }, (err, result) => {
        if (!err) {
            result.databases.forEach((db) => {
                console.log('Database: ' + db.name);

                client.db(db.name).listCollections().toArray((err, collections) => {
                    collections.forEach((col) => {
                        console.log(col.name);
                    });
                });

            });
        } else {
            return console.log(err.message);
        }
    });
});
ambianBeing
  • 3,449
  • 2
  • 14
  • 25
Mr. L
  • 101
  • 12
  • `dbName` is an optional parameter of db(). From the [documentations](http://mongodb.github.io/node-mongodb-native/3.1/api/MongoClient.html#db) – Mr. L Nov 09 '19 at 20:12
  • Yes! Just an FYI with `mongodb@3.3.1` (which i was on) this was not the case but with `V3.3.3` latest as of now `dbName` optionality is working indeed. – ambianBeing Nov 11 '19 at 13:50

2 Answers2

1

try to convert it to promise and use the map to async await the results thus https://flaviocopes.com/javascript-async-await-array-map/ may be helpfull

M.Amer
  • 658
  • 6
  • 13
  • Convert what..? – Mr. L Nov 09 '19 at 19:05
  • 1
    I am soory It was not clear. get a list of databases and save it in array. and then loop over them using map to get their collections. you need to use async/await in map so ckeck the link – M.Amer Nov 09 '19 at 20:47
1

This is because client.db(db.name).listCollections() is async operation. To put it very plainly the result.databases.forEach is not going to wait for each listing of collections for the current db that just got printed above. Which is evident from the output.

That is how callbacks with async operations behave. This could be made nicer to look with async/await or promise/then syntaxes. If sticking to callback syntax, simply moving console of db inside would achieve the output.

// print db names with their corresponding collections
client
  .db("test")
  .admin()
  .listDatabases({ nameOnly: true }, (err, results) => {
    if (err) {
      return console.error("Error::", err.message);
    }
    results.databases.forEach((item, index) => {
      client
        .db(item.name)
        .listCollections()
        .toArray((err, collections) => {
          if (err) {
            return console.error("Error::", err.message);
          }
          console.info("DATABASE: ", item.name);
          collections.forEach(col => {
            console.info("COLLECTION: ", col.name);
          });
        });
    });
  });

UPDATE: With async/await syntax

const MongoClient = require("mongodb").MongoClient;
const url = "mongodb://localhost:27017";
const client = new MongoClient(url, {
  useNewUrlParser: true,
  useUnifiedTopology: true
});
const defaultDb = "test";

(async function() {
  try {
    await client.connect();
    //fetching dbs
    const dbs = await client
      .db(defaultDb)
      .admin()
      .listDatabases({ nameOnly: true });

    for (db of dbs.databases) {
      console.info("Database::", db.name);
      const collections = await client
        .db(db.name)
        .listCollections()
        .toArray();

      for (c of collections) {
        console.info("Collection::", c.name);
      }
    }
    client.close();
  } catch (err) {
    console.log(err.stack);
  }
})();

Notice the for..of syntax with await because forEach doesn't behave the way intended Why?. And a plain for(let i=0 ... i++) would also work just fine with await.

ambianBeing
  • 3,449
  • 2
  • 14
  • 25