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.
Related
Wondering what the best solution to this problem is, also this is not my actual code structure or names but the simplest way to illustrate the problem.
I have a function which was purely used to perform an ajax call and load a template with jquery.
function load(template) {
$('#container').load(template, data, function() {
// complete code here
});
}
Focusing on the 3rd param in $.load(), namely a callback function that runs when the request is complete.
Now I have my load() function in another wrapper function:
function processTask(variable) {
load(variable);
}
The problem I have is I need some code to run after the ajax load is complete, however as my app has grown my wrapper function processTask may or may not invoke an ajax load so I can't perform my must needed code inside the complete callback.
Do I change my $.load() to perform synchronous or just manage my code better so that if I am calling a $.load() it puts my needed code in the callback and if not it places it where I need it to be?
I have read about javascript Promises and I'm unsure if they will help in this situation.
EDIT
So my processTask is an object method.
function classObj(name, fn) {
this.name = name;
this.processTask = fn;
this.load = function(template) {
$('#container').load(template, data, function() {
// complete code here
});
}
}
And in context I do this:
var task = new classObj('taskName', function() {
this.load('myFile.php');
// Or another function and not load() based on whats needed in the task.
});
Basically I have an object that I can add custom methods to at will and they can easily be called dynamically, until now they have always loaded a file.
First, change your load function to return the xhr from get (or ajax):
function load(template) {
return $.get('myFile.php', data, function(result) {
$('#container').html(result);
});
}
Then, within your code you can use when then to perform your code after the load completes if applicable:
var xhr;
/* ... */
if(something){
xhr = load(template);
}
/* ... */
if(xhr){
$.when(xhr).then(doSomething);
} else {
doSomething();
}
And in fact, this can be simplified using the fact that a non-deferred object passed to when (including undefined apparently) will execute the then immediately and get rid of the if:
$.when(xhr).then(doSomething);
If xhr is undefined then when will resolve immediately causing then to execute immediately.
So far in my tests written in CasperJS, I've been using waitForSelector() on page-specific elements to determine if a page has fully loaded (including all the async ajax requests). I was hoping to come up with a more standard way of waiting for page load and was wondering if the following was possible?
Inject as clientscript the following (include.js)
$(document).ajaxStop(function() {
// Do something
})
Description of ajaxStop according to jquery api: Register a handler to be called when all Ajax requests have completed.
Define a casper.waitForLoad function that when called would wait for the "something" in above code block
Use the function in several parts of the test.
Also any tips on the // Do Something part would also be appreciated :) I was thinking about using the window.callPhantom feature in phantomJS but I'm reading that it's not officially supported in casperjs.
I would do something like this in include.js:
(function(){
window._allAjaxRequestsHaveStopped = false;
var interval;
$(document).ajaxStop(function() {
if (interval) {
clearInterval(interval);
interval = null;
}
interval = setTimeout(function(){
window._allAjaxRequestsHaveStopped = true;
}, 500);
});
$(document).ajaxStart(function() {
window._allAjaxRequestsHaveStopped = false;
if (interval) {
clearInterval(interval);
interval = null;
}
});
})();
This sets a (hopefully unique) variable to the window object that can be later retrieved. This also waits a little longer incase there is another request after the previous batch ended.
In CasperJS you would probably do something like the following to wait for the change in the request status. This uses adds a new function to the casper object and uses casper.waitFor() internally to check the change.
casper.waitForAjaxStop = function(then, onTimeout, timeout){
return this.waitFor(function(){
return this.evaluate(function(){
return window._allAjaxRequestsHaveStopped;
});
}, then, onTimeout, timeout);
};
And use it like this:
casper.start(url).waitForAjaxStop().then(function(){
// do something
}).run();
or this:
casper.start(url).thenClick(selector).waitForAjaxStop().then(function(){
// do something
}).run();
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.
var blah = Some.Thing(data, function(a,b) {
// code here
});
Some.Thing = function(data, callback) {
if(...) {
var a = Other.Thing(data, function() {
// code here
callback();
return;
});
}
callback();
};
My question is, will the part that says //code here fire only after everything else and their callbacks fire?
The //code here part seems to fire, and there seems to be some timing issue.
You're not actually using callback anywhere in Some.Thing, so it's impossible to say. But yes, generally, unless something actually calls callback, the code within it is not executed. It is evaluated (parsed), but not executed.
That is impossible to tell from the code you supplied.
The method callback can either be called while on the same stack, or its execution might be deferred due to ajax or setTimeout being used (asynchronous).
If being deferred, then it would be called only after the main method has completed and the thread going back to idle.
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.