简体   繁体   English

在 web worker 中包装 setTimeout() 时递归太多

[英]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).我正在尝试将 self.setTimeout() 函数包装在网络工作者中(因此没有本机窗口对象)。 I thought it would be pretty straight forward by using the method apply syntax:我认为使用方法 apply 语法会非常简单:

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.这个调用应该等同于使用任意数量的参数(函数、延迟、传递给函数的参数)调用 self.setTimeout() 并返回超时的 id。

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?看起来像这样调用它以某种方式打破了这里描述的机制: 递归的“setTimeout”函数调用最终会杀死 JS 引擎吗? that destroys previous function context and makes it actually recursive.这会破坏先前的函数上下文并使其真正递归。 Just in case it is browser specific: Tested in Firefox 74.0 (64-Bit).以防万一它是特定于浏览器的:在 Firefox 74.0(64 位)中测试。

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.我想将一些占用大量 CPU 的代码从主线程移动到网络工作者,而无需重写所有内容。

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.作为一种解决方法,我在工作人员内部使用Mock Dom 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.因为我实际上并不想做 DOM 操作,所以我只是以最简单的方式添加模拟 dom 中缺少的任何功能。 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.但出于某种原因,库在某些时候明确调用 window.setTimeout() 而不是 setTimeout() - 所以我需要将此函数添加到模拟窗口对象,并且它需要正常工作。

Update更新

Thx, Alexandre Senges for pointing out the error that this function will actually call itself.感谢 Alexandre Senges 指出该函数实际上会调用自身的错误。

The solution inside a web-Worker that actually works is实际工作的 web-Worker 内部的解决方案是

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.所以,我需要写出整个函数的路径,以防止 window.setTimeout 调用自己。

Update 2更新 2

Actually, as the other answer from Kaiido pointed out, my original idea should have worked fine on it's own.实际上,正如 Kaiido 的另一个答案所指出的那样,我最初的想法应该可以很好地发挥作用。 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.发生太多递归的原因是我在有效复制self.setTimeout = window.setTimeout的代码的其他部分出错 - 从而导致setTimeout===self.setTimeout===window.setTimeout所以函数窗口.setTimeout 突然无意识地递归。

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.在这里,每次函数调用自身时,它都会创建一个新的 x,该 x 被压入堆栈而不弹出前一个 x,因为我们还没有从调用者那里返回。 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.)如果我们让这种情况发生太久,堆栈会变满,我们会得到一个StackOverflow (因此是网站的名称)。

Now, JS protects us from letting that happen by limiting the depth of the call stack.现在,JS 通过限制调用堆栈的深度来保护我们不让这种情况发生。

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.setTimeout ,它会调用setTimeout.apply ,它指的是新函数本身,所以它会再次调用自己等等。一个解决方法是在一个新变量中提取前一个方法,然后调用它:

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.您不应该尝试替换window方法,因为它会为您的所有程序甚至可能依赖它们的库更改它。 If I were you, I would simply create a new function myTimeout .如果我是你,我会简单地创建一个新函数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 :要获得此错误,您可能确实将window设置为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;

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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