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