Problem
Some time ago, in the work on the client (javascript) part of the josi engine, by the way, a rather frequent stack overflow problem arose:
Uncaught RangeError: Maximum call stack size exceeded (google chrome)
The article discusses the solution without using setTimout or setInterval.
The essence
The reason for this behavior is known and understood, and in one form or another is always caused by the following. Classical (direct) recursion generates a chain of consecutive calls, which accordingly leads to filling the call stack, however, the browser’s call stack is quite small, in the chrome at the time of testing it is 500 calls, in safari, if I'm not mistaken, too. In any case, this is a limit value, which means it can be exceeded and an exception is obtained. Naturally, such a long execution of the code is not desirable in principle, and this should be avoided. And yet, personally, I don’t want to rely on luck, despite the fact that the situation in which I had to face the problem of production should not arise, I spent time studying this issue.
Decision
The classic solution (I mean the overwhelming number of articles offering it) is the use of indirect recursion by means of:
setTimeout or
setInterval .
')
As an example, I will cite a simple recursive function, the only purpose of which, sooner or later, to return you the stack size limit along with exeption about exceeding this limit ...
function f(args) { var self=this; var k=args.k;
the same useless function, but now theoretically infinite, except that k overflows
function f(args) { var self=this; var k=args.k;
The current function is terminated immediately by using the setTimout mediator for recursion, and the next call is made on an event.
The negative side of this approach is its extremely low performance, despite the fact that we specify zero delay. Depending on the browser, the function will be called, on average, not earlier than after 10 ms. But we are struggling with exceeding the call stack, which means our function is called hundreds of times, which means a loss in performance of ~ 1 s for every 100 calls. Detailed testing found
here .
The simplest thing that came to mind is to organize a symbiosis from alternate use of direct and indirect calls, so that when a certain counter value is reached, interrupt the stack with an indirect call. In part, this solution is now used. But here, too, things are not so simple, especially if the recursion is represented by a loop of several functions.
Here is a simple example reflecting the essence of such a decision:
var max_call_i=300; function f(args) { var self=this; var k=args.k; var call_i=args.call_i
In my code, the problem arose in the template engine, which just before that was rewritten according to a new paradigm. I did not want to abandon the adopted architecture. At the same time, the real drop in productivity was 20-30% - which was just monstrous. The solution proposed above was also not ideal: the drop in productivity was kept by 5-7%. It did not suit me: googled a lot, and attacked
what was needed .
And
this is a test from the same place, from which it is clear that the proposed approach, in comparison with setTimeout 0, is much more productive, which in practice gave no more than 3% drop in performance in my case ...
This solution is based on a bunch of
window.postMessage and
element.addEventListener , which is cross-browser-friendly for me (ie8 +).
I reworked the function from the above article in the AMD module. Perhaps someone will be useful ...
define([], function () { return { args: