Delay an angular.js $http service
Asked Answered
H

8

35

I have some angular factories for making ajax calls towards legacy ASP.NET .asmx web services like so:

module.factory('productService', ["$http",
function ($http) {
    return {
        getSpecialProducts: function (data) {
            return $http.post('/ajax/Products.asmx/GetSpecialProducs', data);
        }
    }
} ]);

I'm testing on a local network so response times are "too" good. Is there a smart way of delaying the $http a couple of seconds from making the call to simulate a bad connection?

Or do I need to wrap all calls to the factory methods in a $timeout ?

$timeout(function() {
  productService.getSpecialProducs(data).success(success).error(error);
}, $scope.MOCK_ajaxDelay);
Heterochromatin answered 14/8, 2013 at 17:21 Comment(0)
A
53

Interesting question!

As you mentioned yourself, $timeout is the most logical choice for a delayed call. Instead of having $timeout calls everywhere, you could push a response interceptor that wraps the $http promise in a $timeout promise, as conceptually outlined in the documentation of $http, and register it in one of your configuration blocks. This means all $http calls are affected by the $timeout delay. Something along the lines of:

$httpProvider.interceptors.push(function($timeout) {
    return {
        "response": function (response) {
            return $timeout(function() {
                return response;
            }, 2500);
        }
    };
});

As a bonus to your "to simulate a bad connection?", you could reject or do absolutely nothing randomly, too. Heh heh heh.

Acima answered 14/8, 2013 at 17:45 Comment(7)
Beats my answer! - Seems I need to do some reading on interceptorsHowdah
This doesn't work correctly if the server returns an error (i.e. anything non 2XX). You need to add the error function and reject it in that case as well.Ditheism
@Ditheism of course you would handle the fail of the response, but that is not part of the question ;-).Hairtail
Where is the best place to put that in, the run or the config phase? Okay I answer myself: you can't put $q and $timeout inside the config-phase. Mhm realy? I give up...Hairtail
The syntax has changed in the newer AngularJS versions. The new syntax is: https://mcmap.net/q/424668/-delay-an-angular-js-http-servicePolarization
I updated the syntax! Feel free to edit answers when the syntax is outdated.Albanese
@stevuu I removed the unnecessary usage of $q. $timeout and other similar services return a promise that is resolved with the return value of the function, so it's not necessary to create a promise manually.Slather
T
17

The new chrome device emulator has a network throttling function:

enter image description here

To get there: In Google Chrome, press F12 to open the Developer Tools. Then, on the top left corner, click the "Toggle device mode" icon (left to the "Elements" menu).

Tonicity answered 20/10, 2014 at 12:6 Comment(2)
In my Chrome I found the dropdown for throttling on the Network tab. No need to activate device emulator.Thacker
Problem is this doesn't work if you are mocking data and not actually making the network callsObfuscate
G
4

Developing more on the answer of @stevuu

responseInterceptors seems to be depreceted (as of 1.2.20) I have modified the code to work on the interceptors mechanism:

$httpProvider.interceptors.push(function($q, $timeout) {
    return {
        'response': function(response) {
            var defer = $q.defer();
            $timeout(function() {
                        defer.resolve(response);
                }, 2300);
            return defer.promise;
        }
    };
});
Gaivn answered 5/9, 2014 at 14:38 Comment(0)
H
3

You could use the $q service for defer().promise pattern:

function someFunction(MOCK_ajaxDelay) {
   var deferred = $q.defer();
   $http.post('/ajax/Products.asmx/GetSpecialProducs', data).success(function(response) {
      $timeout(function() {deferred.resolve({ success: true, response: response })}, MOCK_ajaxDelay);  
   }).error(function() {
      $timeout(function() {deferred.resolve({ success: true, response: response } }, MOCK_ajaxDelay);  
   });
   return deferred.promise;
}

someService.someFunction(500).then(function(data) {
    if (data.success) {
      $scope.items = data.response.d;
    }
});

But if you are really mock testing, the better solution is to look into ngMock: http://docs.angularjs.org/api/ngMock.$httpBackend

Hyperkeratosis answered 14/8, 2013 at 17:48 Comment(0)
P
2

While @stevuu's answer is correct, the syntax has changed in the newer AngularJS versions since then. The updated syntax is:

$httpProvider.interceptors.push(["$q", "$timeout", function ($q, $timeout) {
  function slower(response) {
    var deferred = $q.defer();
    $timeout(function() {
        deferred.resolve(response);
    }, 2000);
    return deferred.promise;
  }
  return {
    'response': slower
  };
}]);
Polarization answered 7/8, 2015 at 8:49 Comment(0)
H
0

You can achieve this using the promise api combined with a $timeout. The $http.post function returns a promise from which you can call .success and .error (these are http specific methods). This promise is resolved when the http request is complete. If you build your own promise then you can tell it to delay 2 seconds and then resolve when the http request is complete:

module.factory('productService', function ($http, $q, $timeout) {

    return {
        getSpecialProducts: function (data) {
            var defer = $q.defer();
            $http.post('/ajax/Products.asmx/GetSpecialProducs', data).success(
              function(data) {
                // successful http request, resolve after two seconds
                $timeout(function() {
                    defer.resolve(data);
                }, 2000)
            }).error(function() {
                defer.reject("Http Error");
            })
            return defer.promise;
        }
    }

});

But note - you will have to use promise.then(successCallback, errorCallback) functionality - that is, you'll lose the ability to access http headers, status & config from your controllers/directives unless you explicitly supply them to the object passed to defer.resolve({})

Links:

Howdah answered 14/8, 2013 at 18:7 Comment(0)
S
0

In response to the testing aspect of your question, Fiddler has a really useful function that helps when you need to simulate delays:

  1. Click on the AutoResponders tab in Fiddler.
  2. Add a rule with a regex that matches the URL of the request you want to delay.
  3. Set the "respond with" to "*delay:1000" where the number is the delay in milliseconds.

The AutoResponder functionality in Fiddler is extremely useful for testing JS that involves a lot of http requests. You can set it to respond with particular http error codes, block responses, etc.

Salaam answered 27/11, 2013 at 17:51 Comment(1)
I've assumed that you are using Windows because of the use of a asp.net web service.Salaam
S
0

If you are using a service that returns a promise, then inside you should put a return before the $timeout as well because that returns just another promise.

return dataService.loadSavedItem({
    save_id: item.save_id,
    context: item.context
}).then(function (data) {
    // timeout returns a promise
    return $timeout(function () {
        return data;
    },2000);
});

Hope it helps someone!

Sylvestersylvia answered 8/7, 2015 at 18:0 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.