Limiting or delaying http requests from a AngularJS service - javascript

I have created a service that provides my AngularJS app with data, it's rather simple the service just contains lots a method that make a $http call. The problem is that this call is made everytime when a HTML item in my view (a div of product details) is visible in the view port (I'm lazy loading), thus it is possible for the user to scroll straight to the bottom of the page, etc and create a number of requests (usually around 50 - 60) that will block/slow down other requests or the app on the whole. Thus I need a way to limit, restrict, queue or delay the requests - queuing would be best, but for the time being I was just going to restrict / manage the amount of requests in the service.
This is how I call my service in my controllers / directives:
productAvailabilityService.getAvailability(scope.productId).then(function (result) {
// do stuff with the result...
});
and this is the service
.factory('productAvailabilityService', function ($http) {
var prodAv = {};
prodAv.getAvailability = function (productId) {
return $http({
method: 'GET',
url: '/api/product/getAvailability',
params: {
'productId': productId
}
}).then(
function (response) {
return response;
}, function () {
return 0;
});
}
};
return prodAv;
});
Now I want to add the limiting functionality... like so:
.factory('productAvailabilityService', function ($http) {
var prodAv = {};
prodAv.requests = 0;
prodAv.getAvailability = function (productId) {
if (prodAv.requests > 6) {
return function () {
return 'Too many requests';
};
} else {
prodAv.requests++;
return $http({
method: 'GET',
url: '/api/product/:productId/getAvailability',
params: {
'productId': productId
}
}).then(
function (response) {
prodAv.requests--;
return response;
}, function () {
prodAv.requests--;
return 0;
});
}
};
return prodAv;
});
This gives me an error when the number of requests is greater than 6, .getAvailability(...).then is not a function which I don't seem to be able to fix, can anyone see what I am doing wrong... also this method does seem a little wrong, is there a better way to manage the amount of times I can call a service / run a $http request? Thanks in advance!

