简体   繁体   中英

too much recursion when wrapping setTimeout() in web worker

I am trying to wrap the self.setTimeout() function inside a web worker (so there is no native window object). I thought it would be pretty straight forward by using the method apply syntax:

window.setTimeout = function ( /**/) {
    return setTimeout.apply(self,arguments); 
};

This call should be equivalent to calling self.setTimeout() with any number of arguments (function, delay, arguments to pass to function) and returning the id for the timeout.

However, the middle line of code eventually throws "too much recursion" error. It looks like calling it like this somehow breaks the mechanic described here: Will a recursive 'setTimeout' function call eventually kill the JS Engine? that destroys previous function context and makes it actually recursive. Just in case it is browser specific: Tested in Firefox 74.0 (64-Bit).

Some background

Just in case if anyone is wondering, why I am trying this:

I want to move some CPU-heavy code from the main Thread to web-workers without rewriting everything.

Unfortunately, the code relies on some library that in turn relies on window and document being present, or it won't initialize.

As a workaround, I am using Mock Dom inside the worker. Because I don't actually want to do DOM manipulations, I am just adding any features missing from the mock dom in the most simply way possible. But for some reason, the library at some point explicitely calls window.setTimeout() instead of just setTimeout() - so I need to add this function to the mock window object and it needs to work correctly.

Update

Thx, Alexandre Senges for pointing out the error that this function will actually call itself.

The solution inside a web-Worker that actually works is

window.setTimeout = function ( /**/) {
    return DedicatedWorkerGlobalScope.prototype.setTimeout.apply(self,arguments); 
};

So, I need to write the whole path of the fuction to prevent window.setTimeout from calling itself.

Update 2

Actually, as the other answer from Kaiido pointed out, my original idea should have worked fine on it's own. The reason why too much recursion happened was that I made an error in some other part of the code that effectively copied self.setTimeout = window.setTimeout - thus causing setTimeout===self.setTimeout===window.setTimeout so the function window.setTimeout suddenly was recursive without intention.

Maximum recursion depth happens when you call the same function over and over again in itself too many times. The reason is that every time you call the function, you have to create new instances of all its stack variables.

For instance:

const recursive = (x) => {
    console.log(x);
    recursive(x+1);
}

Here, every time the function calls itself, it creates a new x that is pushed to the stack without popping the previous x because we still haven't returned from the caller. If we let that happen for too long, the stack would get full and we would get a StackOverflow (thus the name of the website.)

Now, JS protects us from letting that happen by limiting the depth of the call stack.

In your example, the problem is due to the fact that you inadvertently created a recursive function. When you will call your new window.setTimeout , it will call setTimeout.apply which refers to the new function itself, so it calls itself again etc. One fix would be to extract the previous method in a new variable and call it afterwards:

window.oldTimeout = window.setTimeout;
window.setTimeout = function ( /**/) {
    return oldTimeout.apply(self,arguments); 
};

However I think that what you are trying to do is all around a bad idea. You shouldn't try to replace a window method as it will change it for all your program and even for libraries which might be dependent on them. If I were you, I would simply create a new function myTimeout .

Your code should work just fine:

 const worker_script = ` const window = {}; window.setTimeout = function ( /**/) { return setTimeout.apply(self,arguments); }; window.setTimeout( (val)=>postMessage('done ' + val), 200, 'ok'); `; const blob = new Blob( [ worker_script ] ); const url = URL.createObjectURL( blob ); const worker = new Worker( url ); worker.onmessage = e => console.log( e.data ); worker.onerror = console.error;

To get this error, you probably did set window to self :

 const worker_script = ` const window = self; window.setTimeout = function ( /**/) { return setTimeout.apply(self,arguments); }; window.setTimeout( (val)=>postMessage('done ' + val), 200, 'ok'); `; const blob = new Blob( [ worker_script ] ); const url = URL.createObjectURL( blob ); const worker = new Worker( url ); worker.onmessage = e => console.log( e.data ); worker.onerror = console.error;

If so, you don't even need to rewrite anything:

 const worker_script = ` const window = self; window.setTimeout( (val)=>postMessage('done ' + val), 200, 'ok'); `; const blob = new Blob( [ worker_script ] ); const url = URL.createObjectURL( blob ); const worker = new Worker( url ); worker.onmessage = e => console.log( e.data ); worker.onerror = console.error;

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM