Q is Qool

27 בפברואר 2013

no comments

So, you are writing more and more Java Script code. You aim for more responsive and more sophisticated web application and therefore you are moving code from server to the client side.

Eventually, you will need to model your client side code.

Probably you will end up with something that resembles any other client side application layering.

I usually start with a DAL later that is responsible for fetching data from the server (or from local storage) than wrap it inside some BL objects that compose a more convenient API for building the UI. This is the classical 3 layers modeling: DAL –> BL –> UI

One of the main challenges when coding under this modeling is around managing asynchronous invocation cross different layers

Let me present the challenge using a simple example

At the DAL layer we need to fetch data from the server. This is done using AJAX request.

   1:  DAL.prototype.getUsers = function () {
   2:      $.ajax({
   3:          url: "/api/user",
   4:          type: "GET",
   5:          dataType: "json",
   6:          success: function (users) {
   7:          },
   8:          error: function () {
   9:          }
  10:      });
  11:  }

The problem is that the DAL does not know how to handle the data nor how to handle failures. The simple solution is to change the getUsers signature and receive success and error callbacks.

   1:  DAL.getUsers = function (success, error) {
   2:      $.ajax({
   3:          url: "/api/user",
   4:          type: "GET",
   5:          dataType: "json",
   6:          success: success,
   7:          error: error,
   8:      });
   9:  }

This looks reasonable. However, in many cases you may want to change the data before returning it or wrap the AJAX error inside a more general application error. For example, the BL layer may want to do something like below

   1:  BL.getUsers = function (success, error) {
   2:      DAL.getUsers(
   3:          function (dtos) {
   4:              var users = $.map(dataFromServer, function (dto) {
   5:                  return new User(dto);
   6:              });
   7:   
   8:              success(users);
   9:          },
  10:          error);
  11:  }

As you can the code becomes more complex and hard to read.

Now, let’s see how we can achieve the same using Q. First, you can read Q’s full documentation here. It is quite good.

The idea behind Q is to use Promise and Deferred objects. Actually, I already wrote a bit about them a few months ago. Q makes them even more easier to be used. The big change is that instead of getting success and error callbacks we return a deferred object which later can be used by the caller  to monitor the state of the deferred operation. The nice thing is that in many cases a middle application layer can just invoke an asynchronous operation inside lower level layer and just return the result to the caller. No special coding and delegation of success and error callbacks is required. In some cases you may want to combine several deferred operations into one. Q will help you with that.

Let’s get back to our example. The DAL can be written as below

   1:  DAL.getUsers = function () {
   2:      return Q.when($.ajax({
   3:          url: "/api/user",
   4:          type: "GET",
   5:          dataType: "json",
   6:      }));
   7:  }

You see, Q knows how to wrap jQuery.ajax return value and make it a Q compliant deferred object. Next, is the BL:

   1:  BL.getUsers = function (success, error) {
   2:      return DAL.getUsers()
   3:          .then(function (dtos) {
   4:              return $.map(dtos, function (dto) {
   5:                  return new User(dto);
   6:              });
   7:          });
   8:  }

Now it is even more impressing since we did not have to deal with the error callback. In case DAL.getUsers fails the caller of BL.getUsers will just know about it even though we did not handle is explicitly inside BL.getUsers.

Last is the UI. Assuming the UI is the top most layer you probably want to handle both success and error scenarios

   1:  UI.refreshUserList = function () {
   2:      BL.getUsers()
   3:          .then(function (users) {
   4:              bindUI(users);
   5:          })
   6:          .fail(function (err) {
   7:              setErrorMessage(err.message);
   8:          });
   9:  }

In general, you may think about then/fail as a try/catch handlers. For example, if you want to wrap AJAX error inside the DAL by returning a standard application error you may do it as below:

   1:  DAL.getUsers = function () {
   2:      return Q.when($.ajax({
   3:          url: "/api/user",
   4:          type: "GET",
   5:          dataType: "json",
   6:      }).
   7:      fail(function (err) {
   8:          throw new ServerError(err.status, err.statusText);
   9:      }));
  10:  }

I am using Q for almost 1 year and I really love it. What do you think?

Add comment
facebook linkedin twitter email

Leave a Reply

Your email address will not be published.

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

*