I'm trying to write a web app that uses Javascript to perform a fairly complex calculation (involves factorials and Bessel functions). When I run the script in IE, it gives me a warning that the script is unresponsive or taking a long time, and asks if I want to continue running it. I've read that to get around this, you can use either the setTimeout or setInterval commands to essentially reset the counter that IE uses to determine if a script is long-running.
I've tried implementing this, but have not succeeded. When I run a profiler, it appears that my function which computes a factorial is what takes most of the time, so I'd like to use setTimeout in that function. Here's the function I currently have:
function factorial(x) {
var buff = 1;
for (i=x;i>=1;i--) {
buff = buff * i;
}
return buff
}
I've tried replacing the code with something like this, but it isn't working properly:
function factorial(x) {
if (x==0) {
factbuff=1;
}
else {
factbuff = x;
factidx = x;
setTimeout('dofact()',50);
}
return factbuff
}
function dofact() {
if (factidx > 1) {
factidx--;
factbuff = factbuff * factidx;
}
}
Anybody know what I'm doing wrong and how I can correctly implement the setTimeout function in order to calculate a factorial while elimina开发者_StackOverflow社区ting the script warning in IE?
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.
精彩评论