Promise chaining – for a cleaner code in AngularJS

There are cases when there is the need to perform a set of asynchronous operations, one after another, to load or process something. In AngularJS, If the result of each operation is wrapped by a promise, taking advantage of promise chaining leads to code easier to be read and maintained.

I do remember of my beginning on using promises. I had a glance at documentation and some examples and got acquainted to its basics – the successful and error callbacks and so on.

Then I had to write something to load a complex resource, by walking over a sort of tree structure, in order to feed in some form. Given all that I knew so far, I wrote what seemed intuitive to me:

  • Load the first resource and have its result as a promise
  • On the success callback for that promise, process the first resource and load the second resource, to have a second promise
  • And so on….

The code looked like this:

resourceService.loadResourceOne().then(
    function (resourceOne) {
        var inputForResourceTwo = computeInputForResource(resourceOne);

        resourceService.loadResourceTwo(inputForResourceTwo).then(
            function (resourceTwo) {
                var inputForResourceThree = computeInputForResource(resourceTwo);

                resourceService.loadResourceThree(inputForResourceThree).then(
                    function (resourceThree) {
                        // Successful end

                        console.log('Resource found: ' + resourceThree);
                    },
                    function (errorLoadingResourceThree) {
                        handleError(errorLoadingResourceThree);
                    }
                )
            },
            function (errorLoadingResourceTwo) {
                handleError(errorLoadingResourceTwo);
            }
        )
    },
    function (errorLoadingResourceOne) {
        handleError(errorLoadingResourceOne);
    }
);

I started to get annoyed when I had to revisit my code, to load even more resources. The increasing nesting of blocks, and the need to scroll the screen up and down to see the success and error handling for an intermediate promise really called my attention. I realized I should look for some refactoring.

By AngularJS design, a promise callback can return another callback, so the load of resources could be done in adjacent blocks of code. That is the so called promise chaining.

The load of three resources, now as a chain:

resourceService.loadResourceOne().then(
    function (resourceOne) {
        var inputForResourceTwo = computeInputForResource(resourceOne);

        // returns promise to the next step in the chain
        return resourceService.loadResourceTwo(inputForResourceTwo);
    }
).then(
    function (resourceTwo) {
        var inputForResourceThree = computeInputForResource(resourceTwo);

        return resourceService.loadResourceThree(inputForResourceThree);
    }
).then(
    function (resourceThree) {
        // Successful end

        console.log('Resource found: ' + resourceThree);
    }
).catch(
    function (error) {
        handleError(error);
    }
);

The handling of a promise is well contained in a block of code, returning the promise to be handled by the next callback in the chain. Further needs to add loading of more resources no longer require nesting of code.

Error handling remained in a single block of code as well, as the last step in the chain, to capture a failure for any promise in the chain. By design, whenever there is a promise rejection, AngularJS looks for the first error callback in the chain – in the example, it is the catch block, which is basically a syntactic sugar for:

.then (null, function (error) {
        handleError(error);
    }
)

Code looks better. After all, a bigger plus in both readability and maintainability – a potential for fewer bugs.

 In such a mood for refactoring, a next step could be done:

function computeInputAndLoadResourceTwo(resourceOne) {
    var inputForResourceTwo = computeInputForResource(resourceOne);

    // returns promise to the next step in the chain
    return resourceService.loadResourceTwo(inputForResourceTwo);
}

function computeInputAndLoadResourceThree(resourceTwo) {
    var inputForResourceThree = computeInputForResource(resourceTwo);

    return resourceService.loadResourceThree(inputForResourceThree);
}

function printResourceThree(resourceThree) {
    // Successful end

    console.log('Resource found: ' + resourceThree);
}

resourceService.loadResourceOne()
    .then(computeInputAndLoadResourceTwo)
    .then(computeInputAndLoadResourceThree)
    .then(printResourceThree)
    .catch(handleError);

Each callback in is now defined as a named function, turning the chain definition much slimmer and making the code self documenting.

It is not the case here, but such an approach also enables reuse of a same function in more than one step in the chain.

References

AngularJS documentation: $q service

Advertisements

AngularJS promises as cache objects

Caching is a handy approach for keeping values that take a good amount of time to be computed.

Given the design in AngularJS, a promise can serve the purpose of being the cache object, so that it’s able to address as many invocation to return the cached value, saving some lines of code.

To exercise that concept, an application to show lucky numbers below

1st value Show me
2nd value Show me
3rd value Show me

