Using Promises in Node.js Applications using Bluebird

Posted by: Ravi Kiran , on 9/9/2015, in Category Node.js
Views: 18469
Abstract: Implement Promises in Node.js using the Bluebird provider

Node.js is a platform created to build server applications using JavaScript. As discussed in first article in this series Node.js Tutorial Series - Getting Started, Node.js assumes that all I/O and network operations are slow and it runs them asynchronously. The asynchronous task frees the event loop when its task runs by the worker threads. This lets the event loop to run another task from the queue.

 

The default APIs provided by the platform and the other packages for asynchronous operations are based on a callback approach. Though this approach solves the problem, it doesn’t fit to all scenarios. Callback approach in Node.js brings the same set of difficulties that we have with JavaScript on the browser today. The callback approach becomes less readable and maintainable when we compose dependent asynchronous calls. Promises solve this problem by providing an easy to use API.

What is a Promise?

A promise is an object that holds an asynchronous operation and notifies on completion of the operation. At a given point of time, a promise is in one of the following states:

  • Pending: When execution of the operation is still in progress
  • Success: Once execution of the operation is successfully completed
  • Failure: Once execution of the operation is failed

The promise object accepts callbacks that get invoked when the operation either succeeds or, fails. Dependent asynchronous operations can be chained together to make them look synchronous and yet achieve the same result, that we get when we use the callback approach.

Promises in Node.js

We have multiple NPM packages available from different open source projects to support promises in Node.js. ‘Bluebird’, ‘RSVP’ and ‘q’ are some of the well-known promise packages in Node.js. These packages solve the problem well and each of them differs a bit in terms of syntax. Out of this these three packages, I like Bluebird, as the usage of this package is similar to using ES6 promises.

Examples in this article would be based on Bluebird. The idea and logic of using promise would remain the same, so you can replace bluebird with any other promise provider of your choice.

Using Promises with Mongoose

The last article in this series was on performing CRUD operations on Mongo DB using Mongoose. As we have seen, all of the operations exposed by this driver are asynchronous. In a typical Node.js application using Mongoose, the data operations are performed in a separate layer and they are called from the layer that exposes the REST APIs or, a layer that performs server side validations. Following example show snippets of a sample model and a sample API that uses Mongoose without promises:

//API
app.get('/api/todos/:id', function(request, response){
  todosDataOps.getTodo(request, response);
});

//Model
exports.getTodo = function(request, response){
  todoModel.find({id: parseInt(request.params.id)}).exec(function (error, results) {
    if(error){
      response.status(500).send({error: error});
    }
    else {
      response.send(results);
    }
  });
};

The problem with the above snippet is that the model layer directly operates on the request and response objects and it composes the response to be sent from the API. Because, otherwise it is not possible to separate the concerns if we use the callback approach to handle the asynchronous task.

Let’s refactor the above code using promises.

As a first step, we need to install the Bluebird promise package in the project.

> npm install bluebird

Now we need to include this package in the model layer.

var Promise = require('bluebird');

Let’s use this promise in the getTodo method defined above.

exports.getTodo = function(id){
  return new Promise(function(resolve, reject){
    todoModel.find({id: id}).exec(function (error, results) {
      if(error){
        reject({error: error});
      }
      else {
        resolve(results);
      }
    });
  });
};

Now the model layer is independent of the request and response objects. The API layer gets access to the promise returned from the above method and it can compose the response based on the state with which the promise ends.

app.get('/api/todos', function(request, response){
  var promise = todosDataOps.getTodo(parseInt(request.params.id));
  promise.then(function(data){
    response.send(data);
  }, function (error) {
    response.status(500).send({error: error});
  });
});

This approach seems cleaner as we have strict separation between the concerns. Now, say I need to modify a property of the todo item if it is found in the DB. So we need to perform the modification after the read operation is done. If we use callback approach to handle this, we will end up with the following snippet:

function findAndUpdate(id, isDone) {
   todoModel.find({id: id})
      .exec(function (findError, records) {
         if(findError){
            throw new Error({text: "record not found", error: findError});
         }
         todoModel.update({id: id},{done: isDone},{multi: false}, function (updateError, updated) {
            if(updateError){
               throw new Error({text:"Error while updating the todo item"},{error: updateError});
            }
            return updated;
         });
      });
};

Though the code was manageable till this level, simply imagining one more async operation after the update operation, freezes our thought process as the code will be hard to read and maintain later. Let’s refactor the above function using promises. Following snippet shows this function after refactoring:

function findAndUpdate(id, isDone) {
  new Promise(function (resolve, reject) {
    todoModel.find({id: id})
      .exec(function (findError, records) {
        if (findError) {
          reject({text: "Record not found", error: findError});
          return;
        }
        resolve(records);
      });
    }).then(function (result) {
      return new Promise(function (resolve, reject) {
        todoModel.update({id: result[0].id},{done: isDone},{multi: false}, function (updateError, updated) {
          if(updateError){
            reject({text:"Error while updating the todo item"},{error: updateError});
          }
          resolve(updated);
        });
    });
  }, function (error) {
    return error;
  });
}

If there is one more dependent async operation, we can perform that in callback of then function on the last promise. This process makes the code look linear and easily readable.

Conclusion

Promises are created to simplify asynchronous operations in JavaScript. As we saw, they help us in making the code look easier and also adhere to Separation-Of-Concerns (SoC). ECmAScript 2015 (or, ES 2015 or, ES6) adds promises to the language and hence makes promises first class citizens for the JavaScript world.

To see how to implement Promises using Q, check this article Using Promises in Node.js Applications using Q

You may also want to read Asynchronous Programming in ES6 Using Generators and Promises

Was this article worth reading? Share it with fellow developers too. Thanks!
Share on LinkedIn
Share on Google+
Further Reading - Articles You May Like!
Author
Rabi Kiran (a.k.a. Ravi Kiran) is a developer working on Microsoft Technologies at Hyderabad. These days, he is spending his time on JavaScript frameworks like AngularJS, latest updates to JavaScript in ES6 and ES7, Web Components, Node.js and also on several Microsoft technologies including ASP.NET 5, SignalR and C#. He is an active blogger, an author at SitePoint and at DotNetCurry. He is rewarded with Microsoft MVP (Visual Studio and Dev Tools) and DZone MVB awards for his contribution to the community


Page copy protected against web site content infringement 	by Copyscape




Feedback - Leave us some adulation, criticism and everything in between!

Categories

JOIN OUR COMMUNITY

POPULAR ARTICLES

FREE .NET MAGAZINES

Free DNC .NET Magazine

Tags

JQUERY COOKBOOK

jQuery CookBook