3

I might be doing it wrong with Mongoose after using it for a while. I find it difficult to read when you have too much callbacks, eg:

I want to find a document and update it. I need to find it first, then the update will be inside the callback of the findOne.

var sam = new Character({ name: 'Sam', inventory: {}});

Character.findOne({ name: 'Sam' }, function(err, character) {
      console.log(character);

      // now I want to update it.
      character.update({... }, function(err, characterID) {

          // now I want to know the size of the document after the update.
          Character.findOne({ _id: characterID }, function(err, character) {
            // Now update it again....
        });
    });

});

It ends up a spaghetti code eventually!

You see what I mean?

Any ideas how to do it better?

Or mongodb native is better without all these callbacks?

EDIT:

Character.findOne({...}, function(err, character) {

    return character.update(...);

}).select("-field1 -field2").then(function(data) {
      //
}).catch(function(error) {
    // Handle any error from all above steps
}).done();

Error:

TypeError: Character.findOne(...).select(...).then(...).catch(...).done is not a function
6
  • 2
    Besides using promises, you could also consider using findOneAndUpdate. Commented Mar 25, 2017 at 9:35
  • 1
    You can use node q promise api github.com/kriskowal/q to avoid the callback hell Commented Mar 25, 2017 at 9:35
  • @Gaurav it is a 404! Commented Mar 25, 2017 at 9:37
  • 1
    @teelou i don't know why its showing you 404!, it works. Commented Mar 25, 2017 at 9:44
  • 1
    Promise is the answer for all callback hell cases. But for now, ES7 async/await (with babel configuration) make your code more readable and easy to use than Promise. Commented Mar 26, 2017 at 14:51

3 Answers 3

2

You can use chaining of q promise in nodejs

var Q = require('q');

function findOne(filter) {
    return Q.Promise(function(resolve, reject) {
        Character.findOne(filter, function(err, character) {
            resolve({
                err: err,
                character: character
            });
        });
    });
}

function update(data) {
    return Q.Promise(function(resolve, reject) {
        character.update(data, function(err, characterID) {
            resolve({
                err: err,
                characterID: characterID
            });
        });
    });
}

findOne({ name: 'Sam' }).then(function(data) {
    if (!data.err) {
        // now you can update it.
        return update(data.character);
    } else {
        throw new Error(data.err);
    }
}).then(function(data) {
    if (!data.err) {
        // now you can update it.
        return update(data.characterId);
    } else {
        throw new Error(data.err);
    }
    return findOne({ id: characterId });
}).then(function(data) {
    if (!data.err) {
        // now you can update it.
        return update(data.character);
    } else {
        throw new Error(data.err);
    }
}).catch(function(error) {
    // Handle any error from all above steps
}).done();
Sign up to request clarification or add additional context in comments.

3 Comments

btw, why there is a .exec() in your example?
exec() function is used for execute the query at a later time. see the docs mongoosejs
got it. another question - how do I catch the errors? in my example, there are err if you notice that.
2

Since Mongoose supports promises (documented here), you can rewrite your code to this:

var sam = new Character({ name: 'Sam', inventory: {}});

Character.findOne({ name: 'Sam' }).then(character => {
  return character.update(...);
}).then(characterID => {
  return Character.findOne({ _id: characterID });
}).then(character => {
  ...
}).catch(err => {
  // TODO: handle error.
});

There's not need to wrap each function you use using an external promise library. You may get a warning about Mongoose using a deprecated promise library, which is easily fixed by including this at the top of your code:

mongoose.Promise = global.Promise;

Documented here.

However, you can replace the entire promise chain above with a single command:

Character.findOneAndUpdate({ 
  name : 'Sam'
}, {
  $set : { 
    inventory : {}
  }
}, { 
  new : true
}).then(character => {
  ...
});

Documented here.

9 Comments

thanks for the tip! but I get an error when I try to chain it with select. see my edit above.
@teelou the documentation states that queries (and query-related functions , like .select()) don't return real promises. If you want one, add .exec: Character.findOne(...).select(...).exec().then(...)
sorry i still get .findOne(...).select(...).exec(...).then(...).catch(...).done is not a function.
@teelout oh sorry, missed it: done also isn't a standard promise-related function, but you generally don't need to use it anyway if you have a .then and a .catch
@teelou it could be that those libraries implement .done; if you really want to use it, you can use either of those as the default promise library that Mongoose will use (doc).
|
1

You have now discovered "callback hell". This is not limited to Mongoose, or Mongo, but is part of all Node.js programming, and don't feel alone. All of us have had to deal with it.

In MongoDB (or any data event with most DB's) the problem is the chain of events taking place and the need to catch the errors or make the program wait for the step to be completed.

  1. Promises can be either a async or sync solution to callbacks. You can use bluebird to wrap your events in Promises. This requires an understanding of the .then and return constructs. However in essence what you do is say: "Do step 1 and then do step 2." (and so on). Remember though, depending upon the coding you still have to catch errors, and you can easily turn your entire code into sync which is not an ideal situation in Node.js. However, it will solve the "callback hell" of Mongo connections. What I am saying is try not to fall into the habit of putting all your code into the .then --> return paradigm, but only those places where it really is required.

  2. The new MongoDB Node Driver (v. 2.2.5) is much better than the previous. Mongo has instantiated ECMAScript 6 if you include the co module, which supplies the yield and return events (read below about async/await, as it is essentially the same thing). This is lightyears ahead of what the driver was until now. This will help you get out of "callback hell" with your Mongo connections and CRUD operations.

So to get out of "callback hell" the normal response is to use promises.

To make matters a bit more complicated, the new Node.js (not the LTS) but versions above 7.7XX which are not yet official use a long awaited async/await structure from ES7. This should keep your code async with promises. An outside article on this can be seen here.

Depending upon your app and your requirements, try the use of Promises, or you can simply use the new MongoDB driver with co for Mongo. As to which module to use, be it bluebird, co, or something else, that is up to you. When Node.js officially releases the async/await and it works as advertised, it would be a good time to use that model instead of callbacks.

5 Comments

thanks. can't wait for the new NodeJS! It seems that I have to deal with callbacks less in PHP...
btw, bluebird has a poor documentation - it does tell you how to use it! var Promise = require("bluebird"); that's all you get from the site.
@teelou as a PHP & Laravel programmer myself, don't make that mistake. Node.js and Mongo belong together. I personally do not use Mongoose, as I find the idea of scheming out a collection counter-intuitive to the NoSQL paradigm. It is like saying since I don't have MySQL let me turn MongoDB into a MySQL structure. Just does not make sense in BigData as the whole purpose is not to have that type of structure. But it does pay to get a lock on the promise paradigm as you will need it in Node.js, sooner or later.
@teelou Bluebird documentation is horrible, I agree. It starts from the point where it assumes you "get" promises and the whole sync - async paradigm. Actually you will find many who complain about the documentation. You are not alone.
haha... I will go for github.com/kriskowal/q for now then! thank you! :-)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.