0

I'm trying to create a mongoClient connection pool module to use in my project. The problem is the first time I load the page and the mongoFactory is called, it seems that my mongoConnection is not established yet. Every other time after that I load the page it works just fine. Not sure what i'm missing here..

Here's my mongoFactory.js

/**
 * Creates and manages the Mongo connection pool
 *
 * @type {exports}
 */
var constants = require('../js/constants.js');
var Q = require('q');
var MongoClient = require('mongodb').MongoClient;
var db = null;

var getDb = function() {

  console.log('db = '+db);
  // If the connection pool has not been created or has been closed, create it, else return an existing connection
  if (db === null) {

    var def = Q.defer();

    // Initialize connection once
    MongoClient.connect(constants.mongoUrl, function (err, database) {
      if (err) {
        def.reject(err);
        throw err;
      }

      console.log('inside connection');
      db = database;
      def.resolve();
    });

    def.promise.then(function () {
      console.log('before returning connection');
      return db;
    });
  } else {
    console.log('connection already exists. returning');
    return db;
  }
}

module.exports.getDb = getDb;

Here's my file that i'm calling the mongoFactory

var mongoFactory = require('../../lib/mongoFactory');

module.exports = function() {

  return {

    find: function find(callback) {

      var db = mongoFactory.getDb();
      var cases = db.collection('mycollection'); // <-- First time i load the page, errors b/c db is undefined. Every other time after that it works just fine. ERROR: TypeError: Uncaught error: Cannot call method 'collection' of undefined
...
Stennie
  • 63,885
  • 14
  • 149
  • 175
Catfish
  • 18,876
  • 54
  • 209
  • 353
  • Doesn't use promises, but related: http://stackoverflow.com/questions/24621940/how-to-properly-reuse-connection-to-mongodb-across-nodejs-application-and-module – go-oleg Aug 22 '14 at 16:28
  • Similar yes, but i don't think it's quite the same. From what I read, `require` is synchronous so I would think that requiring mongoFactory.js would finish before the next line is executed. – Catfish Aug 22 '14 at 17:53
  • Yea, your `getDb` is the one thats "not synchronous" so it should probably return a promise as opposed to the db (which it can't do synchronously if it requires connecting to mongo). – go-oleg Aug 22 '14 at 17:56
  • But i have a promise inside my getDb() function so wouldn't it not return until that promise is resolved? – Catfish Aug 22 '14 at 17:59
  • 1
    No, that `return db` is returning from the anonymous function you pass to `def.promise.then()` not from `getDb`. If your code flow goes into `db === null`, the function will return nothing. – go-oleg Aug 22 '14 at 18:03
  • Can you provide an example of how I would do it then? – Catfish Aug 22 '14 at 18:05

3 Answers3

2

Your mongoFactory.getDb should return a promise which will be fulfilled once the connection is established (which the first time around will take some time and after that will happen immediately).

/**
 * Creates and manages the Mongo connection pool
 *
 * @type {exports}
 */
var constants = require('../js/constants.js');
var Q = require('q');
var MongoClient = require('mongodb').MongoClient;
var db = null;

var getDb = function() {

  var def = Q.defer();

  console.log('db = '+db);
  // If the connection pool has not been created or has been closed, create it, else return an existing connection
  if (db === null) {

    // Initialize connection once
    MongoClient.connect(constants.mongoUrl, function (err, database) {
      if (err) {
        def.reject(err);
      }

      console.log('inside connection');
      db = database;
      def.resolve(db);
    });
  } else {
    def.resolve(db);
  }

  return def.promise;
}

module.exports.getDb = getDb;

and to use:

var mongoFactory = require( './mongoFactory' );
var con = mongoFactory.getDb();

con.done( function(db) {
    // use db here
} );

con.fail( function(err) {
   // error occurred
} );

You may want to checkout mongoskin which is a promise wrapper around node-mongodb-native.

go-oleg
  • 19,272
  • 3
  • 43
  • 44
1

Constructing a deferred inside a function doesn't automatically make it blocking - in fact it can't, promises are still asynchronous.

Your function does indeed return nothing (i.e. undefined) in the case that db === null, and only returns the database connection if it already has been established from a previous call. Instead, it should always return a Promise which represents the (possibly later arriving) eventual connection.

/**
 * Creates and manages the Mongo connection pool
 *
 * @type {exports}
 */
var constants = require('../js/constants.js');
var Q = require('q');
var MongoClient = require('mongodb').MongoClient;
var dbPromise = null;

module.exports.getDb = function getDb() {
  // If the connection pool has been created or is being created, return the existing promise for that, else create it and return the promise for that
  if (dbPromise === null) {
    var def = Q.defer();

    // Initialize connection once
    MongoClient.connect(constants.mongoUrl, function (err, database) {
      if (err)
        def.reject(err);
      else
        def.resolve(database); // let the database be the result value of the promise!
    });
    return dbPromise = def.promise;
  } else {
    return dbPromise;
  }
};

Now that your function returns a promise, you will need to use it inside the then callback:

find: function find(callback) {
  return mongoFactory.getDb().then(function(db) {
    // db is always available in the promise callback
    var cases = db.collection('mycollection');
    // return a promise or a value from the callback
  }); // and find will return a promise for that value
}
Community
  • 1
  • 1
Bergi
  • 630,263
  • 148
  • 957
  • 1,375
0

MongoClient.connect is async in nature, why not doing it the nodejs way? I would change your getDb signature to accept a callback:

var getDb = function(callback) {
  console.log('db = '+db);
  // If the connection pool has not been created or has been closed, create it, else return an existing connection
  if (db === null) {

    // Initialize connection once
    MongoClient.connect(constants.mongoUrl, function (err, database) {
      if (err) {
        callback(err);
        return;
      }

      console.log('inside connection');
      db = database;
      callback(null, db);
    });
  } else {
    console.log('connection already exists. returning');
    callback(null, db);
  }
}

Then your consumer must:

mongoFactory.getDb(function(err, db) {
  if (err) {
     // handle error
  }
  var cases = db.collection('mycollection');
});