9

So there are a few other queries around this subject such as: How can I promisify the MongoDB native Javascript driver using bluebird?

However it does not seem to address the latest version of the driver, which seems to have issues when trying to promisify. Currently I can get the MongoClient working by doing:

Promise.promisifyAll(mongodb.MongoClient); // Using .Prototype here fails to promisify

However no matter what I try the Collections do not seem to operate using the *async calls, it may invoke them but they never get resolved or rejected so they just hang in limbo.

Historically in the previous versions you would just Promise.promisifyAll(mongodb) and you were done, but I am unsure as to how to correctly handle this in the new driver.

Here is an example output of a collection which has been created using the mongo direct promisification connectAsync then getting the collection from the returned db. Once I try to do anything on the collection it just hangs and promises dont return from it:

{ s: { pkFactory: { [Function: ObjectID] index: 14727641, createPk: [Function: createPk], createFromTime: [Function: createFromTime], createFromHexString: [Function: createFromHexString], isValid: [Function: isValid], ObjectID: [Circular], ObjectId: [Circular], createPkAsync: [Object], createFromTimeAsync: [Object], createFromHexStringAsync: [Object], isValidAsync: [Object], bindAsync: [Object], toStringAsync: [Object], callAsync: [Object], applyAsync: [Object], lazyAsync: [Object], throttleAsync: [Object], debounceAsync: [Object], delayAsync: [Object], everyAsync: [Object], cancelAsync: [Object], afterAsync: [Object], onceAsync: [Object], fillAsync: [Object] }, db: { domain: [Object], _events: {}, _maxListeners: undefined, s: [Object], serverConfig: [Getter], bufferMaxEntries: [Getter], databaseName: [Getter], options: [Getter], native_parser: [Getter], slaveOk: [Getter], writeConcern: [Getter] }, topology: { domain: [Object], _events: [Object], _maxListeners: undefined, connectTimeoutMS: 500, s: [Object], bson: [Getter], isMasterDoc: [Getter], poolSize: [Getter], autoReconnect: [Getter], host: [Getter], port: [Getter], emitOpen: false, socketTimeoutMS: 0 }, dbName: 'some-db-name', options: {}, namespace: 'some-namespace', readPreference: null, raw: undefined, slaveOk: false, serializeFunctions: undefined, internalHint: null, collectionHint: null, name: 'some-collection-name' } }

Community
  • 1
  • 1
Grofit
  • 17,693
  • 24
  • 96
  • 176
  • For anyone else looking at this, in Mongo 2.* they seem to change what is returned from certain methods, like findAsync now seems to return some huge models rather than the documents which I was getting before, so this issue is a half way house between having to migrate logic and having to promisify it correctly. – Grofit Mar 19 '15 at 16:29

4 Answers4

8

You can promisify it directly after requiring, as exemplified on bluebird API docs, like this:

var Promise = require("bluebird");
var MongoDB = Promise.promisifyAll(require("mongodb"));
var util = require('util');

console.log(util.inspect(MongoDB, { showHidden: true }));

