Canceling an ajax request: can the handler still be executed - javascript

In Is JavaScript guaranteed to be single-threaded?
it becomes clear that however javascript is single threaded there are still caveats.
I was wondering whether the following pseudo-code is always predictable (I'm 'using' jQuery)
var lastReq;
$('#button').click(function()
{
if (lastReq) lastReq.abort();
lastReq = $.ajax(...);
});
The case could be that between the click event and the abort, the data from the server came through and put an event on de eventqueue. If this happens just before the abort, the succes event of the ajax post would be triggered.
I have no idea how to really test this possible race condition.
Does anyone have an idea how this works? Or how to test this with a prepped example?

I don't know if this workaround could be really useful and be bullet-proof (I'm trying to be creative) but, since jQuery 1.5 ajax methods return a deferred object lastReq have state() method available
From http://api.jquery.com/deferred.state/
The deferred.state() method returns a string representing the current state of the Deferred object. The Deferred object can be in one of three states:
...
"resolved": The Deferred object is in the resolved state, meaning that either deferred.resolve() or deferred.resolveWith() has been called for the object and the doneCallbacks have been called (or are in the process of being called).
so you could refactor your code like so
var lastReq;
$('#button').click(function() {
if (lastReq) {
if (lastReq.state() === "resolved") {
return false; /* done() of the previous ajax call is in the process of
being called. Do nothing and wait until the state
is resolved */
}
else {
lastReq.abort();
}
}
lastReq = $.ajax(url).done(function() {
/* do something */
lastReq = null;
});
});
hope this could help to give you an idea to work on, but I suspect there's no really need of this kind of workaround

Edit:
As soon as you abort the request, the browser shouldn't be listening for it any longer. So when it reaches the response in the event queue, my guess is it should just throw it out.
However, if you're having problems with it, you could try the following:
Could you do a check in your success function + url to cancel itself if it detects it no longer needs to be run?
For instance (and I'm not saying you should do it this way, I haven't tried attaching anything to the jqXHR request):
var numReq = 0, lastReq;
$('#button').on('click', function(e) {
if (lastReq) { lastReq.abort(); }
numReq ++;
lastReq = $.ajax({
success : function(d, s, x) {
if (x.reqNum < numReq) { return; }
}
});
lastReq.reqNum = numReq;
});
My understanding is the ajax event won't be added to the event queue until after the button click is done, so you shouldn't (theoretically) have to worry about setting the reqNum after the ajax ...
I've also tried the following code:
var numReq = 0, lastReq, timer = 100,
c = setInterval(function() {
if (lastReq) { lastReq.abort(); }
numReq ++;
lastReq = $.ajax({
url : 'index.html',
cache : false,
success : function(d, s, x) {
if (x.reqNum < numReq) { console.log('it happens'); }
}
});
lastReq.reqNum = numReq;
}, timer);
Varying the timer to try and match (as close as possible) the load time of the page. I haven't had "it happens" show up.

Related

Force Chrome to check if a $.ajax call has finished?

