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.
Related
function windowResize() {
someFunction();
console.log("test3");
}
function someFunction(){
console.log("test");
longExecutingFunctionWithAsyncReq();
console.log("test2");
}
function longExecutingFunctionWithAsyncReq() {
// some codes here
}
whenever the window is resize(zoomed out/in), this function is called.
But if the user spams the zoom, someFunction() will not have the time to finish and will then cause the error.
I'm thinking of addressing this issue by stopping the current operation and then process the new operation. Also, I've tried reading about Deferred and Promise, but I can't grasp the simplicity of the topic and I'm not sure if it really solves my problem. Plus, I've also checked on callbacks and was very doubtful that this will not solve my problem either.
If my solution is not possible though, I thought of just queuing the operations, but the downside might be, the queue might overflow if not controlled. As for this solution, I've not looked any farther to this, except reading about it.
you could use a timeout and clear it before resetting it when the resize function is called:
var myTimeout;
function windowResize() {
clearTimeout(myTimeout);
myTimeout = setTimeout(someFunction, 500);
}
this way the function will be called when the user stops resizing and 500 miliseconds have passed.
if you just need to wait for operation to finish you can set up a flag.
var working = false;
function windowResize() {
if (!working){
working = true;
someFunction();
console.log("test3");
}
}
function someFunction(){
console.log("test");
longExecutingFunctionWithAsyncReq();
console.log("test2");
}
function longExecutingFunctionWithAsyncReq() {
// some codes here
// on finish set working to False
}
var isStillWorking = false;
function windowResize() {
if(isStillWorking) {
// Do nothing.
} else {
someFunction(function(){
isStillWorking = false;
});
console.log("test3");
}
}
function someFunction(callback){
isStillWorking = true;
console.log("test");
longExecutingFunctionWithAsyncReq();
console.log("test2");
}
function longExecutingFunctionWithAsyncReq() {
// some codes here
}
To clarify more of Anton's answer I manage to implement the same thing using a flag [global] variable and a callback. I use a callback in order to flag=false since I also need to wait for the asynchronous requests inside the function to finish before resetting the flag.
I have a JavaScript function like the following.
function changeTheDom(var1, var2, var3) {
// Use DWR to get some server information
// In the DWR callback, add a element to DOM
}
This function is called in a couple of places in the page. Sometimes, in a loop. It's important that the elements be added to the DOM in the order that the changeTheDom function is called.
I originally tried adding DWREngine.setAsync(false); to the beginning of my function and DWREngine.setAsync(true); to the end of my function. While this worked, it was causing utter craziness on the rest of the page.
So I am wondering if there is a way to lock the changeTheDom function. I found this post but I couldn't really follow the else loop or how the lockingFunction was intended to be called.
Any help understanding that post or just making a locking procedure would be appreciated.
Don't try to lock anything. The cleanest way is always to adapt to the asynchronous nature of your code. So if you have an asynchronous function, use a callback. In your particular case I would suggest that you split your function up in one part that is executed before the asych call and one part that is executed afterwards:
function changeTheDomBefore(var1, var2, var3) {
//some code
//...
asyncFunction(function(result){
//this will be executed when the asynchronous function is done
changeTheDomAfter(var1, var2, var2, result);
});
}
function changeTheDomAfter(var1, var2, var3, asynchResult) {
//more code
//...
}
asyncFunction is the asynchronous function which, in this example, takes one argument - the callback function, which then calls your second changeTheDom function.
I think I finally got what you mean and I decided to create another answer, which is hopefully more helpful.
To preserve order when dealing with multiple asynchronous function calls, you could write a simple Queue class:
function Queue(){
var queue = [];
this.add = function(func, data) {
queue.push({func:func,data:data});
if (queue.length === 1) {
go();
}
};
function go() {
if (queue.length > 0) {
var func = queue[0].func,
data = queue[0].data;
//example of an async call with callback
async(function() {
func.apply(this, arguments);
queue.shift();
go();
});
}
}
};
var queue = new Queue();
function doit(data){
queue.add(function(result){
console.log(result);
}, data);
}
for (var i = 0; i < 10; i++) {
doit({
json: JSON.stringify({
index: i
}),
delay: 1 - i / 10.0
});
}
FIDDLE
So everytime you invoke your async function, you call queue.add() which adds your function in the queue and ensures that it will only execute when everything else in the queue is finished.
I want my handler of the ready event will fire after all other handlers are done.
It's extremely handy for manipulating plugins' undesired actions.
If I write my handler after all others, it only guarantees it will fire after all others fired, not finished:
$(function() {
setTimeout(function() { alert('other handler'); }, 500);
});
$(function() { alert('my handler'); });
Fiddle
In that code, my handler alerted first.
I read that before jQuery version 1.4 the readyList was public. so in version 1.7 I have no idea how I can tell that my handler is the last handler or not.
If the idea is that you don't control the other ready handlers, then given your example where another handler used a setTimeout, you can never actually know (without inspecting the other code) if your code will run after all other code.
The readyList wouldn't help even if it was public, because in your example, the handler with the setTimeout will be removed from the readyList long before the setTimeout handler runs. The readyList Array doesn't have any control over that sort of asynchronous code either.
So if you don't control (can't modify) the other code, then I really don't have a solution. But if the other code is just long running, but not asynchronous, then there wouldn't be any issue, because if your code is the last .ready() handler assigned, it shouldn't matter how long the other handlers take to execute. If their code is synchronous, it will force yours to wait until they're complete. It's just that if they're using asynchronous code, like your setTimeout example, then there's nothing you can do short of examining the other code, and modifying yours to make sure it fires last.
You can use something like this:
function Join(cb) {
var paths = 0;
var triggerCallback = cb;
this.add = function () {
paths ++;
return this.call;
};
this.call = function () {
paths --;
if (paths == 0)
if (triggerCallback)
triggerCallback();
};
return this;
}
An example:
function finishedAll() {
alert("All finished");
}
window.join = new Join(finishedAll);
function sampleCall(callJoinHandle) {
alert("Not done yet.");
if (callJoinHandle) callJoinHandle();
}
var cb1 = join.add();
setTimeout(function () { sampleCall(cb1); }, 1000);
var cb2 = join.add();
setTimeout(function () { sampleCall(cb2); }, 1000);
var cb3 = join.add();
setTimeout(function () { sampleCall(cb3); }, 1000);
An idea could be creating an array of deferred to use inside every ready function (except the last one), resolving each one when the snippet has completed.
Then, in the last ready function you could simply check the promise resolution with $.when and then execute some other code: e.g.
var dfdArray = [];
$(function() {
var dfd = $.Deferred();
dfdArray.push(dfd);
setTimeout(function() {
console.log('another simple handler');
dfd.resolve();
}, 2000);
});
$(function() {
var dfd = $.Deferred();
dfdArray.push(dfd);
setTimeout(function() {
console.log('first handler');
dfd.resolve();
}, 1200);
});
$(function() {
$.when.apply($, dfdArray).done(function() {
alert('my final handler');
})
});
See fiddle in action here: http://jsfiddle.net/DXaw5/
I don't know if it is possible for you to create a queue for all the functions like
var queue = [];
queue .push(fun1);
queue .push(fun2);
//execute the first function and remove it.
(queue .shift())();
I usually use the following pattern, simply keepig a counter of finished async functions:
var fired = 10;
var finished = 0;
for (var i = 0; i < fired; i++) {
// Call an asynchronous function 10 times
async_function(function() {
// When asynchronous function finishes,
// we check if it was the last one.
if (++finished == fired) all_ready();
});
}
The same in coffeescript:
fired = 10
finished = 0
(async_function -> all_ready() if ++finished == ready) for n in [0...fired]
(We call the same function for 10 times to keep the example simple, while in reality you may of course call different functions, but the same idea apply; in callback function you check the counter.)
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.
I need to visit each node in a tree, do some asynchronous work, and then find out when all of the asynchronous work has completed. Here are the steps.
Visit a node and modify its children asynchronously.
When async modifications to children are done, visit all children (which might require async work).
When all asynchronous work for all descendants is done, do something else.
Update:
I ended up using a pattern that looks like a monitor/lock (but isn't) for each node to know when to begin step 2. I used events and attributes to keep track of all descendants of a node to know when to begin step 3.
It works, but man is this difficult to read! Is there a cleaner pattern?
function step1(el) { // recursive
var allDone = false;
var monitor = new Monitor();
var lock = monitor.lock(); // obtain a lock
$(el).attr("step1", ""); // step1 in progress for this node
// fires each time a descendant node finishes step 1
$(el).on("step1done", function (event) {
if (allDone) return;
var step1Descendants = $(el).find("[step1]");
if (step1Descendants.length === 0) {
// step 1 done for all descendants (so step 2 is complete)
step3(el); // not async
allDone = true;
}
});
// fires first time all locks are unlocked
monitor.addEventListener("done", function () {
$(el).removeAttr("step1"); // done with step 1
step2(el); // might have async work
$(el).trigger("step1done");
});
doAsyncWork(el, monitor); // pass monitor to lock/unlock
lock.unlock(); // immediately checks if no other locks outstanding
};
function step2(el) { // visit children
$(el).children().each(function (i, child) {
step1(child);
});
};
Here's an updated version that walks the node-tree, processing each child in the initial root node, and then descends recursively into each child's tree and processes its child nodes and so on.
Here's a jsfiddle demo
// Pass the root node, and the callback to invoke
// when the entire tree has been processed
function processTree(rootNode, callback) {
var i, l, pending;
// If there are no child nodes, just invoke the callback
// and return immediately
if( (pending = rootNode.childNodes.length) === 0 ) {
callback();
return;
}
// Create a function to call, when something completes
function done() {
--pending || callback();
}
// For each child node
for( i = 0, l = rootNode.childNodes.length ; i < l ; i++ ) {
// Wrap the following to avoid the good ol'
// index-closure-loop issue. Pass the function
// a child node
(function (node) {
// Process the child node asynchronously.
// I'm assuming the function takes a callback argument
// it'll invoke when it's done.
processChildNodeAsync(node, function () {
// When the processing is done, descend into
// the child's tree (recurse)
processTree(node, done);
});
}(rootNode.childNodes[i]));
}
}
Original Answer
Here's a basic example you might be able to use... though without the specifics of your problem, it's half psuedo-code
function doAsyncTreeStuff(rootNode, callback) {
var pending = 0;
// Callback to handle completed DOM node processes
// When pending is zero, the callback will be invoked
function done() {
--pending || callback();
}
// Recurse down through the tree, processing each node
function doAsyncThingsToNode(node) {
pending++;
// I'm assuming the async function takes some sort of
// callback it'll invoke when it's finished.
// Here, we pass it the `done` function
asyncFunction(node, done);
// Recursively process child nodes
for( var i = 0 ; i < node.children.length ; i++ ) {
doAsyncThingsToNode(node.children[i]);
}
}
// Start the process
doAsyncThingsToNode(rootNode);
}
It seems the right pattern for this problem and for async work in general is Promises. The idea is that any function that will do asynchronous work should return a promise object, to which the caller can attach functions that should be called when the asynchronous work is completed.
jQuery has a great API for implementing this pattern. It's called a jQuery.Deferred object. Here's a simple example:
function asyncWork() {
var deferred = $.Deferred();
setTimeout(function () {
// pass arguments via the resolve method
deferred.resolve("Done.");
}, 1000);
return deferred.promise();
}
asyncWork().then(function (result) {
console.log(result);
});
Very tidy. What's the difference between a Deferred object and its promise object? Good question.
Here's how you might apply this pattern to solve this problem.
function step1(el) { // recursive
var deferred = $.Deferred();
// doAsyncWork needs to return a promise
doAsyncWork(el).then(function () {
step2(el).then(function () {
step3(el); // not async
deferred.resolve();
});
});
return deferred.promise();
};
function step2(el) { // visit children
var deferred = $.Deferred();
var childPromises = [];
$(el).children().each(function (i, child) {
childPromises.push(step1(child));
});
// When all child promises are resolved…
$.when.apply(this, childPromises).then(function () {
deferred.resolve();
});
return deferred.promise();
};
So much cleaner. So much easier to read.
This is something you would probably prefer to do with threads to continue other work, but since you are using JavaScript you need to work around this with some sort of blocking. One way is make an initially empty list of finished tasks, make the asynchronous calls, and have each call register itself on the list when it is finished. While you are waiting for the calls, enter a loop with a timer, and at each iteration check if the finished tasks list is complete; if so, continue with other tasks. You may want to give up if your loop runs too long.