AngularJs, Javascript

Using promise chaining and $q.when to create a complete and clean flow out of distributed unrelated API’s


Recently i had to develop a feature which utilized no less then 4 different API’s in a single flow. While this may sound complicated, the real challenge was integrating all of them into a clean angular flow, without relying on scope.$apply, safeApply, $digest, unnecessary watchers and all kind of white or black magic. Some of the API calls were synchronous and some were asynchronous, but all of them had to be executed in a chain, passing data to each other.

So – how should we take 4 completely unrelated libraries, exposing different API’s, and not only properly chain them, but also integrate them properly into our application flow?

First of all, $q.when

As angular documentation about $q explains $q.when:
Wraps an object that might be a value or a (3rd party) then-able promise into a $q promise. This is useful when you are dealing with an object that might or might not be a promise, or if the promise comes from a source that can’t be trusted.

So what exactly does it means? If we’ll (extremely) simplify the explanation, it basically means that whichever value you’ll receive from a given 3rd party method, whether it’s a promise or a regular value, you will be able to handle it as you would have handled a regular $q promise resolution. On top of that, what the documentation does not mention, is that after the state is changed (wether it was resolved or rejected) – digest cycle is being triggered, eliminating the need for $scope.$apply.


So here you go, one line of code, one (and a half) line of explanation, and you have all your 3rd party API’s standing in line (literally) ready to be integrated into your application flow.

Now let’s wrap everything to a story-like flow execution

Since we used $q.when to wrap all our vendor methods, they are now chainable and can be executed in a very clear and self explanatory code.

Let’s see some of this code we are talking about:

HTML

<body ng-controller="AppController">

   <h1>Build a car</h1>

    {{car}}
  </body>



JavaScript

app.controller("AppController", function($scope, $q) {

  $scope.car = "Building my car...";

  // execute the car build flow.
  getCarShield()
    .then(getWheels)
    .then(getCarColor)
    .then(displayTheCar)
    .catch(function(err) {
      
      // catch an error if exists
      if (err) $scope.car = 'Could not finish building the car :<';
    });
    
    
  // wrap all of our 3rd party methods in $q.when,
  // this will allow then to be chainable and take normalize the return value,
  // wether its a promise or a value
  var getCarShield = function() {
      return $q.when(carShieldFromMechanic()).then(function(carShield) {
        return carShield;
      });
    },

    getWheels = function(carShield) {
      return $q.when(wheelsFromVendor(carShield)).then(function(wheelsType) {
        return wheelsType;
      });
    },

    getCarColor = function(shieldAndWheels) {
      return $q.when(paintTheCar(shieldAndWheels)).then(function(finalizedCar) {
        return finalizedCar;
      });
    },

    displayTheCar = function(car) {
      $scope.car = car;
    };

});

// get car shield from the mechanic
function carShieldFromMechanic() {
  return '2008 GT500';
}

// get wheels from wheels vendor
function wheelsFromVendor(carShield) {
  return carShield += " with 160/65r315 wheels";
}

// painting the car may take some time, so use jquery promise and notify me when it's done
function paintTheCar(shieldAndWheels) {

  var deferred = $.Deferred();

  setTimeout(
    function resolveOperator() {
      deferred.resolve(shieldAndWheels + ", Colored in red");
    }, 2000);

  return deferred;

}

This code is self explanatory but let’s review it quickly:

At the beginning we define the method chain, each method in the chain is actually a vendor method wrapped in $q.when.
As you can see, carShieldFromMechanic and wheelsFromVendor returning simple values while wheelsFromVendor returns jQuery promise, and all of them wrapped by $q.when.
Each chained method receives a data from the previous method in the chain, and finally we are .catching to see if any of the methods didn’t retrieve a value or an error occurred in one of them.

You can play around and try to change

deferred.resolve(shieldAndWheels + ", Colored in red");

to

deferred.reject("error");

or throw an error inside on of the other methods to see how .catch will handle this error.

And that’s it, now you have a complete working flow which combining multiple 3rd party libraries and perfectly integrated into your Angular application.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s