Good afternoon guys -
Is there a well known way to check if a $.ajax call has finished?
-- FOR INSTANCE --
Let's say I'm using a $.ajax call to load in a large number of leaflet polygons from a .geojson file (asynchronously as a layer group). Under normal circumstances, this happens almost immediately - but the user has elected to load a large job this time around. The user has assumed that the set of polygons has been loaded and attempts to do something with this group of layers prematurely - only to find that nothing happens (the layer group doesn't actually exist yet in the browser).
My intuition (I'm new to web development) is to have some sort of global flag that's set for the dependent algorithm to check. We would set the flag prior to loading the layers, and then change it to some other value in the .done({}) section of the $.ajax call.
-- UPDATE --
I've reworked the code to allow users to choose whether or not they wish to retry the request, and also force the browser to wait some time before retrying the request.
However, I've also found the the issue seems to be with Chrome. Firefox appears to be able to handle the $.ajax.always callback as soon as it finishes (in other words, the $.ajax.always callback will interrupt the regular flow of javascript).
Chrome appears to block the $.ajax.always callback and only lets it execute after all other javascript has finished running (no interrupt).
This particular question stemmed from a debug case that automated user input from within a loop - and since Chrome doesn't call the $.ajax.always callback until the current process is complete, the process isn't marked as completed.
Example Code:
procBox = []; // Global scope, stands for Process Box
function loadPolygons() {
procBox["loadPolygons"] = "running";
$.ajax({
// ajax things here
}).done(function() {
procBox["loadPolygons"] = "done";
}).fail(function() {
// failure stuff here
});
}
function dependentFunction() {
if procBox["loadPolygons"] === "done") {
// The meat of the function (dependentFunction things here)
} else {
// Backup case that allows the browser to retry the request
/* --
* If this fires, the server is still trying to process the
* ajax request. The user will be prompted to retry the request,
* and upon agreement, the function will be called again after
* 1 second passes.
*/
var msg = "Oops! It looks like we're still fetching your "
+ "polygons from the server. Press OK to retry.";
if (confirm(msg)) {
setTimeout(dependentFunction, 1000);
}
}
}
This approach seems to work well in Firefox - the alert() stops JavaScript execution and gives it a chance for the .done({}) callback to occur. But for some reason, the while loop never allows the .done({}) callback to complete in Chrome!
Does anyone know of a better approach for this than using flags or async: false?
I appreciate any answers and knowledge out there!
There are numerous ways to do :
as already sugegsted you can use
since you use jQuery, you can use custom events https://learn.jquery.com/events/introduction-to-custom-events/:
$(document).on("myajax:done", function(){ dependentFunction();})
$.ajax({...}).done(function(){
$(document).trigger("myajax:done")
});
or even global ajax events
https://api.jquery.com/category/ajax/global-ajax-event-handlers/
but really consider why not to do something like
procBox = {onLoadPolygons:dependentFunction}; // Global scope
function loadPolygons() {
$.ajax({
// ajax things here
}).done(function() {
procBox["onLoadPolygons"]();
}).fail(function() {
// failure stuff here
});
}
function dependentFunction() {
alert("Please wait for the polygons to load");
dependentFunctionThings();
}
function dependentFunctionThings(){
// Do dependent function things...
}
UPD:
if you ensist on your structure, and still want to use blocking function
use setInterval to perform check
function dependentFunction() {
var h = setInterval(function() {
if (procBox["loadPolygons"] == "done") {
clearInterval(h);
// Do dependent function things...
}
}, 100);
}
Or wrap it up into a Promise (http://caniuse.com/#feat=promises)
function dependentFunction() {
(new Promise(function(resolve) {
var h = setInterval(function(){
if (procBox["loadPolygons"] == "done") {
clearInterval(h);resolve();
}
}, 100);
})).then(function(){
// Do dependent function things...
});
}
But I still believe that something wrong in your structure
From the docs :
.ajaxComplete()
Whenever an Ajax request completes, jQuery triggers the ajaxComplete event. Any and all handlers that have been registered with the .ajaxComplete() method are executed at this time.
http://api.jquery.com/ajaxcomplete/

How to use params from within a success callback function (Backbone)

I seem to have an issue with the parameters I am trying to set from within the success callback function:
var CampModel = CampDataModel.extend({
initialize : function(){
this.fetchActiveAndPending();
console.log(this.get('active'));
},
//Counts active and pending campaigns for front page.
CountActiveAndPending : function(data){
var active = 0;
var pending = 0;
$.each(data.returnValue,function(index,val){
if (val.ApprovedOnSite){
active++;
}
else
pending++;
});
this.set('active',active);
this.set('pending',pending);
},
//fetches data from server using campModel.
fetchActiveAndPending : function(){
console.log('fetching!');
that = this;
this.fetch({
success:function(model,response){
that.CountActiveAndPending(response);
}
});
}
});
return CampModel;
});
the result of this.get('active') is always the default number. If I try and use this.get('active') from within the success callback function it gives the right result. Is it possible to set a var from within the callback func and call it from outside, let's say the initialize function?
It's not a problem of closures (meaning that your variable isn't accessible from your callback function or something weird like that), it's a problem of execution timing. Your success callback will be executed asynchronously when the client gets the response from the server. The only way to be sure that the response has arrived is to use the listeners (http://backbonejs.org/#Events) or the callbacks (as your success function). If you make sure that a part of your code is executed after the response was received, you'll have the right value for your active parameter.
Here when you do:
console.log(this.get('active'));
The request is still pending, therefore active is still equal to -1. So your problem is still that you're not considering the asynchronous side of your code.
I agree with #Loamhoof, you have a timing issue, one solution is:
initialize : function(){
this.fetchActiveAndPending(function() {
console.log(this.get('active'));
});
},
CountActiveAndPending : function(data){
...
},
fetchActiveAndPending : function(completeFn){
console.log('fetching!');
var _this = this;
this.fetch({
success:function(model,response){
_this.CountActiveAndPending(response);
completeFn();
}
});
}
p.s. Thanks to #Loamhoof for challenging my previous assumptions and providing an example.