You have to use a promise for productAvailabilityService. You return a function instead of a promise, so the "then()" throw an error in the first line code..
here is the fraction of code with the promise:
.factory('productAvailabilityService', function ($http,$q) {
var prodAv = {};
prodAv.requests = 0;
prodAv.getAvailability = function (productId) {
if (prodAv.requests > 6) {
var deferred = $q.defer();
deferred.reject("no more than 6 calls");
return deferred.promise;
}else{ //etc.
you could maybe also use the resolve function if you manage a delayed call,

Add this DelayHttp
app.factory('DelayHttp', ($http, $timeout) => {
let counter = 0,
delay = 100;
return (conf) => {
counter += 1;
return $timeout(() => {
counter -= 1;
return $http(conf);
}, counter * delay);
};
});
Usage:
return DelayHttp({
url: url,
method: 'GET',
params: params
});
Which queues and executes the requests by 100ms apart or max 10 requests/second. You can adjust the delay to match your desired throughput.

i would set a time gap and wait until the user arrived its desired content before issuing the requests something like
var timer;
function getData(){
$timeout.cancel(timer);
timer=$timeout(function(){
$http.get(url)
},200);
}

Related

$http request to return all records without pagination in a single request

There are more than 4000 records. API returns max 1000 records and has pagination. I call for this function (loop), and use "skip" to obtain records at intervals of 1000 records.
I need all records at once, but below code returns only first 1000 records.
var array=[];
function loop(skip){
return $http({
method: 'GET',
url: myurl+'&$skip='+skip,
timeout:5000
}).success(function(res){
if(res.d.length>0)
{
Array.prototype.push.apply( array,res.d);
loop(skip +1000);
}
return array;
}).error(function(response,status,headers,config) {
});
}
getAll = function() {
return loop(0);
}
I need a single request can obtain the total records.
but only I get the first 1000 records in this part :(
getAll().then(function() {
console.log("in this part i need the array with my 4000 records")
});
First, a bit sidenote from angular doc:
The $http legacy promise methods success and error have been deprecated. Use the standard then method instead. If $httpProvider.useLegacyPromiseExtensions is set to false then these methods will throw $http/legacy error.
Yet another solution: pass accumulator result as parameter to loop function
function loop(skip,result){
result = result||[];//accumulator for result, init empty array if not pass
return $http(
{
method: 'GET',
url: myurl+'&$skip='+skip,
timeout:5000
}).then(function success(response){
if(response.data.length > 0){
Array.prototype.push.apply(result,response.data);
return loop(skip+1000,result);
}
return result;
},function error(){
});
}
Notice, that main difference from your current code is return before calling loop function inside success handler.
This work, because if from then function return promise, then next then would be applied after returned promise fulfiled.
Sample plunkr
Should be possible with some recursive promise chaining. Try this
function getAll(page) {
if (typeof page === 'undefined') {
page = 0;
}
return $http.get(myurl, {$skip: page * 1000}).then(function(res) {
var data = res.data;
if (data.length > 0) {
return getAll(page + 1).then(function(nextData) {
return data.concat(nextData);
});
}
return data;
});
}
And call it like
getAll().then(function(allRecords) {
console.log(allRecords);
});
Here's a hacked together Plunker demo ~ http://plnkr.co/edit/ey8gdytvuBE6cpuMAtnB?p=preview
The
Working sample https://jsfiddle.net/coolbhes/xmqur1bq/ (replaced service with a promise)
Code which would work for you as below :
var array = [];
function loop(skip, resolveHandle) {
$http({
method: 'GET',
url: myurl + '&$skip=' + skip,
timeout: 5000
}).then(function successCallback(res) {
if (res.d.length > 0) {
Array.prototype.push.apply(array, res.d);
loop(skip + 1000, resolveHandle);
} else {
//done with all requests;
resolveHandle(array);
}
}, function errorCallback(rej) {
});
};
function getAll() {
return new Promise(function(resolve, reject) {
loop(0, resolve);
});
}
getAll().then(function(data) {
console.log("in this part i need the array with my 4000 records", data)
}

AngularJS & Protractor - Make a http request last longer

I'm working on tests for my angularjs app and when the page is loaded some http calls are made. When any call is made a loading circle appears and when a response is received the loading circle is hidden.
How can I make the loding circle visible for let's say 10 seconds ?
You can intercept the http requests and delay them:
network-delay.js
exports.module = function() {
angular.module('networkDelayInterceptor', [])
.config(function simulateNetworkLatency($httpProvider) {
function httpDelay($timeout, $q) {
var delayInMilliseconds = 1000;
var responseOverride = function (reject) {
return function (response) {
//Uncomment the lines below to filter out all the requests not needing delay
//if (response.config.url.indexOf('some-url-to-delay') === -1) {
// return response;
//}
var deferred = $q.defer();
$timeout(
function() {
if (reject) {
deferred.reject(response);
} else {
deferred.resolve(response);
}
},
delayInMilliseconds,
false
);
return deferred.promise;
};
};
return {
response: responseOverride(false),
responseError: responseOverride(true)
};
}
$httpProvider.interceptors.push(httpDelay);
})
};
Usage
beforeAll(function() {
var networkDelay = require('network-delay');
// You can customize the module with a parameter for the url and the delay by adding them as a 3rd and 4th param, and modifying the module to use them
browser.addMockModule('networkDelayInterceptor', networkDelay.module);
});
afterAll(function() {
browser.removeMockModule('networkDelayInterceptor');
});
it('My-slowed-down-test', function() {
});
Source: http://www.bennadel.com/blog/2802-simulating-network-latency-in-angularjs-with-http-interceptors-and-timeout.htm
You can make use of setTimeout method in javascript or alternatively $timeout function in angular js.

Multiple Promise Chains in Single Function

I have some code that will dynamically generate an AJAX request based off a scenario that I'm retrieving via an AJAX request to a server.
The idea is that:
A server provides a "Scenario" for me to generate an AJAX Request.
I generate an AJAX Request based off the Scenario.
I then repeat this process, over and over in a Loop.
I'm doing this with promises here: http://jsfiddle.net/3Lddzp9j/11/
However, I'm trying to edit the code above so I can handle an array of scenarios from the initial AJAX request.
IE:
{
"base": {
"frequency": "5000"
},
"endpoints": [
{
"method": "GET",
"type": "JSON",
"endPoint": "https://api.github.com/users/alvarengarichard",
"queryParams": {
"objectives": "objective1, objective2, objective3"
}
},
{
"method": "GET",
"type": "JSON",
"endPoint": "https://api.github.com/users/dkang",
"queryParams": {
"objectives": "objective1, objective2, objective3"
}
}
]
This seems like it would be straight forward, but the issue seems to be in the "waitForTimeout" function.
I'm unable to figure out how to run multiple promise chains. I have an array of promises in the "deferred" variable, but the chain only continues on the first one--despite being in a for loop.
Could anyone provide insight as to why this is? You can see where this is occuring here: http://jsfiddle.net/3Lddzp9j/10/
The main problems are that :
waitForTimeout isn't passing on all the instructions
even if waitForTimeout was fixed, then callApi isn't written to perform multiple ajax calls.
There's a number of other issues with the code.
you really need some data checking (and associated error handling) to ensure that expected components exist in the data.
mapToInstruction is an unnecessary step - you can map straight from data to ajax options - no need for an intermediate data transform.
waitForTimeout can be greatly simplified to a single promise, resolved by a single timeout.
synchronous functions in a promise chain don't need to return a promise - they can return a result or undefined.
Sticking with jQuery all through, you should end up with something like this :
var App = (function ($) {
// Gets the scenario from the API
// sugar for $.ajax with GET as method - NOTE: this returns a promise
var getScenario = function () {
console.log('Getting scenario ...');
return $.get('http://demo3858327.mockable.io/scenario2');
};
var checkData = function (data) {
if(!data.endpoints || !data.endpoints.length) {
return $.Deferred().reject('no endpoints').promise();
}
data.base = data.base || {};
data.base.frequency = data.base.frequency || 1000;//default value
};
var waitForTimeout = function(data) {
return $.Deferred(function(dfrd) {
setTimeout(function() {
dfrd.resolve(data.endpoints);
}, data.base.frequency);
}).promise();
};
var callApi = function(endpoints) {
console.log('Calling API with given instructions ...');
return $.when.apply(null, endpoints.map(ep) {
return $.ajax({
type: ep.method,
dataType: ep.type,
url: ep.endpoint
}).then(null, function(jqXHR, textStatus, errorThrown) {
return textStatus;
});
}).then(function() {
//convert arguments to an array of results
return $.map(arguments, function(arg) {
return arg[0];
});
});
};
var handleResults = function(results) {
// results is an array of data values/objects returned by the ajax calls.
console.log("Handling data ...");
...
};
// The 'run' method
var run = function() {
getScenario()
.then(checkData)
.then(waitForTimeout)
.then(callApi)
.then(handleResults)
.then(null, function(reason) {
console.error(reason);
})
.then(run);
};
return {
run : run
}
})(jQuery);
App.run();
This will stop on error but could be easily adapted to continue.
I'll try to answer your question using KrisKowal's q since I'm not very proficient with the promises generated by jQuery.
First of all I'm not sure whether you want to solve the array of promises in series or in parallel, in the solution proposed I resolved all of them in parallel :), to solve them in series I'd use Q's reduce
function getScenario() { ... }
function ajaxRequest(instruction) { ... }
function createPromisifiedInstruction(instruction) {
// delay with frequency, not sure why you want to do this :(
return Q.delay(instruction.frequency)
.then(function () {
return this.ajaxRequest(instruction);
});
}
function run() {
getScenario()
.then(function (data) {
var promises = [];
var instruction;
var i;
for (i = 0; i < data.endpoints.length; i += 1) {
instruction = {
method: data.endpoints[i].method,
type: data.endpoints[i].type,
endpoint: data.endpoints[i].endPoint,
frequency: data.base.frequency
};
promises.push(createPromisifiedInstruction(instruction));
}
// alternative Q.allSettled if all the promises don't need to
// be fulfilled (some of them might be rejected)
return Q.all(promises);
})
.then(function (instructionsResults) {
// instructions results is an array with the result of each
// promisified instruction
})
.then(run)
.done();
}
run();
Ok let me explain the solution above:
first of all assume that getScenario gets you the initial json you start with (actually returns a promise which is resolved with the json)
create the structure of each instruction
promisify each instruction, so that each one is actually a promise whose
resolution value will be the promise returned by ajaxRequest
ajaxRequest returns a promise whose resolution value is the result of the request, which also means that createPromisifiedInstruction resolution value will be the resolution value of ajaxRequest
Return a single promise with Q.all, what it actually does is fulfill itself when all the promises it was built with are resolved :), if one of them fails and you actually need to resolve the promise anyways use Q.allSettled
Do whatever you want with the resolution value of all the previous promises, note that instructionResults is an array holding the resolution value of each promise in the order they were declared
Reference: KrisKowal's Q
Try utilizing deferred.notify within setTimeout and Number(settings.frequency) * (1 + key) as setTimeout duration; msg at deferred.notify logged to console at deferred.progress callback , third function argument within .then following timeout
var App = (function ($) {
var getScenario = function () {
console.log("Getting scenario ...");
return $.get("http://demo3858327.mockable.io/scenario2");
};
var mapToInstruction = function (data) {
var res = $.map(data.endpoints, function(settings, key) {
return {
method:settings.method,
type:settings.type,
endpoint:settings.endPoint,
frequency:data.base.frequency
}
});
console.log("Instructions recieved:", res);
return res
};
var waitForTimeout = function(instruction) {
var res = $.when.apply(instruction,
$.map(instruction, function(settings, key) {
return new $.Deferred(function(dfd) {
setTimeout(function() {
dfd.notify("Waiting for "
+ settings.frequency
+ " ms")
.resolve(settings);
}, Number(settings.frequency) * (1 + key));
}).promise()
})
)
.then(function() {
return this
}, function(err) {
console.log("error", err)
}
, function(msg) {
console.log("\r\n" + msg + "\r\nat " + $.now() + "\r\n")
});
return res
};
var callApi = function(instruction) {
console.log("Calling API with given instructions ..."
, instruction);
var res = $.when.apply(instruction,
$.map(instruction, function(request, key) {
return request.then(function(settings) {
return $.ajax({
type: settings.method,
dataType: settings.type,
url: settings.endpoint
});
})
})
)
.then(function(data) {
return $.map(arguments, function(response, key) {
return response[0]
})
})
return res
};
var handleResults = function(data) {
console.log("Handling data ..."
, JSON.stringify(data, null, 4));
return data
};
var run = function() {
getScenario()
.then(mapToInstruction)
.then(waitForTimeout)
.then(callApi)
.then(handleResults)
.then(run);
};
return {
// This will expose only the run method
// but will keep all other functions private
run : run
}
})($);
// ... And start the app
App.run();
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js">
</script>
jsfiddle http://jsfiddle.net/3Lddzp9j/13/
You have a return statement in the loop in your waitForTimeout function. This means that the function is going to return after the first iteration of the loop, and that is where you are going wrong.
You're also using the deferred antipattern and are using promises in places where you don't need them. You don't need to return a promise from a then handler unless there's something to await.
The key is that you need to map each of your instructions to a promise. Array#map is perfect for this. And please use a proper promise library, not jQuery promises (edit but if you absolutely must use jQuery promises...):
var App = (function ($) {
// Gets the scenario from the API
// NOTE: this returns a promise
var getScenario = function () {
console.log('Getting scenario ...');
return $.get('http://demo3858327.mockable.io/scenario');
};
// mapToInstructions is basically unnecessary. each instruction does
// not need its own timeout if they're all the same value, and you're not
// reshaping the original values in any significant way
// This wraps the setTimeout into a promise, again
// so we can chain it
var waitForTimeout = function(data) {
var d = $.Deferred();
setTimeout(function () {
d.resolve(data.endpoints);
}, data.base.frequency);
return d.promise();
};
var callApi = function(instruction) {
return $.ajax({
type: instruction.method,
dataType: instruction.type,
url: instruction.endPoint
});
};
// Final step: call the API from the
// provided instructions
var callApis = function(instructions) {
console.log(instructions);
console.log('Calling API with given instructions ...');
return $.when.apply($, instructions.map(callApi));
};
var handleResults = function() {
var data = Array.prototype.slice(arguments);
console.log("Handling data ...");
};
// The 'run' method
var run = function() {
getScenario()
.then(waitForTimeout)
.then(callApis)
.then(handleResults)
.then(run);
};
return {
run : run
}
})($);
App.run();

Executing certain block of code only after angular service gets data from server and completes its execution

In my service:
appRoot.factory('ProgramsResource', function ($resource) {
return $resource('Home/Program', {}, { Program: { method: 'get', isArray: false } })
});
In my controller:
appRoot.controller('ProgramCtrl', function ($scope, ProgramsResource) {
$scope.searchPrograms = function () {
$scope.Programs = ProgramsResource.get(
{
Name: $scope.searchProgramName,
});
};
$scope.SortBy = "Name";
$scope.searchPrograms();
//Lines of code which I want to execute only after the searchPrograms() completes its execution
$scope.TotalItems = $scope.Programs.TotalItems;
$scope.ItemsPerPage = $scope.Programs.ItemsPerPage;
});
searchPrograms(); is responsible for getting data from server. And only after line $scope.searchPrograms(); I want to execute below code:
$scope.TotalItems = $scope.Programs.TotalItems;
$scope.ItemsPerPage = $scope.Programs.ItemsPerPage;
But its not happening like that. Its not waiting for searchPrograms() to complete its operation and executing the lines of code below it.
As in js, it won't wait for ajax to complete and executes the lines below it, this is happening.
To execute certain code only after ajax completion there is concept of call back functions in js and for the same there is concept of promises in angular.
I got very nice article over angular promises but not able to understand that, how exactly I should use the promises in my case.
you can add a callback function parameter into ProgramResource.get:
$scope.searchPrograms = function () {
$scope.Programs = ProgramsResource.get(
{
Name: $scope.searchProgramName,
}, function () {
$scope.TotalItems = $scope.Programs.TotalItems;
$scope.ItemsPerPage = $scope.Programs.ItemsPerPage;
});
};
You should use $q and deferred promise so you will be able to do something like this
$scope.searchPrograms().then(function(data) { // data is the data that search programs should return
$scope.TotalItems = $scope.Programs.TotalItems;
$scope.ItemsPerPage = $scope.Programs.ItemsPerPage;
}
make sure that searchPrograms return a promise.
var deferred = $q.defer();
var callback = function (response) {
if(response.error) {
deferred.reject(response.error)
}
deferred.resolve(response);
};
//Your service call that need a callback like myService.request(callback);
return deferred.promise;
So when the request will be done the code will be execute in .then(function(data) {

AngularJS service retry when promise is rejected

I'm getting data from an async service inside my controller like this:
myApp.controller('myController', ['$scope', 'AsyncService',
function($scope, AsyncService) {
$scope.getData = function(query) {
return AsyncService.query(query).then(function(response) {
// Got success response, return promise
return response;
}, function(reason) {
// Got error, query again in one second
// ???
});
}
}]);
My questions:
How to query the service again when I get error from service without returning the promise.
Would it be better to do this in my service?
Thanks!
You can retry the request in the service itself, not the controller.
So, AsyncService.query can be something like:
AsyncService.query = function() {
var counter = 0
var queryResults = $q.defer()
function doQuery() {
$http({method: 'GET', url: 'https://example.com'})
.success(function(body) {
queryResults.resolve(body)
})
.error(function() {
if (counter < 3) {
doQuery()
counter++
}
})
}
return queryResults.promise
}
And you can get rid of your error function in the controller:
myApp.controller('myController', ['$scope', 'AsyncService',
function($scope, AsyncService) {
$scope.getData = function(query) {
return AsyncService.query(query).then(function(response) {
// Got success response
return response;
});
}
}
]);
This actually works:
angular.module('retry_request', ['ng'])
.factory('RetryRequest', ['$http', '$q', function($http, $q) {
return function(path) {
var MAX_REQUESTS = 3,
counter = 1,
results = $q.defer();
var request = function() {
$http({method: 'GET', url: path})
.success(function(response) {
results.resolve(response)
})
.error(function() {
if (counter < MAX_REQUESTS) {
request();
counter++;
} else {
results.reject("Could not load after multiple tries");
}
});
};
request();
return results.promise;
}
}]);
Then just an example of using it:
RetryRequest('/api/token').then(function(token) {
// ... do something
});
You have to require it when declaring your module:
angular.module('App', ['retry_request']);
And in you controller:
app.controller('Controller', function($scope, RetryRequest) {
...
});
If someone wants to improve it with some kind of backoff or random timing to retry the request, that will be even better. I wish one day something like that will be in Angular Core
I wrote an implementation with exponential backoff that doesn't use recursion (which would created nested stack frames, correct?) The way it's implemented has the cost of using multiple timers and it always creates all the stack frames for the make_single_xhr_call (even after success, instead of only after failure). I'm not sure if it's worth it (especially if the average case is a success) but it's food for thought.
I was worried about a race condition between calls but if javascript is single-threaded and has no context switches (which would allow one $http.success to be interrupted by another and allow it to execute twice), then we're good here, correct?
Also, I'm very new to angularjs and modern javascript so the conventions may be a little dirty also. Let me know what you think.
var app = angular.module("angular", []);
app.controller("Controller", ["$scope", "$http", "$timeout",
function($scope, $http, $timeout) {
/**
* Tries to make XmlHttpRequest call a few times with exponential backoff.
*
* The way this works is by setting a timeout for all the possible calls
* to make_single_xhr_call instantly (because $http is asynchronous) and
* make_single_xhr_call checks the global state ($scope.xhr_completed) to
* make sure another request was not already successful.
*
* With sleeptime = 0, inc = 1000, the calls will be performed around:
* t = 0
* t = 1000 (+1 second)
* t = 3000 (+2 seconds)
* t = 7000 (+4 seconds)
* t = 15000 (+8 seconds)
*/
$scope.repeatedly_xhr_call_until_success = function() {
var url = "/url/to/data";
$scope.xhr_completed = false
var sleeptime = 0;
var inc = 1000;
for (var i = 0, n = 5 ; i < n ; ++i) {
$timeout(function() {$scope.make_single_xhr_call(url);}, sleeptime);
sleeptime += inc;
inc = (inc << 1); // multiply inc by 2
}
};
/**
* Try to make a single XmlHttpRequest and do something with the data.
*/
$scope.make_single_xhr_call = function(url) {
console.log("Making XHR Request to " + url);
// avoid making the call if it has already been successful
if ($scope.xhr_completed) return;
$http.get(url)
.success(function(data, status, headers) {
// this would be later (after the server responded)-- maybe another
// one of the calls has already completed.
if ($scope.xhr_completed) return;
$scope.xhr_completed = true;
console.log("XHR was successful");
// do something with XHR data
})
.error(function(data, status, headers) {
console.log("XHR failed.");
});
};
}]);
Following this article Promises in AngularJS, Explained as a Cartoon
you need to retry only when the response comes under 5XX category
I have written a service called http which can be called by passing all http configs as
var params = {
method: 'GET',
url: URL,
data: data
}
then call the service method as follows:
<yourDefinedAngularFactory>.http(params, function(err, response) {});
http: function(config, callback) {
function request() {
var counter = 0;
var queryResults = $q.defer();
function doQuery(config) {
$http(config).success(function(response) {
queryResults.resolve(response);
}).error(function(response) {
if (response && response.status >= 500 && counter < 3) {
counter++;
console.log('retrying .....' + counter);
setTimeout(function() {
doQuery(config);
}, 3000 * counter);
} else {
queryResults.reject(response);
}
});
}
doQuery(config);
return queryResults.promise;
}
request(config).then(function(response) {
if (response) {
callback(response.errors, response.data);
} else {
callback({}, {});
}
}, function(response) {
if (response) {
callback(response.errors, response.data);
} else {
callback({}, {});
}
});
}
I ended up doing this a lot so I wrote a library to help address this problem : )
https://www.npmjs.com/package/reattempt-promise-function
In this example you could do something like
myApp.controller('myController', ['$scope', 'AsyncService',
function($scope, AsyncService) {
var dogsQuery = { family: canine };
$scope.online = true;
$scope.getDogs = function() {
return reattempt(AsyncService.query(dogsQuery)).then(function(dogs) {
$scope.online = true;
$scope.dogs = dogs;
}).catch(function() {
$scope.online = false;
});
}
}]);

Categories

Resources