Monday, September 21, 2015

Promises in AngularJS. Part I. Basics.

There are many blog posts about Promises in general and Promises in AngularJS in particular. Promises are a part of the ES6 (EcmaScript 6) specification, so it is worth to learn them. In this blog post, I will try to summarize the most important info about Promises in a simple and clear way. The necessary code snippets will be provided too. The first part is about basics.

What is a Promise? Promise is a solution to avoid so-called "Pyramid of Doom". If you work with asynchronous operations (HTTP calls, file system operations, etc.), you know the situation where you have callbacks nested inside of another callbacks which are nested again inside of callbacks and so on. An example:
asyncOperationOne('first data', function(firstResult) {
    asyncOperationTwo('second data', function(secondResult) {
        asyncOperationThree('third data', function(thirdResult) {
            asyncOperationFour('fourth data', function(fourthResult) {
                doSomething();
            });
        });
    });
});
Every asynchronous operation invokes a callback function when the operation is completely done. In case of AngularJS and the $http service the "Pyramid of Doom" could look as follows:
var result = [];

$http.get('/api/data/first.json').success(function(data) {
    result.push(data);
    $http.get('/api/data/second.json').success(function(data) {
        result.push(data);
        $http.get('/api/data/third.json').success(function(data) {
            result.push(data);
            $scope.result = result;
        });
    });
});
We didn't show yet the error handling. Now imagine how it looks like with error handling. Terrible and not readable. Promises help to synchronize multiple asynchronous functions and avoid Javascript callback hell. The official concept of Promises is described in the Promises/A+ specification. With promises, an asynchronous call returns a special object called Promise. The calling code can then wait until that promise is fulfilled before executing the next step. To do so, the promise has a method named then, which accepts two functions - success and error function. The success function will be invoked when the promise has been fulfilled. The error function will be invoked when the promise has been rejected. An example of $http service based on promises:
$http.get('/api/data.json').then(
    function(response) {
        ...
    },
    function(error) {
        ...
    }
);
We can say, a promise represents the future result of an asynchronous operation. The real power from promises is the chaining. You can chain promises. The then function returns a new promise, known as derived promise. The return value of some promise's callback is passed as parameter to the callback of the next promise. In this way, you can access previous promise results in a .then() chain. An error is passed too, so that you can define just one error callback at the end of chain and handle all errors there. Here is an example of promise chaining:
$http.get('/api/data.json').then(
    function(response) {
        return doSomething1(response);
    })
    .then(function(response) {
        return doSomething2(response);
    })
    .then(function(response) {
        return doSomething3(response);
    }, function(error) {
        console.log('An error occurred!', error);
    }
);
If you return some value from the derived promise as shown above, the derived promise will be resolved immediately. A derived promise can be deferred as well. That means, the resolution / rejection of the derived promise can be deferred until the previous promise has been resolved / rejected. This is done by returning a promise from the success or error callback. The AngularJS' $http service returns a promise, so that we can rewrite the shown above "Pyramid of Doom" with $http as
$http.get('/api/data/first.json').then(
    function(response) {
        // response here comes from the GET to /api/data/first.json
        result.push(response.data);
        return $http.get('/api/data/second.json');
    })
    .then(function(response) {
        // response here comes from the GET to /api/data/second.json
        result.push(response.data);
        return $http.get('/api/data/third.json');
    })
    .then(function(response) {
        // response here comes from the GET to /api/data/third.json
        result.push(response.data);
        $scope.result = result;
    }, function(error) {
        console.log('An error occurred!', error);
    }
);
What happens now when a HTTP call in the promise chain fails? In this case, success callbacks in every subsequent promise in the chain will be skipped (not invoked). The next error callback in the promise chain however will be invoked. Therefore, the error callback is indispensable. An example:
$http.get('/api/data/wrong_or_failed_url.json').then(
    function(response) {
        // this function is not invoked
        return $http.get('/api/data/second.json');
    })
    .then(function(response) {
        // this function is not invoked
        return $http.get('/api/data/third.json');
    })
    .then(function(response) {
        // this function is not invoked
        $scope.result = response.data;
    }, function(error) {
        // error callback gets invoked
        console.log('An error occurred!', error);
    }
);
Instead of error callback you can also use an equivalent - promise's function catch. The construct promise.catch(errorCallback) is a shorthand for promise.then(null, errorCallback).

Be aware that either the success or the error callback will be invoked, but never both. What to do if you need to ensure a specific function always executes regardless of the result of the promise? You can do this by registering that function on the promise using the finally() method. In this method, you can reset some state or clear some resources. An example:
$http.get('/api/data.json').then(
    function(response) {
        // Do something in success case
    },
    function(error) {
        // Do something in failure case
    }).finally(function() {
        // Do something after either success or failure
    }
);
Where and how to use the promise examples above? A typical pattern in AngularJS is to have calls via $http in a service. Controllers call services and are not aware that $http is used. Example for MyController -> MyService -> $http:
// In MyService
this.fetchData = function() {
    return $http.get('/api/data.json');
};

// In MyController
$scope.fetchData = function() {
    MyService.fetchData().then(function(response) {
        return response.data;
    }, function(error) {
        ...
    }).finally(function() {
        ...
    })
};
We have to mention yet that AngularJS' implementation of promises is a subset of the library Q. Q is the most known implementation of the Promises/A+ specification. That's all for this short post. In the next post, we will meet the AngularJS' deferred object. A deferred object is an object that exposes a promise as well as the associated methods for resolving / rejecting that promise. It is constructed using the $q service - the AngularJS' implementation of promises / deferred objects inspired by Q.

Stay tuned!