Prevent multiple AJAX calls to the same url within timeframe - javascript

I have an app which wants to get info about every marker on a map.
Each marker has a class, such as "car" or "pedestrian".
The app makes (via jQuery) a getJSON call to "http://myserver/info/".
However, since multiple markers may have the same class, the server could end up getting hit with many requests.
Accordingly, I'd like to pool requests which occur within a specified time frame (maybe 5 seconds or so) so that only one request is made, but each calling instance of getJSON is unaware of it.
My thought is to wrap getJSON in another function which stores the URLS in a hashmap/dictionary and stores up promises for each requester. When data is returned, the promises are fulfilled.
I ask, is there a standard way of doing this (debouncing an AJAX request, as it were)?

I created something (in 25 minutes ^^) that might help you; it's a Timeout manager:
var requestsPool = {
requests: {}, //list of urls
timeout: 5000, //In milliseconds
add: function(url) {
if(requestsPool.exists(url)) return false; //check if url is already present in the pool
requestsPool.requests[url] = setTimeout(function(u) {
requestsPool.remove(u);
}.bind(this, url), requestsPool.timeout); //Defining the timeout
return true;
},
exists: function(url) {
return requestsPool.requests[url]; //Return the Timeout ID if present or undefined
},
remove: function(url) {
return delete requestsPool.requests[url]; //return true almost always #link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/delete
},
cancel: function(url) {
clearTimeout(requestsPool.requests[url]); //cancel the timeout
return requestsPool.remove(url); //remove the url form the pool
}
}
$(anchor).click(function() {
if(requestsPool.exists(anchor.href)) {
// If cooldown is present
} else {
$.getJSON(anchor.href, function(data) {
requestsPool.add(anchor.href);
});
}
})

My thought is to wrap getJSON in another function which stores the URLS in a hashmap/dictionary and stores up promises for each requester
Yes, that's a good idea. It might look like this:
var debouncedGet = (function() {
var pool = {};
return function get(url) {
if (!pool[url]) {
pool[url] = $.getJSON(url);
setTimeout(function() {
pool[url] = null;
}, 5000); // you might want to move this into a `pool[url].always(…)` callback
// so the timer starts when the request returned
}
return pool[url];
};
}());

Here's my bid:
(function(window,$,undefined){
'use strict';
var cache = {},
timeout = 5e3;
// Use like traditional $.getJSON
$.getJSON = function(url,data,callback){
if ($.isFunction(data)){
callback = data;
data = undefined;
}
// Establish a cache key so we can re-reference existing
// requests to subsequent ones (within the timeout window).
var cacheKey = url;
if (cache[cacheKey]){
// This is an existing request; Simple add the callback
// onto the promise and return it.
return cache[cacheKey].done(callback);
} else {
// This is a new request. Build up a new request,
// attach the callback to the promise, and also add
// a couple cleanup methods for disposing the cache
// when appropriate.
cache[cacheKey] = $.ajax($.extend({
url: url,
type: 'get',
dataType: 'json',
data: data,
}, $.isPlainObject(url) && url))
.done(callback)
.always(function(){
delete cache[cacheKey];
});
setTimeout(function(){
// TODO: Probbaly want to store a reference to
// this timeout and clear it in the .always (to
// avoid race condition between .always firing
// and new request coming in but not returning yet)
cache[cacheKey] && delete cache[cacheKey];
}, timeout);
return cache[cacheKey];
}
};
})(window,jQuery);
And, FWIW, a jsFiddle: http://jsfiddle.net/ajtbdxt7/

Related

Timing a set of functions with asyncronous subroutines

