This is an update, and an explanation why the code in the prior answer (now deleted) is wrong.
First, let's restate the problem: allowing factorial function to run in IE, without
triggering the "long running script" warning.
This is the code that was previously proposed:
BROKEN. DO NOT USE.
var executions = 0;
function factorial(x) {
executions++;
if (x > 1) {
if (executions % 100 === 0) {
return (function() { // NO NO NO
var y = x;
setTimeout(function(y) { return y*factorial(y-1); }, 50);
})();
} else {
return x*factorial(x-1);
}
} else {
return 1;
}
}
Ok, so what's wrong with that code?
The factorial function itself is recursive. That means the function calls itself. Each time a function calls itself, you get another stack frame in memory. What the code above attempts to do is call itself one hundred times. I don't know how many nested stack frames a browser can tolerate, but 100 seems a little high. I would do it in batches of 10.
The function proposed above is not asynchronous. When you use setTimeout() to get around the IE warning, the function needs to become asynchronous. This means - instead of calling it like var value = func(x);
, you will need to convert your code to pass a callback, which the async function invokes when it has the result.
related to the problem above, the use of setTimeout in the proposed code is wrong. In one place, the code does this:
return (function() { // NO NO NO
var y = x;
setTimeout(function(y) { return y*factorial(y-1); }, 50);
})();
What does that do? Let's break it down. It has an anonymous function. That function gets invoked (by the open-close paren at the end). And the value of that invocation is returned by the main factorial function. what is the value of invoking the anon function? Problem a: It does not return a value. It is undefined. (See What does javascript function return in the absence of a return statement?)
Fixing it is not as simple as returning the value of the call to setTimeout()
. That is also wrong. The return value of setTimeout()
is an identifier that can be used to clear the timeout with clearTimeout()
. It is definitely NOT the value delivered by what setTimeout invokes.
ok, how to fix it? First, realize the factorial function will be asynchronous, so getting and displaying the result will look like this:
function displayFacResult(result) {
var t2 = document.getElementById('t2');
t2.value = result;
}
function b1_Click() {
var t1 = document.getElementById('t1'),
value = parseInt(t1.value, 10);
computeFactorialAsynchronously(value, displayFacResult);
}
The button click calls "compute", and passes it the name of a function that gets called with the result. The function that gets called with the result actually does the displaying. This is the async invocation pattern.
ok, now the computation.
function computeFactorialAsynchronously(firstX, callback) {
var batchSize = 3, limit=0, result = 1;
var doOneFactorialStep = function(x, steps) {
if (steps) { limit = x - steps; }
if (x==1) { callback(result); }
if (x>limit) {
result *= x;
doOneFactorialStep(x-1);
}
else {
setTimeout(function () {
doOneFactorialStep(x, batchSize);
}, 1);
}
};
doOneFactorialStep(firstX, batchSize);
// the actual return value of the computation
// always comes in the callback.
return null;
}
It computes factorials by "chunk", each chunk involves N multiplications, and is represented by the variable "steps" in the above. The value of N (steps) determines the level of recursion. 100 is probably too large. 3 is probably too small for good performance, but it illustrates the asynchonicity.
Inside the computeFactorialAsynchronously function, there is a helper function that computes one chunk and then calls setTimeout to compute the next chunk. There's some simple arithmetic to manage when to stop computing the current chunk.
working example: http://jsbin.com/episip
In a sense, moving to the asynchronous model takes you away from a purely functional metaphor, where the result of the computation is the result of the function. We can do that in javascript, but it runs into the IE "long running script" warning. To avoid the warning, we go asynchronous, which means the return value of "computeFactorial" is not the actual factorial. In the asynch model, we get the result via a "side effect" - from a callback function that the computation function invokes when it is finished computing.