I have a problem with a javascript-Function, where I can't use callback-Functions.
The function gets a List of "commands", and iterates over them. It performs one command after another. In order to do that, I have to use a recursive Function with callbacks.
Here's an example to describe it:
function processList( inputArray, cb ){
if(inputArray.length == 0) return cb(); //No commands left
var command = inputArray.pop(); //Get last Element from the List
processCommand(command, function(){ //Execute the command
return processList(inputArray, cb); //Continue with the next command, when this one finished
});
}
function processCommand(command, cb){
setTimeout(function(){
console.log("Processed command "+command);
cb();
}, 10);
}
This is how I call the function:
processList( ["cmd1", "cmd2", "cmd3"], function(){
console.log("DONE");
});
It works perfectly, one command is executed after the previous one.
My Problem:
The list contains thousands of Elements, and it's even possible that the list gets new commands during processing. And I reach the maximum call-stack within seconds.
I don't need the call-stack. When I finished the last command, there are just thousands of returns, which guide me back to the function, which started everything.
I have no idea how to solve this problem, and I don't want to use busy waiting (which makes the code extremly inefficient).
Is there another trick? Like signals or trashing the call-stack?
Edit:
Here's a jsFiddle for demonstration: http://jsfiddle.net/db6J8/ (Notice, that your browser-tab might freeze/crash)
The error-Message is Uncaught RangeError: Maximum call stack size exceeded
I tested it with Chrome, you might have to increase the Array in other Browsers (IE has a huge Callstack).
Edit2:
Thanks for you help, I didn't recognise the difference between adding/removing the timeout. Hover, it doesn't solve my Problem. Here are some more details: I have different commands. Some commands are synchronous, others are asynchronous. So I have to use callback-Functions, but still have the problem with the callstack.
Here's an updated example:
var commands = [];
for(var i=15000; i>=0;i--){ commands.push(i); }
processList(commands, function(){
alert("DONE");
});
function processList( inputArray, cb ){
if(inputArray.length == 0) return cb(); //No commands left
var command = inputArray.pop(); //Get last Element from the List
processCommand(command, function(){ //Execute the command
return processList(inputArray, cb); //Continue with the next command, when this one finished
});
}
function processCommand(command, cb){
if(command%2 == 0){ //This command is synchron
console.log("Processed sync. command "+command);
cb();
}else{
setTimeout(function(){
console.log("Processed async. command "+command);
cb();
}, 1);
}
}
And the fiddle: http://jsfiddle.net/B7APC/
// setTimeout(function(){ console.log("Processed command " + command); cb(); // }, 1);
That's the reason. Without setTimeout
, you easily will hit the call stack limit (Demo with named functions):
Maximum recursion depth exceeded:
finishedCommand _display/:16
processCommand _display/:23
processList _display/:15
finishedCommand _display/:16
processCommand _display/:23
…
processList _display/:15
finishedCommand _display/:16
processCommand _display/:23
processList _display/:15
<Global Scope> _display/:16
Choose a synchronous loop instead of recursively calling processCommand
.
If you put the setTimeout
back into effect, each timer event will call the function with a fresh new call stack, and it will never overflow:
setTimeout(function later(){
console.log("Processed command " + command);
cb();
}, 1);
You see that the stack always looks like this when "DONE" is logged - regardless of how many commands you processed (Demo):
BreakPoint at console.log()
readyHandler show/:8
processList show/:13
finishedCommand show/:17
later show/:24
<Global Scope> // setTimeout