Each lucky number is revealed on the user click to the corresponding Show me button.

Suppose all lucky numbers are provided by a third party service. All numbers are computed at once, but that operation takes some time to complete. To improve user experience, all lucky numbers are preloaded and cached, so that the user click on any button can have instant effect on the screen

Some HTML code:

<body ng-app="LuckyNumbersApp">

<table ng-controller="LuckyNumbersValuesCachingCtrl">
    <thead>
        <tr>
            <th colspan="3">Lucky numbers - caching values</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td>1st value</td>
            <td>{{luckyNumbers[0]}}</td>
            <td>
                <button ng-click="getLuckyNumber(0)">Show me</button>
            </td>
        </tr>
        <tr>
            <td>2nd value</td>
            <td>{{luckyNumbers[1]}}</td>
            <td>
                <button ng-click="getLuckyNumber(1)">Show me</button>
            </td>
        </tr>
        <tr>
            <td>3rd value</td>
            <td>{{luckyNumbers[2]}}</td>
            <td>
                <button ng-click="getLuckyNumber(2)">Show me</button>
            </td>
        </tr>
    </tbody>
</table>

And the AngularJS counterpart:

var appModule = angular.module('LuckyNumbersApp', []);

appModule.controller('LuckyNumbersValuesCachingCtrl', function ($scope, $q, luckyNumbersService) {
    //  The values retrieved from outside the application are cached
    var cachedValues;
    $scope.luckyNumbers = [];

    function getAllLuckyNumbers() {
        var newPromise = $q.defer();

        if (cachedValues) {
            newPromise.resolve(cachedValues);
        }
        else {
            luckyNumbersService.computeLuckyNumbers().then(function (data) {
                cachedValues = data;
                newPromise.resolve(data);
            });
        }

        return newPromise.promise;
    }

    $scope.getLuckyNumber = function (num) {
        console.log('[Values caching] Loading lucky number ' + (num + 1));
        getAllLuckyNumbers().then(function (result) {
            console.log('[Values caching] Lucky number ' + (num + 1) + ' : ' + result[num]);
            $scope.luckyNumbers[num] = result[num];
        });
    };

    getAllLuckyNumbers();
});

The retrieval the numbers from the service (luckyNumbersService) is implemented inside function getAllLuckyNumbers, which extracts the array of numbers from a promise returned by service and wraps the array in a new promise, that is returned to the function caller. The array is kept in cachedValues variable, to avoid more invocations to the service in further function calls. Each number can then consumed, to populate the screen: user clicks on button, getLuckyNumber function is called, then getAllLuckyNumber is called (if the preloading of numbers has already completed, all calls here would here will already be handled by the cache).

So far, so good. It all works.

A fact: the way promises were designed in AngularJS allow the then method to be called many times. Documentation already gives in a hint (On the initial description of then method: This method returns a new promise…) and I confirmed that by looking at the $q service source code. In order not to extend this post too much, I won’t go deep.

Having that said, rather than caching the array of numbers, extracted from the promise returned by luckyNumbersService, the promise itself can be cached, so the code can be polished a bit.

A second version of the controller:

appModule.controller('LuckyNumbersPromiseCachingCtrl', function ($scope, $q, luckyNumbersService) {
    // Simpler approach: the promise itself returned by the service is cached
    var cachedPromise;
    $scope.luckyNumbers = [];

    function getAllLuckyNumbers() {
        if (!cachedPromise) {
            cachedPromise = luckyNumbersService.computeLuckyNumbers();
        }

        return cachedPromise;
    }

    $scope.getLuckyNumber = function (num) {
        console.log('[Promise caching] Loading lucky number ' + (num + 1));
        getAllLuckyNumbers().then(function (result) {
            console.log('[Promise caching] Lucky number ' + (num + 1) + ' : ' + result[num]);
            $scope.luckyNumbers[num] = result[num];
        });
    };

    getAllLuckyNumbers();
});

Now, getAllLuckyNumbers caches and forwards the promise returned by the service. The rest of the code stays the same, where each user click to a button makes the corresponding numbers provided by the service to populate the screen. And for multiple button clicks, the promise instance cached by the controller will be able to handle each then call made inside getLuckyNumber, invoking the callback with the array of numbers computed by the service.

Even if getAllLuckyNumbers had to create a new promise, to wrap result of perhaps some transformation, validation, filtering or any other operation over the numbers provided by the service, that promise could be kept and reused later.

For a running version of this example, access this link

References

AngularJS documentation: $q service