Javascript - pause execution until flag becomes true

how can i pause a javascript execution until a flag becomes true?
For Example, i've a xml message like this:
[...]
<action>
<resource update>id</resourceupdate>
</action>
<action>
<event>id1</event>
</action>
<action>
<event>id2</event>
</action>
<action>
<event>id3</event>
</action>
[...]
I wish that the event nodes are processed only after processing node resourceupdate (which requires more time to be served, as it requires the loading of a page):
in javascript to process this message with an iterator (each) i've tried:
$(_response).find('ACTION').each(function() {
if (tagName=="RESOURCEUPDATE") {
ready = false;
//load the resource with selected id in an iframe
} else if (tagName=="EVENT") {
browserLoaded(); //the waiting function
eventhandler(); //consume the event
}
});
the waiting function is:
function browserLoaded() {
if (!ready) {
setTimeout(browserLoaded(),1000);
}
}
and the ready var becomes true when the iframe is loaded:
$(iframe).load(function() {
ready = true;
});
but when execute i'll catch this error:
Maximum call stack size exceeded error
any ideas?
thanks!
It's really a bad idea to use some kind of a flag. You have to use Deferred Pattern. Something like this:
var resources = [];
$(_response).find('ACTION').each(function() {
var deferred = resources.length > 0 ? resources[resources.length - 1] : null;
switch (tagName) {
case "RESOURCEUPDATE":
deferred = $.Deferred();
//load the resource with selected id in an iframe
$(iframe).bind('load', function () {
deferred.resolve(/*specific arg1, arg2, arg3, ...*/)
});
resources.push(deferred);
break;
case "EVENT":
if (deferred) {
deferred.done(function (/*specific arg1, arg2, arg3, ...*/) {
// process event node
});
}
break;
}
});
// clean up deferreds objects when all them will be processed
$.when.apply($, resources).then(function() {
resources.length = 0;
})
P.S.: http://api.jquery.com/category/deferred-object/
The problem is in this function which call itself until the stack is full:
function browserLoaded() {
if (!ready) {
//here you call browserLoaded function instead of passing a reference to the function
setTimeout(browserLoaded() ,1000);
}
}
Your function should look like this:
function browserLoaded() {
if (!ready) {
// note the missing "()"
setTimeout(browserLoaded, 1000);
}
}
This is a terrible design. You don't need the 'waiting' timeout mechanism. If you are loading the pages via jQuery ajax request, make use of the callback functions to continue with your code execution (you can perhaps keep track of the 'current' item being processed and continue with the next). If you are loading iFrames, that's bad design too, you should move to the jQuery ajax way.
One quick hack that you could do is just set up a polling loop: use setInterval to check every once in a while if the variable has been set and clearInterval and continue execution when its time.
Any way, its going to be a pain to do things. Essentially, the only way to tell something in Javascript to run latter is to package it inside a function. After you do this it gets easier though, since you can pass that function around and have the async code call it back when you are done.
For example, your processing might look something like this:
//this is a simple "semaphore" pattern:
var things_to_load_count = 1
var check_if_done(){
things_to_load_count--;
if(things_to_load_count <= 0){
//code to do stuff after you finish loading
}
};
$(_response).find('ACTION').each(function() {
if (tagName=="RESOURCEUPDATE") {
things_to_load_count++;
run_code_to_load_stuff( check_if_done )
//make sure that the run_code_to_load_stuff function
//calls the callback you passed it once its done.
} else if (tagName=="EVENT") {
//process one of the normal nodes
}
});
//this will run the remaining code if we loaded everything
//but will do nothing if we are still waiting.
check_if_done()
Are you sure that the ready variable that set true in the iframe load function is the same as the one that is checked before another settimeout is called. It seems that the one in the iframe load function is a local variable and the other one a global variable.
Or both ready variables are local.