I have two functions periodically called via setInterval. The goal is to defer Function B until Function A is done (and vis versa). Currently, Function A will start, complete some of its subroutines, but not reach the end before Function B begins.
I've tried passing Function B as an argument of Function A. I am not sure if that was sufficient to create a callback. I also tried jQuery's $.when(setInterval(functionA, 10000)).then(setInterval(functionB, 5000)).
How do I ask JavaScript to wait for functions/blocks of code to finish? Thank you in advance.
Edit: Below is code very similar to my original. Sorry for not being concise.
Function A, getFruits(): There is a remote JSON that changes on its own (fruits.json). getFruits() does two things: 1) It empties an array, [allFruits] (just in case); 2) It adds all the names of fruit currently in the remote JSON to [allFruits]. Now, [allFruits] is an instanced copy of the remote JSON. Before this question, I only called getFruits() once, at startup; in other words, I did not use setInterval for getFruits().
Function B, checkFruits(): Now checkFruits() periodically (setInterval(checkFruits, 5000)) compares [allFruits] to the remote version. If any fruit was added to the remote version, checkFruits appends [allFruits] with those fruits' names; it also runs useful code (i.e. pushes the new names to an array [queue]).
For this implementation, it is important to create an initial list so only new (post-startup) fruit trigger the useful code of checkFruits(). Moreover, it is important only to add (never subtract) names from [allFruits] within a session. This is to prevent a new fruit from triggering the useful code more than once per session.
Problem: Now I want to make getFruits() (Function A) periodic. Because getFruits() empties [allFruits], it will allow the names that built up to again trigger useful code (but only once in between invocations of getFruits()). However, when I use setInterval(getFruits, 10000), there are times (in this example, always) when getFruits() overlaps with checkFruits(). When that happens, I notice only part of getFruits() finishes before checkFruits() starts. The console.log() messages appear in this order: 'getFruits() start:', 'checkFruits():', 'getFruits() end:'. Furthermore, my useful code is ran before getFruits() finishes (this is what is really undesired), and [allFruits] gets duplicates. This would not occur if getFruits() completely finished before checkFruits() jumped in.
debugging = true;
var debug = function() {
if (debugging){
console.log.apply(console, arguments)
};
}
var allFruits = [];
var queue = [];
var getFruits = function() {
allFruits = []; // Empty the list
debug('getFruits() start:', 'allFruits =', allFruits, 'queue =', queue);
$.ajax({
url: 'fruits.json',
dataType: 'json',
success: function(data) {
data.fruits.forEach(function(element) {
allFruits.push(element.name);
});
debug('getFruits() end:', 'data =', data, 'allFruits =', allFruits, 'queue =', queue);
},
});
}
var checkFruits = function() {
$.ajax({
url: 'fruits.json',
dataType: 'json',
success: function(data) {
data.fruits.forEach(function(element) {
if (allFruits.indexOf(element.name) === -1) {
queue.push(['fruit', element.name]);
allFruits.push(element.name);
}
});
debug('checkFruits():', 'data =', data, 'allFruits =', allFruits, 'queue =', queue);
}
});
}
getFruits();
setInterval(checkFruits, 5000);
// setInterval(getFruits, 10000); // When I try this, checkFruits() does not wait for getFruits() to finish.
The analogy of my actual remote resource is fruits.json. fruits.json can simply be the following:
{"fruits":[{"name":"apple","color":"red"},{"name":"banana","color":"yellow"},{"name":"tangerine","color":"orange"}]}
Again, the actual, remote JSON changes independently.
What you have here are two methods that each do asynchronouse stuff. Here are some good stack overflow posts on what that means.
Easy to understand definition of "asynchronous event"?
Does async programming mean multi-threading?
Are JavaScript functions asynchronous?
We have no idea how long it will take for an asynchronous call to finish. In your case, the AJAX request could take up to a few seconds depending on network speeds so regardless of when each of these methods are executed you CANNOT know which one will finish first. So what to do? Well, generally when you write/use an asynchronous method (like $.ajax) you give it a callback that will be executed when the asynchronous work is finished. And you have done this in the form of the success callback. And here is the good news. The success callbacks are SYNCHRONOUS (note the missing a). This means that the "useful code" in the success callback that needs to be run when a request finishes will complete (so long as none of it is async) before the "other useful code" in the other success callback is executed at all. And this works no matter which request finishes first. Each success callback will always wait for the other. So I think what was confusing you was your debug statements. If you add the following statements to your code the execution flow may make more sense:
debugging = true;
var debug = function() {
if (debugging) {
console.log.apply(console, arguments)
};
}
var allFruits = [];
var queue = [];
var getFruits = function() {
debug("getFruits: make request");
$.ajax({
url: 'fruits.json',
dataType: 'json',
success: function(data) {
debug("getFruits: start processing");
allFruits = []; // Empty the list
data.fruits.forEach(function(element) {
allFruits.push(element.name);
});
debug('getFruits: finished processing');
},
});
debug("getFruits: request sent, now we wait for a response.");
}
var checkFruits = function() {
debug("checkFruits: make request");
$.ajax({
url: 'fruits.json',
dataType: 'json',
success: function(data) {
debug("checkFruits: start processing");
data.fruits.forEach(function(element) {
if (allFruits.indexOf(element.name) === -1) {
queue.push(['fruit', element.name]);
allFruits.push(element.name);
}
});
debug("checkFruits: finished processing");
}
});
debug("checkFruits: request sent, now we wait for a response.");
}
getFruits();
setInterval(checkFruits, 5000);
// setInterval(getFruits, 10000); // When I try this, checkFruits() does not wait for getFruits() to finish.
After thinking about it I believe the only reason things may not have been behaving as expected is because you're emptying the allFruits array outside of the callback. If you move it as I have done I would think everything should work fine.
Now, I don't know why you need to re-initialize the data since each time you make the request your getting the latest information but lets roll with it. Since both methods make the same request lets consolidate that into a single method. No need to duplicate code ;). And since all of your examples have the getFruits running twice as slow as the checkFruits we could easily add a counter to accomplish the same sequence of events like so:
debugging = true;
var debug = function() {
if (debugging) {
console.log.apply(console, arguments)
};
}
var allFruits = [];
var queue = [];
var count = 0;
var doOneThing = function(data) {
//do stuff
}
var doAnotherThing= function(data) {
//do other stuff
}
var requestFruits = function() {
$.ajax({
url: 'fruits.json',
dataType: 'json',
success: function(data) {
// if count is even...or, do this every other time.
if (count % 2 === 0) {
count++;
doOneThing(data);
}
// do this everytime
doAnotherThing(data);
},
});
}
setInterval(requestFruits, 5000);
Hope this helps. Cheers.
your last code example first executes setInterval(functionA), and when the deferred execution of functionA is setup, executes setInterval(functionB), meaning that B will called +- 5 seconds after that line is executed, while functionA is called +- 10 seconds.
edit to reflect your additional information:
setInterval(function(){
functionA();
functionB();
}, 10000)
setTimeout(function(){
setInterval(functionB, 10000)
}, 5000)
This is a crude answer. I sense that callbacks can achieve this, but I am not sure how to code them, especially involving setInterval.
I create two global variables, getFruitsIsBusy = false and checkFruitsIsBusy = false. I create an IF for both getFruits() and checkFruits(). Here is getFruits():
var getFruits = function() {
if (checkFruitsIsBusy) { // New
setTimeout(getFruits, 100); // New
return; // New
} else { // New
getFruitsIsBusy = true // New
allFruits = []; // Empty the list
debug('getFruits() start:', 'allFruits =', allFruits, 'queue =', queue);
$.ajax({
url: 'fruits.json',
dataType: 'json',
success: function(data) {
data.fruits.forEach(function(element) {
allFruits.push(element.name);
});
getFruitsIsBusy = false // New; in the success function
debug('getFruits() end:', 'data =', data, 'allFruits =', allFruits, 'queue =', queue)
},
});
}
}
If also using this paradigm for checkFruits(), it seems both functions will wait for each other to finish.
Based on an analysis of the timing of two functions (A and B), consider the following solution (Chionglo, 2016):
Keep state information for each of function A and function B. The state of each function should be set within each of the respective functions.
Create a wrapper function for each of function A and function B. The wrapper function calls on the respective function, and then checks for the state of the respective function.
a. The check in wrapper function A: if function A has reached is final state, clear the interval associated with wrapper function A and schedule an interval for wrapper function B.
b. The check in wrapper function B: if function B has reached its final state, clear the interval associated with wrapper function B.
To begin the process, schedule an interval for wrapper function A.
Sample code:
var ac = Math.round(4*Math.random())+4;
var bc = Math.round(6*Math.random())+6;
var ai;
var Astate = false;
var Bstate = false;
function A() {
// Do your thing for A here.
// The following changes the “state of A” and then determines if the final state has been reached.
ac -= 1;
if (ac<1) Astate = true;
else Astate = false;
}
function B() {
// Do your thing for B here.
// The following changes the “state of B” and then determines if the final state has been reached.
bc -= 1;
if (bc<1) Bstate = true;
else Bstate = false;
}
ai = setInterval("processA()", 1000);
function processA() {
A();
if (Astate) {
clearInterval(ai);
ai = setInterval("processB()", 500);
}
}
function processB() {
B();
if (Bstate) {
clearInterval(ai);
ai = undefined;
}
}
Reference
Chionglo, J. F. (2016). An analysis for timing a set of functions. Available at http://www.aespen.ca/AEnswers/1458200332.pdf.