Using bluebird 2.9.14 and mongodb driver 2.0.22, I got this (simplified) results:

  // ....
  Collection: 
   { [Function]
     [length]: 6,
     [name]: '',
     [arguments]: [Getter/Setter],
     [caller]: [Getter/Setter],
     [prototype]: 
      { [constructor]: [Circular],
        collectionName: [Getter],
        // .... 
        findAsync: [Object],
        insertOneAsync: [Object],
        insertManyAsync: [Object],
        bulkWriteAsync: [Object],
        insertAsync: [Object],
        updateOneAsync: [Object],
        replaceOneAsync: [Object],
        updateManyAsync: [Object],
        updateAsync: [Object],
        deleteOneAsync: [Object],
        removeOneAsync: [Object],
        deleteManyAsync: [Object],
        removeManyAsync: [Object],
        removeAsync: [Object],
        saveAsync: [Object],
        findOneAsync: [Object],
        // ....

And queried successfully like this:

MongoDB.connectAsync('mongodb://localhost:27017/test').then(function(db) {
    return db.collection("orders").findOneAsync({});
}).then(function(orders) {
    console.log(orders);
}).catch(function(err) {
    console.log(err);
});

UPDATE

Using the MongoClient object works as well:

var Promise = require("bluebird");
var MongoDB = Promise.promisifyAll(require("mongodb"));
var MongoClient = Promise.promisifyAll(MongoDB.MongoClient);

MongoClient.connectAsync('mongodb://localhost:27017/test').then(function(db) {
    return db.collection("orders").find({}).toArrayAsync();
}).then(function(orders) {
    console.log(orders)
}).catch(function(err) {
    console.log(err);
});
victorkt
  • 13,992
  • 9
  • 52
  • 51
  • I do the above and I can see that there are async methods on some things, but the `MongoClient` object only contains `connect` not `connectAsync`. However in your example you imply you connect directly on the mongodb object not on the `mongodb.MongoClient` object which is what is listed in the documentation: http://mongodb.github.io/node-mongodb-native/2.0/ can you run your code again and output the `MongoClient` object then that may shed some light on the matter as the rest of it could be bound fine but `connectAsync` simply doesn't exist using your example above. (with MongoClient) – Grofit Mar 18 '15 at 21:13
  • I can confirm that the above connection via the base mongodb object works in this manner, but the other objects still do not behave as expected. For example I connect up now using the way shown above, and then create a collection the object not seem to look like yours. I will post an edit with the output of my collection objects. – Grofit Mar 19 '15 at 09:04
  • I updated my answer with an example using the `MongoClient` object. The output I posted was from the base `MongoDB` object, not from `db.collection()`. Still, I could query the database as usual using the *Async functions. – victorkt Mar 19 '15 at 12:30
  • I am going to mark your answer as correct as it seems there is a bit more at play here. as the 2.* version adds some new methods and changes the behavior of existing ones slightly, so I think I was having the issue of some things such as `remove` not working as expected but if I move to `deleteOne` or `deleteOneAsync` it works correctly, so you are correct in your assertions, however anyone else with a similar issue will need to look at migrating their async calls over to the newer ones I think. Thanks again for the help. – Grofit Mar 19 '15 at 13:05
7

By default, mongodb driver always return a promise if you don't specify a callback. But you can instruct it to return promises using your preferred promises library.

Here is a simple method to use bluebird promises when using node-mongodb-native 2.0 driver:

var Promise = require("bluebird");
var MongoClient = require("mongodb").MongoClient; // Doesn't require promisification

/*...*/
function saveData(data) {
  MongoClient
    .connect(MONGO_CONNECTION_STRING, {
      promiseLibrary: Promise // Here you instruct to use bluebird
    })
    .then(function(db) {
      return db
        .collection('myCollection')
        .insert(data)
        .finally(db.close.bind(db))
    })
    .catch(function(err) {
      console.error("ERROR", err);
    });
}
David Rissato Cruz
  • 3,347
  • 2
  • 17
  • 17
  • Be aware of this note too: http://bluebirdjs.com/docs/api/resource-management.html – Tom Dec 04 '15 at 23:34
  • If you're using find, then you have to action the cursor before finally closing the db, but this seems the best solution. – conradj Jun 26 '16 at 21:30
  • Yes, you need to consume it before closing. In fact, one would prefer leaving connections opened, because it is quite expensive to be connecting and disconnecting on every query. I just tried to create a sort of "full cycle example", but I strongly recommend to reuse your connections. Mongo driver can manage a connection pool almost transparently. – David Rissato Cruz Jun 27 '16 at 01:00
1

Streamlined and more realistic version:

var Promise = require('bluebird');
var MongoDB = Promise.promisifyAll(require('mongodb'));

MongoDB.MongoClient.connectAsync('mongodb://localhost:27017/test')
  .then(function(db) { // Expose db to query logic
    // need to return a promise to let outer catch handle it
    return db.collection("orders").find({}).toArrayAsync()
      .then(function (orders) {
        console.log(orders);
      })
      // Ensure that db is closed at the end no matter what...
      .finally(db.close.bind(db)); 
      // No need for another catch here, the outer one will handle it 
  })
  .catch(console.log.bind(console));

Promise nesting is done on purpose to expose db to the rest of logic. The same can be done without nesting by either passing or declaring 'db' globally. Tried it all and this one is the most elegant.

kornieff
  • 2,389
  • 19
  • 29
0

You can read the source of MongoDB Native Driver:

MongoClient.connect = function(url, options, callback) {
  var args = Array.prototype.slice.call(arguments, 1);
  callback = typeof args[args.length - 1] == 'function' ? args.pop() : null;
  options = args.length ? args.shift() : null;
  options = options || {};

  // Get the promiseLibrary
  var promiseLibrary = options.promiseLibrary;

  // No promise library selected fall back
  if(!promiseLibrary) {
    promiseLibrary = typeof global.Promise == 'function' ?
      global.Promise : require('es6-promise').Promise;
  }

  // Return a promise
  if(typeof callback != 'function') {
    return new promiseLibrary(function(resolve, reject) {
      connect(url, options, function(err, db) {
        if(err) return reject(err);
        resolve(db);
      });
    });
  }

  // Fallback to callback based connect
  connect(url, options, callback);
}

The promiseLibrary can be setted Bluebird

Richard Xue
  • 307
  • 2
  • 5