Better way to detect when a variable != undefined when async request is sent

Given the following:
var doThings = (function ($, window, document) {
var someScopedVariable = undefined,
methods,
_status;
methods = {
init: function () {
_status.getStatus.call(this);
// Do something with the 'someScopedVariable'
}
};
// Local method
_status = {
getStatus: function () {
// Runs a webservice call to populate the 'someScopedVariable'
if (someScopedVariable === undefined) {
_status.setStatus.call(this);
}
return someScopedVariable;
},
setStatus: function () {
$.ajax({
url: "someWebservice",
success: function(results){
someScopedVariable = results;
}
});
}
};
return methods;
} (jQuery, window, document));
The issue is clear, this is an async situation were I would like to wait until someScopedVariable is not undefined, then continue.
I thought of using jQuery's .when() -> .done() deferred call but I cant seem to get it to work. I've also thought of doing a loop that would just check to see if its defined yet but that doesnt seem elegant.
Possible option 1:
$.when(_status.getStatus.call(this)).done(function () {
return someScopedVariable;
});
Possible option 2 (Terrible option):
_status.getStatus.call(this)
var i = 0;
do {
i++;
} while (formStatusObject !== undefined);
return formStatusObject;
UPDATE:
I believe I stripped out too much of the logic in order to explain it so I added back in some. The goal of this was to create an accessor to this data.
I would suggest to wait for the complete / success event of an ajax call.
methods = {
init: function () {
_status.getStatus.call(this);
},
continueInit: function( data ) {
// populate 'someScopedVariable' from data and continue init
}
};
_status = {
getStatus: function () {
$.post('webservice.url', continueInit );
}
};
You cannot block using an infite loop to wait for the async request to finish since your JavaScript is most likely running in a single thread. The JavaScript engine will wait for your script to finish before it tries to call the async callback that would change the variable you are watching in the loop. Hence, a deadlock occurrs.
The only way to go is using callback functions throughout, as in your second option.
I agree with the other answer about using a callback if possible. If for some reason you need to block and wait for a response, don't use the looping approach, that's about the worst possible way to do that. The most straightforward would be use set async:false in your ajax call.
See http://api.jquery.com/jQuery.ajax/
async - Boolean Default: true By default,
all requests are sent asynchronously
(i.e. this is set to true by default).
If you need synchronous requests, set
this option to false. Cross-domain
requests and dataType: "jsonp"
requests do not support synchronous
operation. Note that synchronous
requests may temporarily lock the
browser, disabling any actions while
the request is active.

Join 2 'threads' in javascript

If I have an ajax call off fetching (with a callback) and then some other code running in the meantime. How can I have a third function that will be called when both of the first 2 are done. I'm sure it is easy with polling (setTimeout and then check some variables) but I'd rather a callback.
Is it possible?
You could just give the same callback to both your AJAX call and your other code running in the meantime, use a variable to track their combined progress, then link them to a callback like below:
// Each time you start a call, increment this by one
var counter = 0;
var callback = function() {
counter--;
if (counter == 0) {
// Execute code you wanted to do once both threads are finished.
}
}
Daniel's solution is the proper one. I took it and added some extra code so you don't have to think too much ;)
function createNotifier() {
var counter = 2;
return function() {
if (--counter == 0) {
// do stuff
}
};
}
var notify = createNotifier();
var later = function() {
var done = false;
// do stuff and set done to true if you're done
if (done) {
notify();
}
};
function doAjaxCall(notify) {
var ajaxCallback = function() {
// Respond to the AJAX callback here
// Notify that the Ajax callback is done
notify();
};
// Here you perform the AJAX call action
}
setInterval(later, 200);
doAjaxCall(notify);
The best approach to this is to take advantage of the fact that functions are first-order objects in JavaScript. Therefore you can assign them to variables and invoke them through the variable, changing the function that the variable refers to as needed.
For example:
function firstCallback() {
// the first thing has happened
// so when the next thing happens, we want to do stuff
callback = secondCallback;
}
function secondCallback() {
// do stuff now both things have happened
}
var callback = firstCallback;
If both your pieces of code now use the variable to call the function:
callback();
then whichever one executes first will call the firstCallback, which changes the variable to point to the secondCallback, and so that will be called by whichever executes second.
However your phrasing of the question implies that this may all be unnecessary, as it sounds like you are making an Ajax request and then continuing processing. As JavaScript interpreters are single-threaded, the Ajax callback will never be executed until the main body of code that made the request has finished executing anyway, even if that is long after the response has been received.
In case that isn't your situation, I've created a working example on my site; view the source to see the code (just before the </body> tag). It makes a request which is delayed by the server for a couple of seconds, then a request which receives an immediate response. The second request's response is handled by one function, and the first request's response is later handled by a different function, as the request that received a response first has changed the callback variable to refer to the second function.
You are talking about a thing called deferred in javascript as #Chris Conway mentioned above. Similarly jQuery also has Deferred since v1.5.
Check these Deferred.when() or deferred.done()
Don't forget to check jQuery doc.
But to give you some idea here is what I am copying from that site.
$.when($.ajax("/page1.php"), $.ajax("/page2.php")).done(function(a1, a2){
/* a1 and a2 are arguments resolved for the
page1 and page2 ajax requests, respectively */
var jqXHR = a1[2]; /* arguments are [ "success", statusText, jqXHR ] */
if ( /Whip It/.test(jqXHR.responseText) ) {
alert("First page has 'Whip It' somewhere.");
}
});
//Using deferred.then()
$.when($.ajax("/page1.php"), $.ajax("/page2.php"))
.then(myFunc, myFailure);
Something like this (schematic):
registerThread() {
counter++;
}
unregisterThread() {
if (--counter == 0) fireEvent('some_user_event');
}
eventHandler_for_some_user_event() {
do_stuff();
}
You can do this easily with Google's Closure library, specifically goog.async.Deferred:
// Deferred is a container for an incomplete computation.
var ajaxFinished = goog.async.Deferred();
// ajaxCall is the asynchronous function we're calling.
ajaxCall( //args...,
function() { // callback
// Process the results...
ajaxFinished.callback(); // Signal completion
}
);
// Do other stuff...
// Wait for the callback completion before proceeding
goog.async.when(ajaxFinished, function() {
// Do the rest of the stuff...
});
You can join multiple asynchronous computations using awaitDeferred, chainDeferred, or goog.async.DeferredList.

Categories

Resources