When writing an AngularJS application that communicates with back-end services via XHR calls, you need to cater for the latency that will occur when users are accessing the application for real over slow connections. This usually means showing things such as loading indicators when long running requests are in progress. However, when in development, the back-end servers may be located on localhost, or another machine on the local network, leading to no appreciable delay when requests are made.
To help develop and test the application, it can be useful to simulate higher latency, of say a few seconds. There are various approaches you could take to do this: Using networking tools like Fiddler to simulate slow speeds, or modifying the back-end code to delay before it sends a response. However, a quick and easy way to do it purely within the front end is to use a feature of Angular’s $http service called Response Interceptors. These let you insert handlers into the $http service provider that synchronously or asynchronously process the results of requests before they are handed back to the application.
In this case, I don’t want to change the response in any way, I just want to delay it. The interception mechanism is based on promises, just like those returned by the $http service’s methods. The handler function we register with $httpProvider
takes a single promise parameter, and must also return a promise. Working with this system is one of the trickier uses of promises within Angular, and may take a little while to grasp, but it’s very effective.
The handler needs to be registered with the $httpProvider
, not with the $http
service itself. That means it must be done in a module config method. The implementation might look something like this:
[code language=”javascript”]
angular.module(‘app’, [], function($httpProvider) {
var handlerFactory = function($q, $timeout) {
return function(promise) {
return promise.then(function(response) {
return $timeout(function() {
return response;
}, 3000);
}, function(response) {
return $q.reject(response);
});
};
}
$httpProvider.responseInterceptors.push(handlerFactory);
});
[/code]
You can see a working example in this Plunkr. It also demonstrates how to avoid the magic number and make the delay configurable via an httpDelayConfig
service.
How it works
Even with the simplification of async that promises give us, there’s a bit to unpick here. The handlerFactory object is a factory function that just returns the actual handler function. A factory is necessary so the handler can have dependencies on services like $q that it isn’t possible to access within a module config function.
The handler function takes a single parameter called promise
. This is the same as the promise object usually returned by the $http methods. If the code simply returned it without doing anything, then requests would work exactly the same as they normally do. However, I want to introduce a delay, so I register two callbacks via the promise’s then()
method, and return the result of that method call. Because the result of a then()
call is another promise, it will fulfill the function’s return requirement, and create a chain where the code in my callbacks is executed and their result is passed back to the application as the resolved value of the promise.
The error callback, which is the second function passed to the then()
method is the simplest of the two. It simply returns a rejection message that wraps the response it receives. I don’t perform any delay, because I figure that if I’m getting errors coming back from the XHR, I’ll want to know about it right away, and debug without delays.
The success callback uses the $timeout
service to delay by 3000 milliseconds. Here you can see the beauty of Angular’s adoption of promises throughout its API. The $timeout
service call also returns a promise, which is resolved with the result of its callback function parameter when the timeout occurs. This means I can return have the $timeout
callback return the response, and then just return the result of the $timeout
call as the result of the success callback.
Remember I said that the then()
method returns a promise? If a promise is resolved with a promise (as will happen when the success callback returns the $timeout
promise) then it defers further until that promise is resolved. This means the promise I pass back to the application won’t be resolved until the $timeout promise resolves.
All of this can be a bit mind-bending, and you don’t need to actually understand it to use the above code. It can be dropped into your application and will delay your XHR calls without affecting them in any other way. Anything to do with asynchronicity and parallelism is generally a bit of stretch for most people’s brains, mine included. The best way to get to grips with promises seems to be to start off using them for simple things, then when you’re comfortable, move on to slightly more complicated arrangements like this one.
Leave a Reply