簡體   English   中英

在 web worker 中包裝 setTimeout() 時遞歸太多

[英]too much recursion when wrapping setTimeout() in web worker

我正在嘗試將 self.setTimeout() 函數包裝在網絡工作者中(因此沒有本機窗口對象)。 我認為使用方法 apply 語法會非常簡單:

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

這個調用應該等同於使用任意數量的參數(函數、延遲、傳遞給函數的參數)調用 self.setTimeout() 並返回超時的 id。

但是,中間的代碼行最終會拋出“遞歸過多”的錯誤。 看起來像這樣調用它以某種方式打破了這里描述的機制: 遞歸的“setTimeout”函數調用最終會殺死 JS 引擎嗎? 這會破壞先前的函數上下文並使其真正遞歸。 以防萬一它是特定於瀏覽器的:在 Firefox 74.0(64 位)中測試。

一些背景

以防萬一有人想知道,為什么我要嘗試這個:

我想將一些占用大量 CPU 的代碼從主線程移動到網絡工作者,而無需重寫所有內容。

不幸的是,代碼依賴於一些庫,而這些庫又依賴於存在的窗口和文檔,否則它不會初始化。

作為一種解決方法,我在工作人員內部使用Mock Dom 因為我實際上並不想做 DOM 操作,所以我只是以最簡單的方式添加模擬 dom 中缺少的任何功能。 但出於某種原因,庫在某些時候明確調用 window.setTimeout() 而不是 setTimeout() - 所以我需要將此函數添加到模擬窗口對象,並且它需要正常工作。

更新

感謝 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所以函數窗口.setTimeout 突然無意識地遞歸。

當您一次又一次地多次調用同一個函數時,就會發生最大遞歸深度。 原因是每次調用該函數時,都必須為其所有堆棧變量創建新實例。

例如:

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

在這里,每次函數調用自身時,它都會創建一個新的 x,該 x 被壓入堆棧而不彈出前一個 x,因為我們還沒有從調用者那里返回。 如果我們讓這種情況發生太久,堆棧會變滿,我們會得到一個StackOverflow (因此是網站的名稱)。

現在,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;

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM