Search code examples
javascriptweb-worker

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.


Solution

  • 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;