In my previous article I discussed the benefits of using dependency injection to make code more testable and modular. In this article I’ll focus on using promises within an AngularJS application. This article assume some prior knowledge of promises (a good intro on promises and AngularJS’ official documentation).
Promises can be used to unnest asynchronous functions and allows one to chain multiple functions together - increasing readability and making individual functions, within the chain, more reusable.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
saveToIndexDB() are converted to returning promises we can refactor the above code to:
1 2 3 4 5
In addition to increasing readability promises can help with error handling, progress updates, and AngularJS templates.
fetchData is called and an exception is raised in
saveToIndexDB(), it will trigger the final error callback.
1 2 3 4 5 6 7
Unfortunately, if an exception is raised in
getDataFromServer() it will not trigger the final error callback. This happens because
saveToIndexDB() are called within the context of
.then(), which uses try-catch, and automatically calls
.reject() on an exception. To bring this behaviour to the first function we can introduce a try-catch block like:
1 2 3 4 5 6 7 8 9 10 11 12
While adding try-catch made
getDataFromServer() less elegant, it makes it more robust and easier to use as the first in a chain of promises.
Using Notify for Progress Updates
A promise can only be resolved, or rejected, once. To provide progress updates, which may happen zero or more times, a promise also includes a notify callback (introduced in AngularJS 1.2+). Notify could be used to provide incremental progress updates on a long running asynchronous task. Below is an example of a long running function,
processLotsOfData(), that uses
notify to provide progress updates.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
Using the notify function, we can make many updates to the $scope’s progress variable before processLotsOfData is resolved (finished), making notify ideal for progress bars.
Unfortunately, using notify in a chain or promises is cumbersome since calls to notify do not bubble up. Every function in the chain would have to manually bubble up notifications, making code a little more difficult to read.
AngularJS templates understand promises and delays their rendering until they’re resolved, or rejected. AngularJS templates no longer resolve promises - they must be resolved in the controller before they’re assigned to the scope. For instance let’s say our template looks like:
We could do the following in our controller:
1 2 3 4 5 6 7 8 9
The view renders normally, and when the promise is resolved AngularJS automatically updates the view to include the value resolved in getBio.
Limitations of Promises in AngularJS
When a promise is resolved asynchronously, “in a future turn of the event loop”, the .resolve() function must be wrapped in a promise. In the contrived example below, a user would click a button triggering
goodbye(), which should update the
$scope’s greeting attribute.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
Unfortunately, it doesn’t work as expected, since the asynchronous event works outside of AngularJS’ event loop. The fix for this (besides using AngularJS’ setTimemout function), is to wrap the deferred’s resolve in
$scope.$apply to trigger the digest cycle and update the
1 2 3 4 5
Jim Hoskins goes into more detail on using
Using promises is an important part of writing an AngularJS app idiomatically and should help make your code more readable. Understanding their shortcomings, and their strengths make them much easier to work with.