Pattern to force events to fire serially with jquery - javascript

I'm making a bunch of ajax calls from the browser to a service and I'd like to be nice to the server by not sending them all at once. Is there a standard pattern for serializing the firing of a list of events so that the next one doesn't fire until the last one finished?

I think jQuery.whenSync() Plugin For Chaining Asynchronous Callbacks Using Deferred Objects can help you queue AJAX calls.

I do this now with one of my apps. Basically I have an array that stores all my ajax call parameters. On the callback I pass the next increment until there are no more. Here's a general example (you could use ajax or get of course, this is just how I do it:
var params = [
{url : 'someurl', data : 'somedata', callback : doSomething},
{url : 'anotherurl', data : 'moredata'}
],
process = function(i) {
if (i < params.length) {
var item = params[i];
// Navigate to first view trainee screen
$.post(
item.url,
item.data,
function(data) {
// Could test for function, etc but this is how I do it
try {
item.callback(data);
} catch (e) {}
process(++i);
}
);
}
};
process(0);

Related

ASP.NET MVC Action on Synchronous AJAX Requests

I am looking for an MVC implementation equivalent in functionality to the WebForms ajax request handlers below:
Sys.WebForms.PageRequestManager.getInstance().add_beginRequest(function());
Sys.WebForms.PageRequestManager.getInstance().add_endRequest(function());
A very similar question (link below) was answered with a reference to the global .ajax event handlers, but as all of my calls are synchronous I am having the issue that it is firing the ajax finished event at the end of each individual ajax request, and I want it to start with the first and end when the final one finishes. I tried using ajaxStart and ajaxStop which the documentation suggests should be exactly what I am looking for, but I belive that these would only work as intended with async ajax requests.
(ASP.NET MVC 4 Action on AJAX Request)
Is there any way to do this using the jQuery built in ajax event handlers, or any alternative method for synchronous requests?
You can use jQuery's .when (in combination with .apply) which allows you to execute a callback function based on any number of Deferred objects (which are returned by jQuery .ajax calls). Check out the fiddle and open up dev tools, if you watch the log you will see the correct order of events. First 'started' is logged, then a number of 'ajax response' messages are logged, and finally the 'complete' message is logged at the end of all requests.
Fiddle: http://jsfiddle.net/efjmcm49/
var ajaxCalls = [];
console.log('started');
for(var i = 1; i <= 5; i++){
var currentCall = $.ajax({
url: '/echo/json',
type: 'GET',
success:function(data){
console.log('ajax response');
}
});
ajaxCalls.push(currentCall);
}
$.when.apply($, ajaxCalls).done(function () {
console.log('complete');
});
In a series of synchronous requests, jQuery cannot know when you are done making all of them. How would it know that you aren't just about to start another one?
If the requests are synchronous can't you just use normal program flow? Like:
ajaxStart(); //execute arbitrary code
while(condition) {
//ajax requests here
}
ajaxStop(); //execute arbitrary code
jquery don't have anything inbuilt like that but you can do one thing, take one counter and
initialize with 0 and in each ajaxstart you can increment by 1 and in success/error of ajax
decrement counter by 1 and as soon as you get value of counter as 0 means all request is over.

Reuse Deferred more than once

