I'm using the Asynchronous each() function plugin code posted at:
jQuery Tips and Tricks
It works well but I can't seem to exit a running callback function using return; or return true;. the consquence is that when the condition to "exit" is met, it stops all remaining $.forEach "loop" execution.
Since the plugin implements a setTimeout loop approach, perhaps I just need to know how to exit an already running setTimeout callback function? BTW - I'm avoiding using for loops or $.each() to avoid browser lockup while big json processing occurs. "backgrounding" the loop significantly improves performance of the UI.
$.forEach(json, 1000, function(idx,item) {
if(some_condition) return true; //exit this timeout iteration
//otherwise do something
});
jQuery.forEach = function (in_array, in_pause_ms, in_callback)
{
if (!in_array.length) return; // make sure array was sent
var i = 0; // starting index
bgEach(); // call the function
function bgEach()
{
if (in_callback.call(in_array[i], i, in_array[i]) !== false)
{
i++; // move to next item
if (i < in_array.length) setTimeout(bgEach, in_pause_ms);
}
}
return in_array; // returns array
};
jQuery.fn.forEach = function (in_callback, in_optional_pause_ms)
{
if (!in_optional_pause_ms) in_optional_pause_ms = 10; // default
return jQuery.forEach(this, in_optional_pause_ms, in_callback); // run it
};
Thanks much!
From the docs:
We can break the $.each() loop at a particular iteration by making the
callback function return false.
EDIT: This also applies to your custom forEach.
Related
I want the function in my FOR loop to finish running before going on to the next code but I just can't get it to work.
It should fade in fade out the text "Result" before popping the alert window 'Lucky you' but I would see the alert window loop first before seeing the text. I don't know what I'm doing wrong.
HTML:
<div id="quotes" style="display: none;">Result</div>
Javascript:
function showNextQuote() {
$("#quotes").fadeIn(2000).delay(2000).fadeOut(2000);
}
for (i = 1; i < 4; i++){
showNextQuote(); alert("Lucky you");
};
Since these operations are asynchronous you can't block processing while they're running. Instead, you pass them a function to execute when they complete (called a callback). So chaining them like this would look more like a recursive function than a loop.
(Though this isn't really technically recursion since the asynchronous operations aren't building up a stack. It's semantically/logically very similar to tail recursion though, at least in function structure.)
Something like this:
function showNextQuote(iteration) {
// terminating condition
if (iteration > 4) {
return;
}
// recurse
$("#quotes").fadeIn(2000).delay(2000).fadeOut(2000, function () {
alert("Lucky you");
showNextQuote(iteration + 1);
});
}
showNextQuote(1);
What this does is define the function, which internally passes a reference to itself as a callback to fadeOut. Each callback increments the iteration value by 1. Then after defining the function it's invoked with an initial iteration value of 1.
As said in comments, JavaScript can't do this in a loop, since fadeIn, delay and fadeOut are all asynchronous.
Something like this should work:
function showNextQuote(n) {
if (n > 0) {
$("#quotes").fadeIn(2000).delay(2000).fadeOut(2000, function() {
alert("Lucky you");
showNextQuote(n - 1);
});
}
}
showNextQuote(4);
I am trying to implement some flow control of async ajax calls. My project receives basically an array of values then completes an ajax call for each of them, process the results then run a function when complete.
I have a scoping issue in that the flow control function when initiated a 2nd time with a different array of work to complete its job reference value is overwritten. I have rewritten just the part I have trouble with eliminating all the other functions and posted it on jsfiddle.
http://jsfiddle.net/qanx9/
When the code is initiated the jobRunner function will console log the key to the globalObj where work is found. On the 3rd run of the function job2 is lost and everything is now referring to job1. I have tried putting everything inside this function in an anonymous function but that made no difference.
This is my first attempt at flow control and my project is actually in nodejs. There are libraries that do this for me but I am trying to get a good understanding on how this works.
// global used to track async flow control and store completed work
globalObj = {
job1: {
work: [0,1,2,3,4,5,6,7,8,10],
results: []
},
job2: {
work: [11,12,13,14,15,16,17,18,19,20],
results: []
},
async: {
limit: 5,
running: 0
}
};
// flow control
function jobRunner(job) {
console.log(job)
g = globalObj.async;
j = globalObj[job];
while (g.running < g.limit && j.work.length > 0) {
var task = j.work.shift();
fakeAsyncAjax(task,function(result){
j.results.push(result);
g.running--;
if (j.work.length > 0) {
jobRunner(job);
} else if (g.running == 0) {
jobDone(job);
}
})
g.running++;
}
}
function jobDone(job) {
console.log(job+' complete..');
}
// instead of doing real ajax calls here ive done a basic simulation with a random delay, using setTimeout to make it async.
function fakeAsyncAjax(ref,completeFunc){
setTimeout(function(){
completeFunc(ref);
},Math.floor((Math.random() * 500) + 1))
}
// initate jobs
jobRunner('job1')
jobRunner('job2')
I am a complete noob to Ajax so please forgive me if this is a completely asinine piece of code:
for (var i=0; i<11; i++) {
jQuery('#position').html(i);
var offset = jQuery('#offset').html();
var postcall = 'controller.php?url='+encodeURIComponent(scrapurl)+'&scrape_absolute='+absoluteep+'&scrape_season='+season+'&scrape_show='+showslug+'&scrape_defimg='+encodeURIComponent(defaultimg)+'&offset='+offset;
jQuery.post(postcall,function(data){
jQuery('#offset').html(data);
});
}
The goal here is to execute controller.php with the given values and plug 'offset' back into each call using the returned info. It works but it runs from 0 to 10 instantly and my webserver rejects the subsequent calls.
My goal is to make sure it doesn't call the php again until the last operation has completed.
The key is to make your next AJAX call inside of your callback function. That way, your next post will not occur until the first finishes. In your code, because .post() is non-blocking (asynchronous), it continues the loop immediately, incrementing i/#position and firing off the next .post().
To solve this, encapsulate your .post() in a wrapper function. Have a counter that tracks how many times it has been called. Call the function from the callback of the .post(), and you end up with a recursive function that will do the calls in sequence:
var position=0;
function doNextAJAXPost() {
if(position < 11) {
jQuery('#position').html(position);
position++;
var offset = jQuery('#offset').html();
jQuery.post('controller.php?url='+encodeURIComponent(scrapurl)+'&scrape_absolute='+absoluteep+'&scrape_season='+season+'&scrape_show='+showslug+'&scrape_defimg='+encodeURIComponent(defaultimg)+'&offset='+offset, function(data){
jQuery('#offset').html(data);
doNextAJAXPost();
});
}
}
doNextAJAXPost();
use a self executing recursive function
(function callself(i) {
jQuery('#position').html(i);
var offset = jQuery('#offset').html();
var postcall = 'controller.php?url='+encodeURIComponent(scrapurl)+'&scrape_absolute='+absoluteep+'&scrape_season='+season+'&scrape_show='+showslug+'&scrape_defimg='+encodeURIComponent(defaultimg)+'&offset='+offset;
jQuery.post(postcall,function(data){
jQuery('#offset').html(data);
i++;
if ( i < 11 ) callself(i);
});
})(0)
function animateGraph() {
var graph;
for(i=0; i<10; i++)
{
var start = new Date();
while((new Date()) - start <= 500) {/*wait*/}
document.getElementById("timeMark").innerHTML = phoneX[i].epoch;
}
}
The loop works. The wait works. But the document.getElement is not showing up until the last item in the array...why?
Using setTimeout will allow the code to run and not lock up the page. This will allow it to run the code and will not effect other elements on the page.
var cnt = 0;
(function animateGraph() {
document.getElementById("timeMark").innerHTML = phoneX[cnt].epoch;
cnt++;
if (cnt<10){
window.setTimeout(animateGraph,500);
}
})();
The while loop, waiting for a datetime, is not a good way to wait - it just blocks execution. It keeps the browser (including UI, and its updating) frozen until the script finishes. After that, the window is repainted according to the DOM.
Use window.setTimeout() instead:
function animateGraph(phoneX) {
var el = document.getElementById("timeMark")
var i = 0;
(function nextStep() {
if (i < phoneX.length )
el.innerHTML = phoneX[i].epoch;
i++;
if (i < phoneX.length )
window.setTimeout(nextStep, 500);
})();
}
Please note that this runs asynchronous, i.e. the function animateGraph will return before all phoneXes are shown.
Use setTimeout instead of a while loop.
https://developer.mozilla.org/en/DOM/window.setTimeout
Also try something like this.
Javascript setTimeout function
The following snippet uses a helper function to create the timers. This helper function accepts a loop counter argument i and calls itself at the end of the timer handler for the next iteration.
function animateGraph() {
var graph;
setTimeMarkDelayed(0);
function setTimeMarkDelayed(i) {
setTimeout(function() {
document.getElementById("timeMark").innerHTML = phoneX[i].epoch;
if (i < 10) {
setTimeMarkDelayed(++i);
}
}, 3000);
}
}
You actually need some sort of helper function, otherwise you'll end up overwriting the value of i in your for loop in every iteration and by the time your timers run out, i will already be 9 and all handlers will act on the last element in phoneX. By passing i as an argument to the helper function, the value is stored in the local scope of that function and won't get overwritten.
Or you could use setInterval like Radu suggested, both approaches will work.
If I have a function that's passed this function:
function(work) {
work(10);
work(20);
work(30);
}
(There can be any number of work calls with any number in them.)
work performance some asynchronous activity—say, for this example, it just is a timeout. I have full control over what work does on the completion of this operation (and, in fact, its definition in general).
What's the best way of determining when all the calls to work are done?
My current method increments a counter when work is called and decrements it when it completes, and fires the all work done event when the counter is 0 (this is checked after every decrement). However, I worry that this could be a race condition of some sort. If that is not the case, do show my why and that would be a great answer.
There are a ton of ways you can write this program, but your simple technique of using a counter will work just fine.
The important thing to remember, the reason this will work, is because Javascript executes in a single thread. This is true of all browsers and node.js AFAIK.
Based on the thoughtful comments below, the solution works because the JS event loop will execute the functions in an order like:
function(work)
work(10)
counter++
Start async function
work(20)
counter++
Start async function
work(30)
counter++
Start async function
-- back out to event loop --
Async function completes
counter--
-- back out to event loop --
Async function completes
counter--
-- back out to event loop --
Async function completes
counter--
Counter is 0, so you fire your work done message
-- back out to event loop --
There's no race condition. There is the added requirement for every request made to perform a decrement when it's finished (always! including on http failure, which is easy to forget). But that can be handled in a more encapsulated way by wrapping you calls.
Untested, but this is the gist (I've implemented an object instead of a counter, so theoretically you can extend this to have more granular queries about specific requests):
var ajaxWrapper = (function() {
var id = 0, calls = {};
return {
makeRequest: function() {
$.post.apply($, arguments); // for example
calls[id] = true;
return id++;
},
finishRequest: function(id) {
delete calls[id];
},
isAllDone: function(){
var prop;
for(prop in calls) {
if(calls.hasOwnProperty(prop)) {return false;}
}
return true;
}
};
})();
Usage:
Instead of $.post("url", ... function(){ /*success*/ } ... ); We'll do
var requestId;
requestId = ajaxWrapper.makeRequest("url", ...
function(){ /*success*/ ajaxWrapper.finishRequest(requestId); } ... );
If you wanted to be even more sophisticated you could add the calls to finishRequest yourself inside the wrapper, so usage would be almost entirely transparent:
ajaxWrapper.makeRequest("url", ... function(){ /*success*/ } ... );
I have an after utility function.
var after = function _after(count, f) {
var c = 0, results = [];
return function _callback() {
switch (arguments.length) {
case 0: results.push(null); break;
case 1: results.push(arguments[0]); break;
default: results.push(Array.prototype.slice.call(arguments)); break;
}
if (++c === count) {
f.apply(this, results);
}
};
};
The following code below would just work. Because javascript is single threaded.
function doWork(work) {
work(10);
work(20);
work(30);
}
WorkHandler(doWork);
function WorkHandler(cb) {
var counter = 0,
finish;
cb(function _work(item) {
counter++;
// somethingAsync calls `finish` when it's finished
somethingAsync(item, function _cb() {
finish()
});
});
finish = after(counter, function() {
console.log('work finished');
});
};
I guess I should explain.
We pass the function that does work to the workhandler.
The work handler calls it and passes in work.
The function that does work calls work multiple times incrementing the counter
Since the function that does work is not asynchronous (very important) we can define the finish function after it has finished.
The asynchronouswork that is being done cannot finish (and call the undefined finish function) before the current synchronous block of work (the execution of the entire workhandler) has finished.
This means that after the entire workhandler has finished (and the variable finish is set) the asynchronous work jobs will start to end and call finish. Only once all of them have called finish will the callback send to after fire.