New $.Deferred object with the old callbacks

Please forgive me if this is a stupid question. I have been trying for hours and my brain have just stopped working.
I have such system that consists of three AJAX calls. Server response of first call usually is a 200 Success; but second and third queries are fragile because they are image uploading, and on the server side, I have so much validation rules that client's images mostly fail.
window.AjaxCall = function () {
// to pass to $.ajax call later
this.args = arguments;
// xhr status
this.status = null;
// xhr results (jqXHR object and response)
this.xhrResponse = {};
this.dfr = new $.Deferred();
// to provide an easier interface
this.done = this.dfr.done;
this.fail = this.dfr.fail;
this.then = this.dfr.then;
};
AjaxCall.prototype.resetDfr = function () {
this.dfr = new $.Deferred();
};
AjaxCall.prototype.resolve = function () {
this.dfr.resolve(
this.xhrResponse.result,
this.xhrResponse.jqXHR
);
this.resetDfr();
};
AjaxCall.prototype.reject = function () {
this.dfr.reject(
this.xhrResponse.jqXHR
);
this.resetDfr();
};
AjaxCall.prototype.query = function () {
var _this = this;
// if query hasn't run yet, or didn't return success, run it again
if (_this.status != 'OK') {
$.ajax.apply(_this, _this.args)
.done(function (result, textStatus, jqXHR) {
_this.xhrResponse.result = result;
_this.xhrResponse.jqXHR = jqXHR;
_this.resolve();
})
.fail(function (jqXHR) {
_this.xhrResponse.jqXHR = jqXHR;
_this.reject();
})
.always(function (a, b, c) {
var statusCode = (typeof c !== 'string'
? c
: a).status;
if (statusCode == 200) {
_this.status = 'OK';
}
});
}
// if query has been run successfully before, just skip to next
else {
_this.resolve();
}
return _this.dfr.promise();
};
AjaxCall class is as provided above, and I make the three consecutive calls like this:
var First = new AjaxCall('/'),
Second = new AjaxCall('/asd'),
Third = new AjaxCall('/qqq');
First.then(function () {
console.log('#1 done');
}, function() {
console.error('#1 fail');
});
Second.then(function () {
console.log('#2 done');
}, function() {
console.error('#2 fail');
});
Third.then(function () {
console.log('#3 done');
}, function() {
console.error('#3 fail');
});
var toRun = function () {
First.query()
.then(function () {
return Second.query();
})
.then(function () {
return Third.query()
});
};
$('button').click(function () {
toRun();
});
Those code are in a testing environment. And by testing environment, I mean a simple HTML page and basic server support for debugging.
Home page (/) always returns 200 Success.
/asd returns 404 Not Found for the first 3 times and 200 Success once as a pattern (i.e. three 404s -> one 200 -> three 404s -> one 200 -> three 404s -> ... ).
/qqq returns 404 Not Found all the time.
When I click the only button on the page, first query returns success and second fails as expected. When I click the button second time, first query skips because it was successful last time and second fails again, also as expected.
The problem here is:
before I used the resetDfr method because the dfr is alreay resolved or rejected, it doesn't react to resolve and reject methods anymore.
When I call the resetDfr method in the way I show in the example, dfr is able to get resolved or rejected again, but the callbacks of the old dfr are not binded with the new dfr object and I couldn't find a way to clone the old callbacks into the new dfr.
What would be your suggestion to accomplish what I'm trying to do here?
Promises represent a single value bound by time. You can't conceptually "reuse" a deferred or reset it - once it transitions it sticks. There are constructs that generalize promises to multiple values (like observables) but those are more complicated in this case - it's probably better to just use one deferred per request.
jQuery's AJAX already provides a promise interface. Your code is mostly redundant - you can and should consider using the existent tooling.
Let's look at $.get:
It already returns a promise so you don't need to create your own deferred.
It already uses the browser cache, unless your server prohibits HTTP caching or the browser refuses it only one request will be made to the server after a correct response arrived (assuming you did not explicitly pass {cache: false} to its parameters.
If making post requests you can use $.post or more generally $.ajax for arbitrary options.
This is how your code would roughly look like:
$("button").click(function(){
var first = $.get("/");
var second = first.then(function(){
return $.get("/asd");
});
var third = second.then(function(){
return $.get("/qqq");
});
});
The reason I put them in variables is so that you will be able to unwrap the result yourself later by doing first.then etc. It's quite possible to do this in a single chain too (but you lose access to previous values if you don't explicitly save them.
For the record - it wasn't a stupid question at all :)

Good way of avoiding a second ajax call

So I'm doing a an ajax call in this function somewhat like this:
function getCount() {
$.get("/People/getCount", function (data) {
if (data && data != "") {
// lots of code in here
}
What I'm doing in another function is making a second call like this:
function worldPeople() {
return $.get("/People/getCount", function (data) {
if (data != 0) {
var target = $("#worldNumbers").find("span");
target.html(data.length).digits();
}
})
}
So I really would like to avoid making that second call. Is there any good way in avoiding that? Maybe do some chaining or such, reusing the callback from the first one? I've heard that its bad practice to do several calls.
Regards
Would like to thank all who answered. In the end did not use any of the solutions, I solved it in another way. I'm sure most of the examples you gave me were really good. Do not know how to do with accepting answers. Accept all or none?! Thanks!
You could create a simple data store:
App.store = function () {
this.people = null;
this.count
loadPeople = function () {
if(this.people === null) {
$.get("/People/getCount", function (data) {
if (data != 0) {
this.count = (data.length).digits();
this.people = data;
}
}
};
}
What about store count of peoples in hidden field? And than check this field before sending request.
You can achieve this by handling your Ajax requests using some sort of cache. I use a cache that saves the information retrieved based on the url it called. If another function sets off the same request the cache returns the alraedy fetched data.
What you do need to do as well though is check if the data is outdated so you can refetch it if necessary.
Well, you can just send the function pointer to the function that executes $.get
basically you would then do this:
function worldPeople() {
getCountFromServer(function(data){
//do sth with data
});
}
function getCount() {
getCountFromServer(function(data){
//do sth with data
});
}
function getCountFromServer(callback) {
return $.get("/People/getCount", function (data) {
if (data)
callback(data);
});
}
I generally use a caching module pattern for this kind of thing:
// create a quick singleton to store cached data
var People = (function() {
// private variable to act as cache
var count;
// function to get cached data
// note: You have to assume it's always asynchronous
function getCount(callback) {
// have we loaded the data yet?
if (count===undefined) {
// cache miss: load the data, store it, do the callback
$.get("/People/getCount", function (data) {
count = data;
callback(data);
}
} else {
// cache hit - no need to reload
callback(count);
}
}
// provide access to the getter function
return {
getCount: getCount
};
}());
The first time you hit the cache, it'll load from the server; the second time it will load from the private variable.
// will load the data asynchronously
People.getCount(function(count) {
alert("First hit: " + count);
});
// will use the cached data
People.getCount(function(count) {
alert("Second hit: " + count);
});
Depending on the complexity you want to support, you could add additional features like expiring the cache after a particular interval, caching multiple calls (potentially keyed to the AJAX URL), etc. I like to keep the API simple and not reference the AJAX URLs - that way your cache acts like an abstracted service layer, and you can create other cache implementation to work with different data sources - useful for things like stubbing out data before you've implemented your server-side AJAX handlers.

How can jQuery deferred be used?

jQuery 1.5 brings the new Deferred object and the attached methods .when, .Deferred and ._Deferred.
For those who haven't used .Deferred before, I've annotated the source for it.
What are the possible usages of these new methods, how do we go about fitting them into patterns?
I have already read the API and the source, so I know what it does. My question is how can we use these new features in everyday code?
I have a simple example of a buffer class that calls AJAX requests in order. (Next one starts after the previous one finishes).
/* Class: Buffer
* methods: append
*
* Constructor: takes a function which will be the task handler to be called
*
* .append appends a task to the buffer. Buffer will only call a task when the
* previous task has finished
*/
var Buffer = function(handler) {
var tasks = [];
// empty resolved deferred object
var deferred = $.when();
// handle the next object
function handleNextTask() {
// if the current deferred task has resolved and there are more tasks
if (deferred.isResolved() && tasks.length > 0) {
// grab a task
var task = tasks.shift();
// set the deferred to be deferred returned from the handler
deferred = handler(task);
// if its not a deferred object then set it to be an empty deferred object
if (!(deferred && deferred.promise)) {
deferred = $.when();
}
// if we have tasks left then handle the next one when the current one
// is done.
if (tasks.length > 0) {
deferred.done(handleNextTask);
}
}
}
// appends a task.
this.append = function(task) {
// add to the array
tasks.push(task);
// handle the next task
handleNextTask();
};
};
I'm looking for demonstrations and possible uses of .Deferred and .when.
It would also be lovely to see examples of ._Deferred.
Linking to the new jQuery.ajax source for examples is cheating.
I am particularly interested in what techniques are available when we abstract away whether an operation is synchronously or asynchronously done.
The best use case I can think of is in caching AJAX responses. Here's a modified example from Rebecca Murphey's intro post on the topic:
var cache = {};
function getData( val ){
// return either the cached value or jqXHR object wrapped Promise
return $.when(
cache[ val ] ||
$.ajax('/foo/', {
data: { value: val },
dataType: 'json',
success: function( resp ){
cache[ val ] = resp;
}
})
);
}
getData('foo').then(function(resp){
// do something with the response, which may
// or may not have been retrieved using an
// XHR request.
});
Basically, if the value has already been requested once before it's returned immediately from the cache. Otherwise, an AJAX request fetches the data and adds it to the cache. The $.when/.then doesn't care about any of this; all you need to be concerned about is using the response, which is passed to the .then() handler in both cases. jQuery.when() handles a non-Promise/Deferred as a Completed one, immediately executing any .done() or .then() on the chain.
Deferreds are perfect for when the task may or may not operate asynchronously, and you want to abstract that condition out of the code.
Another real world example using the $.when helper:
$.when($.getJSON('/some/data/'), $.get('template.tpl')).then(function (data, tmpl) {
$(tmpl) // create a jQuery object out of the template
.tmpl(data) // compile it
.appendTo("#target"); // insert it into the DOM
});
Here is a slightly different implementation of an AJAX cache as in ehynd's answer.
As noted in fortuneRice's follow-up question, ehynd's implementation didn't actually prevent multiple identical requests if the requests were performed before one of them had returned. That is,
for (var i=0; i<3; i++) {
getData("xxx");
}
will most likely result in 3 AJAX requests if the result for "xxx" has not already been cached before.
This can be solved by caching the request's Deferreds instead of the result:
var cache = {};
function getData( val ){
// Return a promise from the cache (if available)
// or create a new one (a jqXHR object) and store it in the cache.
var promise = cache[val];
if (!promise) {
promise = $.ajax('/foo/', {
data: { value: val },
dataType: 'json'
});
cache[val] = promise;
}
return promise;
}
$.when(getData('foo')).then(function(resp){
// do something with the response, which may
// or may not have been retreived using an
// XHR request.
});
A deferred can be used in place of a mutex. This is essentially the same as the multiple ajax usage scenarios.
MUTEX
var mutex = 2;
setTimeout(function() {
callback();
}, 800);
setTimeout(function() {
callback();
}, 500);
function callback() {
if (--mutex === 0) {
//run code
}
}
DEFERRED
function timeout(x) {
var dfd = jQuery.Deferred();
setTimeout(function() {
dfd.resolve();
}, x);
return dfd.promise();
}
jQuery.when(
timeout(800), timeout(500)).done(function() {
// run code
});
When using a Deferred as a mutex only, watch out for performance impacts (http://jsperf.com/deferred-vs-mutex/2). Though the convenience, as well as additional benefits supplied by a Deferred is well worth it, and in actual (user driven event based) usage the performance impact should not be noticeable.
This is a self-promotional answer, but I spent a few months researching this and presented the results at jQuery Conference San Francisco 2012.
Here is a free video of the talk:
https://www.youtube.com/watch?v=juRtEEsHI9E
Another use that I've been putting to good purpose is fetching data from multiple sources. In the example below, I'm fetching multiple, independent JSON schema objects used in an existing application for validation between a client and a REST server. In this case, I don't want the browser-side application to start loading data before it has all the schemas loaded. $.when.apply().then() is perfect for this. Thank to Raynos for pointers on using then(fn1, fn2) to monitor for error conditions.
fetch_sources = function (schema_urls) {
var fetch_one = function (url) {
return $.ajax({
url: url,
data: {},
contentType: "application/json; charset=utf-8",
dataType: "json",
});
}
return $.map(schema_urls, fetch_one);
}
var promises = fetch_sources(data['schemas']);
$.when.apply(null, promises).then(
function () {
var schemas = $.map(arguments, function (a) {
return a[0]
});
start_application(schemas);
}, function () {
console.log("FAIL", this, arguments);
});
Another example using Deferreds to implement a cache for any kind of computation (typically some performance-intensive or long-running tasks):
var ResultsCache = function(computationFunction, cacheKeyGenerator) {
this._cache = {};
this._computationFunction = computationFunction;
if (cacheKeyGenerator)
this._cacheKeyGenerator = cacheKeyGenerator;
};
ResultsCache.prototype.compute = function() {
// try to retrieve computation from cache
var cacheKey = this._cacheKeyGenerator.apply(this, arguments);
var promise = this._cache[cacheKey];
// if not yet cached: start computation and store promise in cache
if (!promise) {
var deferred = $.Deferred();
promise = deferred.promise();
this._cache[cacheKey] = promise;
// perform the computation
var args = Array.prototype.slice.call(arguments);
args.push(deferred.resolve);
this._computationFunction.apply(null, args);
}
return promise;
};
// Default cache key generator (works with Booleans, Strings, Numbers and Dates)
// You will need to create your own key generator if you work with Arrays etc.
ResultsCache.prototype._cacheKeyGenerator = function(args) {
return Array.prototype.slice.call(arguments).join("|");
};
Here is an example of using this class to perform some (simulated heavy) calculation:
// The addingMachine will add two numbers
var addingMachine = new ResultsCache(function(a, b, resultHandler) {
console.log("Performing computation: adding " + a + " and " + b);
// simulate rather long calculation time by using a 1s timeout
setTimeout(function() {
var result = a + b;
resultHandler(result);
}, 1000);
});
addingMachine.compute(2, 4).then(function(result) {
console.log("result: " + result);
});
addingMachine.compute(1, 1).then(function(result) {
console.log("result: " + result);
});
// cached result will be used
addingMachine.compute(2, 4).then(function(result) {
console.log("result: " + result);
});
The same underlying cache could be used to cache Ajax requests:
var ajaxCache = new ResultsCache(function(id, resultHandler) {
console.log("Performing Ajax request for id '" + id + "'");
$.getJSON('http://jsfiddle.net/echo/jsonp/?callback=?', {value: id}, function(data) {
resultHandler(data.value);
});
});
ajaxCache.compute("anID").then(function(result) {
console.log("result: " + result);
});
ajaxCache.compute("anotherID").then(function(result) {
console.log("result: " + result);
});
// cached result will be used
ajaxCache.compute("anID").then(function(result) {
console.log("result: " + result);
});
You can play with the above code in this jsFiddle.
1) Use it to ensure an ordered execution of callbacks:
var step1 = new Deferred();
var step2 = new Deferred().done(function() { return step1 });
var step3 = new Deferred().done(function() { return step2 });
step1.done(function() { alert("Step 1") });
step2.done(function() { alert("Step 2") });
step3.done(function() { alert("All done") });
//now the 3 alerts will also be fired in order of 1,2,3
//no matter which Deferred gets resolved first.
step2.resolve();
step3.resolve();
step1.resolve();
2) Use it to verify the status of the app:
var loggedIn = logUserInNow(); //deferred
var databaseReady = openDatabaseNow(); //deferred
jQuery.when(loggedIn, databaseReady).then(function() {
//do something
});
You can use a deferred object to make a fluid design that works well in webkit browsers. Webkit browsers will fire resize event for each pixel the window is resized, unlike FF and IE which fire the event only once for each resize. As a result, you have no control over the order in which the functions bound to your window resize event will execute. Something like this solves the problem:
var resizeQueue = new $.Deferred(); //new is optional but it sure is descriptive
resizeQueue.resolve();
function resizeAlgorithm() {
//some resize code here
}
$(window).resize(function() {
resizeQueue.done(resizeAlgorithm);
});
This will serialize the execution of your code so that it executes as you intended it to. Beware of pitfalls when passing object methods as callbacks to a deferred. Once such method is executed as a callback to deferred, the 'this' reference will be overwritten with reference to the deferred object and will no longer refer to the object the method belongs to.
You can also integrate it with any 3rd-party libraries which makes use of JQuery.
One such library is Backbone, which is actually going to support Deferred in their next version.
I've just used Deferred in real code. In project jQuery Terminal I have function exec that call commands defined by user (like he was entering it and pressing enter), I've added Deferreds to the API and call exec with arrays. like this:
terminal.exec('command').then(function() {
terminal.echo('command finished');
});
or
terminal.exec(['command 1', 'command 2', 'command 3']).then(function() {
terminal.echo('all commands finished');
});
the commands can run async code, and exec need to call user code in order. My first api use pair of pause/resume calls and in new API I call those automatic when user return promise. So user code can just use
return $.get('/some/url');
or
var d = new $.Deferred();
setTimeout(function() {
d.resolve("Hello Deferred"); // resolve value will be echoed
}, 500);
return d.promise();
I use code like this:
exec: function(command, silent, deferred) {
var d;
if ($.isArray(command)) {
return $.when.apply($, $.map(command, function(command) {
return self.exec(command, silent);
}));
}
// both commands executed here (resume will call Term::exec)
if (paused) {
// delay command multiple time
d = deferred || new $.Deferred();
dalyed_commands.push([command, silent, d]);
return d.promise();
} else {
// commands may return promise from user code
// it will resolve exec promise when user promise
// is resolved
var ret = commands(command, silent, true, deferred);
if (!ret) {
if (deferred) {
deferred.resolve(self);
return deferred.promise();
} else {
d = new $.Deferred();
ret = d.promise();
ret.resolve();
}
}
return ret;
}
},
dalyed_commands is used in resume function that call exec again with all dalyed_commands.
and part of the commands function (I've stripped not related parts)
function commands(command, silent, exec, deferred) {
var position = lines.length-1;
// Call user interpreter function
var result = interpreter.interpreter(command, self);
// user code can return a promise
if (result != undefined) {
// new API - auto pause/resume when using promises
self.pause();
return $.when(result).then(function(result) {
// don't echo result if user echo something
if (result && position === lines.length-1) {
display_object(result);
}
// resolve promise from exec. This will fire
// code if used terminal::exec('command').then
if (deferred) {
deferred.resolve();
}
self.resume();
});
}
// this is old API
// if command call pause - wait until resume
if (paused) {
self.bind('resume.command', function() {
// exec with resume/pause in user code
if (deferred) {
deferred.resolve();
}
self.unbind('resume.command');
});
} else {
// this should not happen
if (deferred) {
deferred.resolve();
}
}
}
The answer by ehynds will not work, because it caches the responses data. It should cache the jqXHR which is also a Promise.
Here is the correct code:
var cache = {};
function getData( val ){
// return either the cached value or an
// jqXHR object (which contains a promise)
return cache[ val ] || $.ajax('/foo/', {
data: { value: val },
dataType: 'json',
success: function(data, textStatus, jqXHR){
cache[ val ] = jqXHR;
}
});
}
getData('foo').then(function(resp){
// do something with the response, which may
// or may not have been retreived using an
// XHR request.
});
The answer by Julian D. will work correct and is a better solution.

Extjs: two parallel ajax call

my code creates two ajax call at the same time (i assume the parallelism would be more efficient). I want to load a table if both calls succeed. What's the proper way of doing this?
var succeeded = {};
function callBackOne(){
succeeded.one = true;
// your other stuff
if (succeeded.two) { bothHaveSucceeded());
}
function callBackTwo(){
succeeded.two = true;
// your other stuff
if (succeeded.one) { bothHaveSucceeded());
}
I'd use a delayed task personally:
var success = {
one: false,
two: false
};
// Task
var task = new Ext.util.DelayedTask(function(){
// Check for success
if (success.one && success.two) {
// Callback
doCallback();
} else {
task.delay(500);
}
});
task.delay(500);
// First
Ext.Ajax.request({
...
success: function() {
success.one = true;
}
...
});
// Second
Ext.Ajax.request({
...
success: function() {
success.two = true;
}
...
});
The task acts like a thread and will check on the status of the requests and sleep for every 500ms until they both complete.
Old question, but well, as I stumbled upon it...
I'd use the excellent async library by Caolan, particularly here you'll want to use async.parallel.
The examples written on the GitHub doc are worth a read.
https://github.com/caolan/async#parallel
Share an integer variable that each callback checks:
// count variable
var numReturns = 0;
// same call back used for each Ajax request:
function callback() {
numReturns++;
if (numReturns === 2) {
progress();
}
}
If you need different callbacks, have each callback fire an event which does the same thing.

Categories