I'm using deferred as I need to execute several processes asynchronously.
To be clearer, here is the signification of my treatments :
Treatment1 : call of an ajax service providing user rights
Treatment2 : call of an ajax service providing links and labels.
I need to call these 2 services at the same time and then get the unified response of both services in order to display links depending on rights (my real problem is with a 3rd ajax service but let's talk about with only 2 to simplify).
First, I declare the deferred as global var :
var treatment1 = $.Deferred();
var treatment2 = $.Deferred();
Then, when I need to do the job, I call the resolve method with needed data for using it in the global unique treatment:
when my 1st ajax service responds : treatment1.resolve(responseData1)
when my 2nd ajax service responds : treatment2.resolve(responseData2)
When the treatment1 & 2 are finished, the done event is fired :
$.when(treatment1, treatment2).done(function(responseData1,responseData2) {
DoGlobalTreatmentWithAllResponseData(responseData1,responseData2)
}
My problem is that deferred works only once.
As my website is realized in ajax mainly, I need to fire the event multiple times.
The user can click a button to search for users. Then a list of users is displayed and the ajax services are all called asynchronously. This operation can be repeated infinitely.
I just need a way to reuse the principle of deferred but multiple times. I know that this problem has already been discussed and everyone says deferred can't work this way.
But, is it really not possible to reset the deferred state or reset the promises (even by implementing a custom solution, using AOP or something else)?
If it's impossible, what solution could I use? I don't want to fire treatments one after another but I really want to do a global treatment after all the treatments are finished (that is to say, after the last treatment in activity is finished) and I want to use the responseData of each services.
Here is my sample code that I would like to customize : http://jsfiddle.net/PLce6/14/
I hope to be clear as English is not my native language.
Thank you in advance for your help.
Deferreds can be resolved/rejected only once... However, I think the issue is how you're structuring your code...
As long as you're initializing your deferred each time, there isn't any problem in doing this...
I think the issue is this:
First, i declare the deferred as global var:
var treatment1 =$.Deferred();
var treatment2 = $.Deferred();
Instead, can you try doing this in a function that's invoked in the button click
The user can clic a button to search for users
so have a function like so:
function onClick() {
var treatment1 =$.ajax({url: '/call1'});
var treatment2 = $.ajax({url: '/call2'});
$.when(treatment1, treatment2).done(function(obj1, obj2) {
// do whatever else you need
});
}
Now from the rest of your post, looks like you're trying to reuse the deferreds - but in that case, your original solution should not have a problem with keeping deffereds as global since your done will be called with whatever data they were resolved with.
Can you post some more of your code to help explain what you're trying to do.
Updated from my own comment below for elaboration
based on op's fiddle, he wants to be able to trigger dependent action multiple times. Solution is to have the dependent action create new deferreds and hook up a $.when to itself. See updated fiddle at http://jsfiddle.net/PLce6/15/
// global
var d1 = $.Deferred();
var d2 = $.Deferred();
var d3 = $.Deferred();
// here's the reset
function resetDeferreds() {
d1 = $.Deferred();
d2 = $.Deferred();
d3 = $.Deferred();
$.when(d1, d2, d3).done(
function (responseData1, responseData2, responseData3) {
DoGlobalTreatmentWithAllResponseData(responseData1, responseData2, responseData3);
resetDeferreds();
});
// the onclick handlers
function do3() {
d3.resolve('do3 ');
return d3;
}
// the top level $.when
$.when(d1, d2, d3).done(function (responseData1, responseData2, responseData3) {
DoGlobalTreatmentWithAllResponseData(responseData1, responseData2, responseData3);
resetDeferreds();
});
Perhaps you code is not well designed?
I do not see how that would be an issue. The asynchronous process should be responsible for creating a new Deferred object everytime.
function doSomething() {
var d = $.Deferred();
setTimeout(function () {
d.resolve();
}, 1000);
return d;
}
function doSomethingElse() {
var d = $.Deferred();
setTimeout(function () {
d.resolve();
}, 1000);
return d;
}
Then you can always do the following:
$.when(doSomething(), doSomethingElse()).done(function () {
console.log('done');
});
There's always a solution:
If you absolutely need to be able to call resolve multiple times on the same Deferred, then you should wrap the Deferred into another object, let's say DeferredWrapper, which would expose the same API as a Deferred but would delegate all method calls to the it's encapsulated Deferred.
In addition of delegating the function calls, the DeferredWrapper would have to keep track of all listening operations (e.g. done, always, fail...) that were made on the object. The DeferredWrapper could store all actions as [functionName, arguments] tuples in an internal this._actions property.
Finally, you would need to provide a special implementation for state changing operations (e.g. reject, resolve, resolveWith...etc) that would look like:
Let d be the internal Deferred referenced by this._deferred.
Let fn be the function name of the function being called.
If d.state() is not pending:
3.1 Do d = this._deferred = [[native jQuery Deferred]]
3.2 Apply all actions on d.
Return the result of d[fn].apply(d, arguments)
Note: You would also need to implement a custom promise implementation and make sure it behaves correctly. You can probably use a similar approach like the one described.
I'm going to suggest a small change. One element you weren't clear on is whether or not the treatment1 and treatment2 results are different each time. If they are then do what #raghu and #juan-garcia
function onClick() {
var treatment1 =$.ajax({url: '/call1'});
var treatment2 = $.ajax({url: '/call2'});
$.when(treatment1, treatment2).done(function(obj1, obj2) {
// do whatever else you need
});
}
If they don't change then do this :
var treatment1 =$.ajax({url: '/call1'});
var treatment2 = $.ajax({url: '/call2'});
function onClick() {
$.when(treatment1, treatment2).done(function(obj1, obj2) {
// do whatever else you need
});
}
Or some variation of that. Because once they are complete, your callback function will always execute right away. It's still asynchronous, but it doesn't need to wait since everything is ready to go. This serves both use cases. This is a very common pattern for data that may take a few seconds to load before it's functionally useful when drawing a new component in the page. It's a lazy-load mechanism that's very useful. Once it's in though everything looks as if it's responding instantaneously.
I reworked the javascript in your example on JSFiddle to show just the basics I think you needed to see. That is here. Given your example, I think the mistake is in believing that resolve must be called multiple times to trigger a behavior. Invoking the done behavior cues a one time behavior and each invocation of done loads a new behavior into the queue. Resolve is called one time. $.when().done() you call as many times as you have behaviors dependent on the specific when() condition.

Loop calling an asynchronous function

Introduction to the problem
I need to call an asynchronous function within a loop until a condition is satisfied. This particular function sends a POST request to a website form.php and performs some operations with the response, which is a JSON string representing an object with an id field. So, when that id is null, the outer loop must conclude. The function does something like the following:
function asyncFunction(session) {
(new Request({
url: form.php,
content: "sess=" + session,
onComplete: function (response) {
var response = response.json;
if (response.id) {
doStaff(response.msg);
} else {
// Break loop
}
}
})).get();
}
Note: Although I've found the problem implementing an add-on for Firefox, I think that this is a general javascript question.
Implementing the loop recursively
I've tried implementing the loop by recursivity but it didn't work and I'm not sure that this is the right way.
...
if (response.id) {
doStaff(response.msg);
asyncFunction(session);
} else {
// Break loop
}
...
Using jsdeferred
I also have tried with the jsdeferred library:
Deferred.define(this);
//Instantiate a new deferred object
var deferred = new Deferred();
// Main loop: stops when we receive the exception
Deferred.loop(1000, function() {
asyncFunction(session, deferred);
return deferred;
}).
error(function() {
console.log("Loop finished!");
});
And then calling:
...
if (response.id) {
doStaff(response.msg);
d.call();
} else {
d.fail();
}
...
And I achieve serialization but it started repeating previous calls for every iteration. For example, if it was the third time that it called the asyncFunction, it would call the same function with the corresponding parameters in the iterations 1 and 2.
Your question is not exactly clear, but the basic architecture must be that the completion event handlers for the asynchronous operation must decide whether to try again or to simply return. If the results of the operation warrant another attempt, then the handler should call the parent function. If not, then by simply exiting the cycle will come to an end.
You can't code something like this in JavaScript with anything that looks like a simple "loop" structure, for the very reason that the operations are asynchronous. The results of the operation don't happen in such a way as to allow the looping mechanism to perform a test on the results; the loop may run thousands of iterations before the result is available. To put it another way, you don't "wait" for an asynchronous operation with code. You wait by doing nothing, and allowing the registered event handler to take over when the results are ready.
Thank you guys for your help. This is what I ended doing:
var sess = ...;
Deferred.define(this);
function asyncFunction (session) {
Deferred.next(function() {
var d = new Deferred();
(new Request({
url: form.php,
content: "sess=" + session,
onComplete: function (response) {
d.call(response.json);
}
})).get();
return d;
}).next(function(resp) {
if (resp.id) {
asyncFunction(session);
console.log(resp.msg);
}
});
}
asyncFunction(sess);
Why wouldn't you just use a setInterval loop? In the case of an SDK-based extension, this would look like:
https://builder.addons.mozilla.org/addon/1065247/latest/
The big benefit of promises-like patterns over using timers is that you can do things in parallel, and use much more complicated dependencies for various tasks. A simple loop like this is done just as easily / neatly using setInterval.
If I correctly understand what you want to do, Deferred is a good approach. Here's an example using jQuery which has Deferred functionality built in (jQuery.Deferred)
A timeout is used to simulate an http request. When each timeout is complete (or http request is complete) a random number is returned which is equivalent to the result of your http request.
Based on the result of the request you can decide if you need another http request or want to stop.
Try out the below snippet. Include the jQuery file and then the snippet. It keeps printing values in the console and stops after a zero is reached.
This could take while to understand but useful.
$(function() {
var MAXNUM = 9;
function newAsyncRequest() {
var def = $.Deferred(function(defObject) {
setTimeout(function() {
defObject.resolve(Math.floor(Math.random() * (MAXNUM+1)));
}, 1000);
});
def.done(function(val) {
if (val !== 0)
newAsyncRequest();
console.log(val);
});
};
newAsyncRequest();
});
Update after suggestion from #canuckistani
#canuckistani is correct in his answer. For this problem the solution is simpler. Without using Deferred the above code snippet becomes the following. Sorry I led you to a tougher solution.
$(function() {
var MAXNUM = 9;
function newAsyncRequest() {
setTimeout(function() {
var val = Math.floor(Math.random() * (MAXNUM+1));
if (val !== 0)
newAsyncRequest();
console.log(val);
}, 1000);
}
newAsyncRequest();
});

Waiting on multiple asynchronous calls to complete before continuing

So, I have a page that loads and through jquery.get makes several requests to populate drop downs with their values.
$(function() {
LoadCategories($('#Category'));
LoadPositions($('#Position'));
LoadDepartments($('#Department'));
LoadContact();
};
It then calls LoadContact(); Which does another call, and when it returns it populates all the fields on the form. The problem is that often, the dropdowns aren't all populated, and thus, it can't set them to the correct value.
What I need to be able to do, is somehow have LoadContact only execute once the other methods are complete and callbacks done executing.
But, I don't want to have to put a bunch of flags in the end of the drop down population callbacks, that I then check, and have to have a recursive setTimeout call checking, prior to calling LoadContact();
Is there something in jQuery that allows me to say, "Execute this, when all of these are done."?
More Info
I am thinking something along these lines
$().executeAfter(
function () { // When these are done
LoadCategories($('#Category'));
LoadPositions($('#Position'));
LoadDepartments($('#Department'));
},
LoadContact // Do this
);
...it would need to keep track of the ajax calls that happen during the execution of the methods, and when they are all complete, call LoadContact;
If I knew how to intercept ajax that are being made in that function, I could probably write a jQuery extension to do this.
My Solution
;(function($) {
$.fn.executeAfter = function(methods, callback) {
var stack = [];
var trackAjaxSend = function(event, XMLHttpRequest, ajaxOptions) {
var url = ajaxOptions.url;
stack.push(url);
}
var trackAjaxComplete = function(event, XMLHttpRequest, ajaxOptions) {
var url = ajaxOptions.url;
var index = jQuery.inArray(url, stack);
if (index >= 0) {
stack.splice(index, 1);
}
if (stack.length == 0) {
callback();
$this.unbind("ajaxComplete");
}
}
var $this = $(this);
$this.ajaxSend(trackAjaxSend)
$this.ajaxComplete(trackAjaxComplete)
methods();
$this.unbind("ajaxSend");
};
})(jQuery);
This binds to the ajaxSend event while the methods are being called and keeps a list of urls (need a better unique id though) that are called. It then unbinds from ajaxSend so only the requests we care about are tracked. It also binds to ajaxComplete and removes items from the stack as they return. When the stack reaches zero, it executes our callback, and unbinds the ajaxComplete event.
You can use .ajaxStop() like this:
$(function() {
$(document).ajaxStop(function() {
$(this).unbind("ajaxStop"); //prevent running again when other calls finish
LoadContact();
});
LoadCategories($('#Category'));
LoadPositions($('#Position'));
LoadDepartments($('#Department'));
});
This will run when all current requests are finished then unbind itself so it doesn't run if future ajax calls in the page execute. Also, make sure to put it before your ajax calls, so it gets bound early enough, it's more important with .ajaxStart(), but best practice to do it with both.
Expanding on Tom Lianza's answer, $.when() is now a much better way to accomplish this than using .ajaxStop().
The only caveat is that you need to be sure the asynchronous methods you need to wait on return a Deferred object. Luckily jQuery ajax calls already do this by default. So to implement the scenario from the question, the methods that need to be waited on would look something like this:
function LoadCategories(argument){
var deferred = $.ajax({
// ajax setup
}).then(function(response){
// optional callback to handle this response
});
return deferred;
}
Then to call LoadContact() after all three ajax calls have returned and optionally executed their own individual callbacks:
// setting variables to emphasize that the functions must return deferred objects
var deferred1 = LoadCategories($('#Category'));
var deferred2 = LoadPositions($('#Position'));
var deferred3 = LoadDepartments($('#Department'));
$.when(deferred1, deferred2, deferred3).then(LoadContact);
If you're on Jquery 1.5 or later, I suspect the Deferred object is your best bet:
http://api.jquery.com/category/deferred-object/
The helper method, when, is also quite nice:
http://api.jquery.com/jQuery.when/
But, I don't want to have to put a bunch of flags in the end of the drop down population callbacks, that I then check, and have to have a recursive setTimeout call checking, prior to calling LoadContact();
No need for setTimeout. You just check in each callback that all three lists are populated (or better setup a counter, increase it in each callback and wait till it's equal to 3) and then call LoadContact from callback. Seems pretty easy to me.
ajaxStop approach might work to, I'm just not very familiar with it.

Force Javascript function call to wait until previous one is finished

I have a simple Javascript function:
makeRequest();
It does a bunch of stuff and places a bunch of content into the DOM.
I make a few calls like so:
makeRequest('food');
makeRequest('shopping');
However, they both fire so quickly that they are stepping on each other's toes. Ultimately I need it to have the functionality of.
makeRequest('food');
wait....
makeRequest('shopping'); only if makeRequest('food') has finished
Thoughts on getting these to execute only one at a time?
Thanks!
If these functions actually do an AJAX request, you are better keeping them asynchronous. You can make a synchronous AJAX request but it will stop the browser from responding and lead to bad user experience.
If what you require if that these AJAX requests are made one after the other because they depend on each other, you should investigate your function to see if it provides a callback mechanism.
makeRequest('food', function()
{
// called when food request is done
makeRequest('shopping');
});
Using jQuery, it looks something like that
$.get("/food", function(food)
{
// do something with food
$.get("/shopping", function(shopping)
{
// do something with shopping
});
});
I would recommend that you simply write them asynchronously--for example, call makeRequest('shopping'); from the AJAX completion handler of the first call.
If you do not want to write your code asynchronously, see Javascript Strands
I suppose that you have a callback method that takes care of the response for the request? Once it has done that, let it make the next request.
Declare an array for the queue, and a flag to keep track of the status:
var queue = [], requestRunning = false;
In the makeRequest method:
if (requestRunning) {
queue.push(requestParameter);
} else {
requestRunning = true;
// do the request
}
In the callback method, after taking care of the response:
if (queue.length > 0) {
var requestParameter = queue.splice(0,1)[0];
// do the request
} else {
requestRunning = false;
}

Categories