在 web worker 中包装 setTimeout() 时递归过多
too much recursion when wrapping setTimeout() in web worker
我试图将 self.setTimeout() 函数包装在网络工作者中(因此没有本机 window 对象)。我认为使用方法 apply syntax:
会非常简单
window.setTimeout = function ( /**/) {
return setTimeout.apply(self,arguments);
};
此调用应等效于使用任意数量的参数(函数、延迟、传递给函数的参数)调用 self.setTimeout() 并返回超时 ID。
然而,中间那行代码最终抛出"too much recursion"错误。看起来像这样调用它以某种方式破坏了此处描述的机制:Will a recursive 'setTimeout' function call eventually kill the JS Engine? 破坏了以前的函数上下文并使其实际上递归。以防万一它是特定于浏览器的:在 Firefox 74.0(64 位)中测试。
一些背景
以防万一,如果有人想知道我为什么要这样做:
我想将一些 CPU 繁重的代码从主线程移动到 web-workers 而无需重写所有内容。
不幸的是,代码依赖于某些库,而这些库又依赖于 window 和文档存在,否则它不会初始化。
作为一种解决方法,我在 worker 中使用 Mock Dom。因为我实际上并不想进行 DOM 操作,所以我只是以最简单的方式添加模拟 dom 中缺少的任何功能。但出于某种原因,库在某些时候明确地调用 window.setTimeout() 而不仅仅是 setTimeout() - 所以我需要将这个函数添加到模拟 window 对象并且它需要正常工作。
更新
感谢 Alexandre Senges 指出此函数实际调用自身的错误。
web-Worker 中实际有效的解决方案是
window.setTimeout = function ( /**/) {
return DedicatedWorkerGlobalScope.prototype.setTimeout.apply(self,arguments);
};
所以,我需要写出函数的整个路径,以防止 window.setTimeout 调用自身。
更新 2
实际上,正如 Kaiido 的另一个回答所指出的那样,我最初的想法本身应该是可行的。发生太多递归的原因是我在有效复制 self.setTimeout = window.setTimeout
的代码的其他部分犯了一个错误 - 从而导致 setTimeout===self.setTimeout===window.setTimeout
所以函数 window.setTimeout 突然无意中递归了。
当您一遍又一遍地调用同一个函数太多次时,就会出现最大递归深度。原因是每次调用该函数时,都必须为其所有堆栈变量创建新实例。
例如:
const recursive = (x) => {
console.log(x);
recursive(x+1);
}
在这里,每次函数调用自身时,它都会创建一个新的 x,将其推入堆栈而不弹出前一个 x,因为我们仍然没有从调用者那里返回。如果我们让这种情况发生太久,堆栈就会变满,我们会得到一个 Whosebug
(因此是网站的名称。)
现在,JS 通过限制调用堆栈的深度来保护我们不让这种情况发生。
在您的示例中,问题是由于您无意中创建了递归函数。当您调用新的 window.setTimeout
时,它会调用 setTimeout.apply
来引用新函数本身,因此它会再次调用自身等。一个解决方法是将以前的方法提取到一个新变量中并调用之后:
window.oldTimeout = window.setTimeout;
window.setTimeout = function ( /**/) {
return oldTimeout.apply(self,arguments);
};
但是我认为你正在尝试做的是一个坏主意。您不应该尝试替换 window
方法,因为它会为您的所有程序甚至可能依赖于它们的库更改它。如果我是你,我会简单地创建一个新函数 myTimeout
.
您的代码应该可以正常工作:
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;
要得到这个错误,您可能将 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;
如果是这样,你甚至不需要重写任何东西:
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;
我试图将 self.setTimeout() 函数包装在网络工作者中(因此没有本机 window 对象)。我认为使用方法 apply syntax:
会非常简单window.setTimeout = function ( /**/) {
return setTimeout.apply(self,arguments);
};
此调用应等效于使用任意数量的参数(函数、延迟、传递给函数的参数)调用 self.setTimeout() 并返回超时 ID。
然而,中间那行代码最终抛出"too much recursion"错误。看起来像这样调用它以某种方式破坏了此处描述的机制:Will a recursive 'setTimeout' function call eventually kill the JS Engine? 破坏了以前的函数上下文并使其实际上递归。以防万一它是特定于浏览器的:在 Firefox 74.0(64 位)中测试。
一些背景
以防万一,如果有人想知道我为什么要这样做:
我想将一些 CPU 繁重的代码从主线程移动到 web-workers 而无需重写所有内容。
不幸的是,代码依赖于某些库,而这些库又依赖于 window 和文档存在,否则它不会初始化。
作为一种解决方法,我在 worker 中使用 Mock Dom。因为我实际上并不想进行 DOM 操作,所以我只是以最简单的方式添加模拟 dom 中缺少的任何功能。但出于某种原因,库在某些时候明确地调用 window.setTimeout() 而不仅仅是 setTimeout() - 所以我需要将这个函数添加到模拟 window 对象并且它需要正常工作。
更新
感谢 Alexandre Senges 指出此函数实际调用自身的错误。
web-Worker 中实际有效的解决方案是
window.setTimeout = function ( /**/) {
return DedicatedWorkerGlobalScope.prototype.setTimeout.apply(self,arguments);
};
所以,我需要写出函数的整个路径,以防止 window.setTimeout 调用自身。
更新 2
实际上,正如 Kaiido 的另一个回答所指出的那样,我最初的想法本身应该是可行的。发生太多递归的原因是我在有效复制 self.setTimeout = window.setTimeout
的代码的其他部分犯了一个错误 - 从而导致 setTimeout===self.setTimeout===window.setTimeout
所以函数 window.setTimeout 突然无意中递归了。
当您一遍又一遍地调用同一个函数太多次时,就会出现最大递归深度。原因是每次调用该函数时,都必须为其所有堆栈变量创建新实例。
例如:
const recursive = (x) => {
console.log(x);
recursive(x+1);
}
在这里,每次函数调用自身时,它都会创建一个新的 x,将其推入堆栈而不弹出前一个 x,因为我们仍然没有从调用者那里返回。如果我们让这种情况发生太久,堆栈就会变满,我们会得到一个 Whosebug
(因此是网站的名称。)
现在,JS 通过限制调用堆栈的深度来保护我们不让这种情况发生。
在您的示例中,问题是由于您无意中创建了递归函数。当您调用新的 window.setTimeout
时,它会调用 setTimeout.apply
来引用新函数本身,因此它会再次调用自身等。一个解决方法是将以前的方法提取到一个新变量中并调用之后:
window.oldTimeout = window.setTimeout;
window.setTimeout = function ( /**/) {
return oldTimeout.apply(self,arguments);
};
但是我认为你正在尝试做的是一个坏主意。您不应该尝试替换 window
方法,因为它会为您的所有程序甚至可能依赖于它们的库更改它。如果我是你,我会简单地创建一个新函数 myTimeout
.
您的代码应该可以正常工作:
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;
要得到这个错误,您可能将 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;
如果是这样,你甚至不需要重写任何东西:
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;