Reputation: 1277
I would like to secure my code to make sure several Ajax requests triggered by user actions in a certain order will have their responses handled in the same order.
I want to keep the asynchonous mechanism of Ajax. Just secure my code to avoid out of sequence responses that can lead to mess on my web page.
To make it perfectly clear. Let's take an example:
$('#button1').click(function(){
$.ajax({url: 'dosomething1.html'})
.done( function(){ console.log('something 1 success'); } )
.fail( function(){ console.log('something 1 failure'); } );
});
$('#button2').click(function(){
$.ajax({url: 'dosomething2.html'})
.done( function(){ console.log('something 2 success'); } )
.fail( function(){ console.log('something 2 failure'); } );
});
$('#button3').click(function(){
$.ajax({url: 'dosomething3.html'})
.done( function(){ console.log('something 3 success'); } )
.fail( function(){ console.log('something 3 failure'); } );
});
If the user clicks on "#button1" then "#button2" and then "button3", I want to see in the console:
>something 1 success
>something 2 success
>something 3 success
It can happen that the responses are not received in the order the server sent them. So I want to get prepared for this scenario.
Note: I can't know in advance the sequence of events triggered by the user. So I need to find a way to chain the response handlers "on the fly".
What would be the best solution to achieve this?
I'm new to Ajax tricky stuff and I read a large amount of things today without finding THE solution (I guess that somehow deferred and promise objects could do the trick). Please help me to get rid of this terrible headache. :)
Edit to comment the solution by Kevin B.
I struggled with my brain to FULLY understand the example from Kevin B (that does work) until I read a book on Deferreds that explains that the "then" function is actually creating a new Deferred and returns its promise.
This is this new promise that is "chained" to the previous one. It calls its successfull of failure callbacks depending on the result of the previous promise evaluation (resolved or rejected).
In our case that means that when the previous promise is evaluated, the "then" promise is also evaluated and takes as an input the result (resolved or rejected) of the previous promise to decide which callback to call. In kevin B's code the ajax request promise is returned in both cases (resolved or rejected). Hence, the .fail and .done callback of the promise are called ONLY ONCE the "then" promise is evaluated AND the returned promise (ajax request one) is "resolved" (.done function) or rejected (.fail function).
To go further: My understanding is that the promise is a kind of listener on an event that can potentially happen in the future.
In classical cases, when the event happens, the deferred is changed to "resolved" or "rejected" state and the promise callbacks are called. The promise is "listening" to the state of the deferred to be changed. The event trigerring this state change is the resolution or rejection of the initial event (ajax request, timeout, other...).
In "then" cases, the trigerring event for evaluating the promise is: the referenced promise (previous promise in the chain) is evaluated (either resolved or rejected). Given the evaluation result, the success or failure callback is called.
I propose this slightly re-organized code inspired by Kevin's code to help out dummies like me to better understand:
var currentPromise = $.Deferred().resolve().promise();
$('#button1').click(function(){
var button1Promise = $.ajax({url: 'dosomething1.html'})
var thenPromise = currentPromise.then(
function () { return button1Promise;},
function () { return button1Promise;}); // to also handle fail conditions gracefully
currentPromise = thenPromise;
// "thenPromise" callback functions are returning the promise linked to
// the ajax request. So this promise is "replacing" the "thenPromise".
// Hence "done" and "fail" functions are finally defined for the ajax request promise.
thenPromise.done( function(){ console.log('something 1 success'); } );
thenPromise.fail( function(){ console.log('something 1 failure'); } );
});
Hopefully it will help people not totally comfortable with jquery concepts to fully understand promises chaining with "then" function.
Don't hesitate to comment if I misundertood something.
Upvotes: 3
Views: 2795
Reputation: 95018
If you create a promise up front, you could keep chaining off of it to get your desired effect.
// ajax mock
function sendRequest(v, delay) {
var def = $.Deferred();
setTimeout(function () {
def.resolve(v);
}, delay);
return def.promise();
}
var ajaxPromise = $.Deferred().resolve().promise();
var delay = 600; // will decrement this with each use to simulate later requests finishing sooner
// think of this as a click event handler
function doAction (btnName) {
delay -= 100;
var promise = sendRequest(btnName, delay);
ajaxPromise = ajaxPromise.then(function () {
return promise;
}).done(function () {
console.log(btnName);
});
}
doAction("1");
doAction("2");
doAction("3");
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
Since i delayed them by 500ms, 400ms, and 300ms, none of them logged until after 500ms.
Here's your code with the same technique:
var ajaxPromise = $.Deferred().resolve().promise();
$('#button1').click(function(){
var promise = $.ajax({url: 'dosomething1.html'})
ajaxPromise = ajaxPromise.then(function () {
return promise;
}, function () {
return promise; // to also handle fail conditions gracefully
}).done( function(){ console.log('something 1 success'); } )
.fail( function(){ console.log('something 1 failure'); } );
});
// repeat for other two buttons
The important thing is all of the ajax requests will always be sent immediately, but the done and fail handlers won't be executed until their turn.
Upvotes: 3
Reputation: 1691
As you say:
I can't know in advance the sequence of events triggered by the user. So I need to find a way to chain the response handlers "on the fly".
The right way to go about this is definitely to use deferred objects / promises and NOT set the async parameter to false, which can cause a lot of unwanted problems.
Read the canonical introduction on how to do it here.
EDIT:
An example of synchronizing parallel tasks with $.when(), taken from here:
var promiseOne, promiseTwo, handleSuccess, handleFailure;
// Promises
promiseOne = $.ajax({ url: '../test.html' });
promiseTwo = $.ajax({ url: '../test.html' });
// Success callbacks
// .done() will only run if the promise is successfully resolved
promiseOne.done(function () {
console.log('PromiseOne Done');
});
promiseTwo.done(function () {
console.log('PromiseTwo Done');
});
// $.when() creates a new promise which will be:
// resolved if both promises inside are resolved
// rejected if one of the promises fails
$.when(
promiseOne,
promiseTwo
)
.done(function () {
console.log('promiseOne and promiseTwo are done');
})
.fail(function () {
console.log('One of our promises failed');
});
Upvotes: 0
Reputation: 1086
I can't know in advance the sequence of events triggered by the user. So I need to find a way to chain the response handlers "on the fly".
You need to pipe ajax requests to have the respective responses in the same order, one way to do that is using this plugin https://code.google.com/p/jquery-ajaxq/
Upvotes: 0
Reputation: 64
The easiest way here will be to use async : false
parameter for $.AJAX(), just to be sure that your requests run one after another.
http://api.jquery.com/jquery.ajax/
Upvotes: -2