Ben Nadel
On User Experience (UX) Design, JavaScript, ColdFusion, Node.js, Life, and Love.
Ben Nadel at dev.Objective() 2015 (Bloomington, MN) with: Carol Hamilton
Ben Nadel at dev.Objective() 2015 (Bloomington, MN) with: Carol Hamilton@k_Roll242 )

Simulating Network Latency In AngularJS With $http Interceptors And $timeout

By Ben Nadel on

Over the weekend, when I was exploring routing, caching, and nested views with ngRoute in AngularJS 1.x, I wanted to slow down the HTTP speed so that the user could experience Views in a "pending state." But, because I was working with my local system, HTTP requests were completing very quickly, in about 15ms. To get around this I used an HTTP interceptor to simulate network latency in a controlled fashion.


 
 
 

 
 
 
 
 

Run this demo in my JavaScript Demos project on GitHub.

In the past, we've seen that AngularJS implements the $http request pipeline using a promise chain with hooks into both the pre-http and post-http portions of the request. This provides a perfect opportunity to inject a "dely promise" into the overall promise chain:


 
 
 

 
 Simulating network latency with $http interceptors and $timeout in AngularJS. 
 
 
 

For this demo, I'm hooking into the "response" portion of the workflow, as opposed to the "request" portion. For some reason, that just feels a bit more natural. This works for both successful responses as well as failed responses, assuming we use both the "response" and "responseError" hooks:

  • <!doctype html>
  • <html ng-app="Demo">
  • <head>
  • <meta charset="utf-8" />
  •  
  • <title>
  • Simulating Network Latency In AngularJS With HTTP Interceptors
  • </title>
  •  
  • <link rel="stylesheet" type="text/css" href="./demo.css"></link>
  • </head>
  • <body ng-controller="AppController">
  •  
  • <h1>
  • Simulating Network Latency In AngularJS With HTTP Interceptors
  • </h1>
  •  
  • <p ng-switch="isLoading">
  • <strong>State</strong>:
  • <span ng-switch-when="true">Loading http data.</span>
  • <span ng-switch-when="false">Done after {{ delta }} milliseconds</span>
  • </p>
  •  
  • <p>
  • <a ng-click="makeRequest()">Make an HTTP request</a>
  • </p>
  •  
  •  
  • <!-- Load scripts. -->
  • <script type="text/javascript" src="../../vendor/angularjs/angular-1.3.13.min.js"></script>
  • <script type="text/javascript">
  •  
  • // Create an application module for our demo.
  • var app = angular.module( "Demo", [] );
  •  
  •  
  • // -------------------------------------------------- //
  • // -------------------------------------------------- //
  •  
  •  
  • // I control the root of the application.
  • app.controller(
  • "AppController",
  • function( $scope, $http ) {
  •  
  • // I determine if there is pending HTTP activity.
  • $scope.isLoading = false;
  •  
  • // I keep track of how long it takes for the outgoing HTTP request to
  • // return (either in success or in error).
  • $scope.delta = 0;
  •  
  • // I am used to move between "200 OK" and "404 Not Found" requests.
  • var requestCount = 0;
  •  
  •  
  • // ---
  • // PUBLIC METHODS.
  • // ---
  •  
  •  
  • // I alternate between making successful and non-successful requests.
  • $scope.makeRequest = function() {
  •  
  • $scope.isLoading = true;
  •  
  • var startedAt = new Date();
  •  
  • // NOTE: We are using the requestCount to alternate between requests
  • // that return successfully and requests that return in error.
  • $http({
  • method: "get",
  • url: ( ( ++requestCount % 2 ) ? "./data.json" : "./404.json" )
  • })
  • // NOTE: We foregoing "resolve" and "reject" because we only care
  • // about when the HTTP response comes back - we don't care if it
  • // came back in error or in success.
  • .finally(
  • function handleDone( response ) {
  •  
  • $scope.isLoading = false;
  •  
  • $scope.delta = ( ( new Date() ).getTime() - startedAt.getTime() );
  •  
  • }
  • );
  •  
  • };
  •  
  • }
  • );
  •  
  •  
  • // -------------------------------------------------- //
  • // -------------------------------------------------- //
  •  
  •  
  • // Since we cannot change the actual speed of the HTTP request over the wire,
  • // we'll alter the perceived response time be hooking into the HTTP interceptor
  • // promise-chain.
  • app.config(
  • function simulateNetworkLatency( $httpProvider ) {
  •  
  • $httpProvider.interceptors.push( httpDelay );
  •  
  •  
  • // I add a delay to both successful and failed responses.
  • function httpDelay( $timeout, $q ) {
  •  
  • var delayInMilliseconds = 850;
  •  
  • // Return our interceptor configuration.
  • return({
  • response: response,
  • responseError: responseError
  • });
  •  
  •  
  • // ---
  • // PUBLIC METHODS.
  • // ---
  •  
  •  
  • // I intercept successful responses.
  • function response( response ) {
  •  
  • var deferred = $q.defer();
  •  
  • $timeout(
  • function() {
  •  
  • deferred.resolve( response );
  •  
  • },
  • delayInMilliseconds,
  • // There's no need to trigger a $digest - the view-model has
  • // not been changed.
  • false
  • );
  •  
  • return( deferred.promise );
  •  
  • }
  •  
  •  
  • // I intercept error responses.
  • function responseError( response ) {
  •  
  • var deferred = $q.defer();
  •  
  • $timeout(
  • function() {
  •  
  • deferred.reject( response );
  •  
  • },
  • delayInMilliseconds,
  • // There's no need to trigger a $digest - the view-model has
  • // not been changed.
  • false
  • );
  •  
  • return( deferred.promise );
  •  
  • }
  •  
  • }
  •  
  • }
  • );
  •  
  • </script>
  •  
  • </body>
  • </html>

I don't have too much more to say about this. I just thought it was a fun use of the underlying $http implementation. And, it's yet another example of how promises make our lives better.




Reader Comments

@Gleb,

I'm having a little trouble following the code. I **think** you are redefining the "load()" method with a Function() constructor in order to change its lexical binding to the $http object you are defining inside the wedge? Is that accurate? If so, it's a very clever idea! I don't think I've seen the Function constructor used to redefine context like that.

Reply to this Comment

Hi, very good article, i have a question: what if i need to delay the request, for example: show a toast before send the request itself. Thanks!

Reply to this Comment

@Luis,

If you need the delay as part of your application logic, I would probably do that outside of an $http interceptor. Since it is a specific context that triggers the toast item, you don't want to try and incorporate that into into a generic $http interceptor.

I don't know the rules of your application; but, I assume there's some sort of Controller or Directive that manages the toast item. Perhaps you could have the toast $rootScope.$emit( "toastClosed" ) an event when it is closed. Then, you could have some other controller listen for that event and initiate the HTTP request at that time:

$rootScope.$on( "toastClosed", function() {
. . . . make the HTTP request
} );

You could also do this with promises and a host of other ways. It also depends on whether or not you want to pass data around, etc. But, definitely, I wouldn't try to incorporate that into the $http logic itself.

Reply to this Comment

Post A Comment

You — Get Out Of My Dreams, Get Into My Comments
Live in the Now
Oops!
Comment Etiquette: Please do not post spam. Please keep the comments on-topic. Please do not post unrelated questions or large chunks of code. And, above all, please be nice to each other - we're trying to have a good conversation here.