Converting Mongoose model functions to be Promise and callback friendly simultaneously

I just started using promises in one of my Node.js applications after watching the callback hell in some of the code there.

I was going to use Q but while checking the different options I stumbled upon Bluebird and I liked it.

At some time I checked how Mongoose works with promises and discovered that many functions return a promise and also the queries’ exec() returns a promise too. The Promise in Mongoose is mpromise.

However I decided to stick to a single implementation of Promise for my entire application to have something as a standard and predictable way to use “promisified” functions. And this happened to be Bluebird. It’s huge(features) and I like it!

The first thing to “clean” was a function in one of my Mongoose’s models: User.create() – a custom static function that overrides the default create function. I needed the override to be able to receive custom structured arguments and also to do some changes before the model data is saved. I changed the way I handle the create later by removing it and using pre-hooks instead but this is a good example to show and is like a repeatable template for any other function that is going to be converted the same way.

Let’s see the original Promise-free function:

userSchema.static('create', function (data, cb) {
  var user = new User({
    facebookId: data.facebookId,
    accessToken: hat(),
    name: data.name,
    photo: data.photo
  });
  return user.save(cb);
});

You can see that User.create() receives a data object literal and executes the callback cb after it’s saved.

An example usage of create will be:

User.create({
  facebookId: '68yc7j9y4f64',
  name: 'Iliyan Trifonov',
  photo:'https://...'
}, function userCreateCb (err, user) {
  if (err) {
    return console.error(err);
  }
  if (!user) {
   return console.error('User not created!');
  }
  console.info('User created successfully!', user);
});

Now let’s make User.create return a promise:

First we promisify the model with:

Promise.promisifyAll(User);
Promise.promisifyAll(User.prototype);

and then the create function will look like:

userSchema.static('create', function (data) {
  var user = new User({
    facebookId: data.facebookId,
    accessToken: hat(),
    name: data.name,
    photo: data.photo
 });
 return user.saveAsync();
});

Now we should handle the resolve and reject:

User.create({
  facebookId: '68yc7j9y4f64',
  name: 'Iliyan Trifonov',
  photo:'https://...'
}).then(function (user) {
  console.info('User created successfully!', user);
}).catch(function (err) {
  console.error('Error creating the user!', err);
});

For now I am experimenting with the then(resolveCb)/catch(errorCb) combination but it can be changed to then(resolveCb, rejectCb) or then(resolveCb)/then(null, rejectCb).

But what about the rest of the code that depends on User.create to use a callback?

Let’s add the callback argument again:

userSchema.static('create', function (data, cb) {
  var user = new User({
    facebookId: data.facebookId,
    accessToken: hat(),
    name: data.name,
    photo: data.photo
  });
  return user.saveAsync().then(function (user) {
    return cb(null, user);
  }).catch(function (err) {
    return cb(err);
  });
});

And now the create function will work with the legacy code and the new Promises code simultaneously!

 

JavaScript: from callback hell through then-hell to generators+promises

Lately I've been refactoring and then refactoring and then some one of my JS apps every time I ...

Learn more

About Iliyan Trifonov

Web Developer, Blogger
Tagged , , , , . Bookmark the permalink.