← Back to list

CosTimeout — 可信賴的 timeout

使用 Web Worker 避免瀏覽器省電機制影響 Timeout 執行

Dian · 2025-03-21 01:50 · 0 claps · 5.5 min read paywalled
#javascript #forntend #settimeout
Open on Medium ↗
Wiki topics: 🌐 · Web Development

CosTimeout — 可信賴的 timeout

gemini/imagen 3

gemini/imagen 3

使用 Web Worker 避免瀏覽器省電機制影響 Timeout 執行

由於瀏覽器為了節省資源,可能會在 非活動的頁面或標籤頁 背景執行或閒置時延遲或暫停 setTimeoutsetInterval 的執行,這會導致依賴這些機制的功能無法在預期時間點觸發。

而 Web Worker 運行在獨立的執行緒中,與主執行緒分離,因此通常不會受到這種針對頁面生命週期的優化所影響

因此,為了避免省電機制對時間精準度的影響,改用 Web Worker 來執行計時任務。

CosTimeout 的命名由來:

CosTimeout 這個名稱的發想結合了兩個概念: Consistent (一致性): 代表這個計時器旨在提供更一致、更可靠的定時執行,不受瀏覽器省電機制的干擾。 Cosplay (角色扮演): 因為是透過 Web Worker 扮演了一個獨立的計時器角色,模擬一個不受主執行緒生命週期直接影響的計時服務。

以下是使用 Web Worker 實作的精準計時器範例:

Web Worker 程式碼 (cos_timeout.js)

// cos_timeout.js

// 用於儲存 timeout 的 Map,key 為 timeout 的識別符
// 後續透過 key 值來重設 timeout
const timeoutMap = {};

// 清除指定 key 的 timeout
function resetTimeout(key) {
  clearTimeout(timeoutMap[key]);
  delete timeoutMap[key];
}

// 接收主執行緒傳來的訊息
onmessage = (e) => {
  // 從訊息中取得 key 和 timeout 值
  const { key, timeout } = e.data;

  // 從訊息中取得用於回傳訊息的 MessagePort
  const port = e.ports[0];

  // 清除可能已存在的相同 key 的 timeout
  resetTimeout(key);

  // 設定一個新的 timeout
  timeoutMap[key] = setTimeout(() => {
    // timeout 時間到後,透過 MessagePort 回傳包含 key 的訊息給主執行緒
    port.postMessage({ key });
   }, timeout);
};

主執行緒程式碼 (main.js)

// main.js

// 創建一個新的 Web Worker 實例,指定 worker 腳本的路徑
const cosTimeout = new Worker('cos_timeout.js');

// 設定一個基於 Web Worker 的 timeout 功能
function setupCosTimeout(key, callback = () => {}, { timeout = 0 }){
  // 創建一個 MessageChannel,用於主執行緒和 Worker 之間的雙向通信
  const msgChannel = new MessageChannel();

  // 設定 MessageChannel 的 port1 的 onmessage 事件處理函數
  msgChannel.port1.onmessage = () => {
     // 當收到來自 Worker 的訊息時,執行回調函數
     callback();
  };

  // 透過 msgChannel.port2 建立專用的 message 通道
  cosTimeout.postMessage({ key, timeout }, [msgChannel.port2]);

  return msgChannel;
}

// 關閉 MessageChannel
function closeMsgChannel(msgChannel){
  try {
    // 檢查 msgChannel 和其 port 是否存在,避免錯誤
    msgChannel?.port1?.close();
    msgChannel?.port2?.close();
  } catch (e) {
    // 捕獲關閉 port 可能發生的錯誤,例如 port 已經關閉
    console.log(e);
  }
}

// 測試範例:設定一個 5 秒後執行的 timeout
const msgChannel = setupCosTimeout('hello', ()=>console.log('world'), { timeout: 5000 } );

// 在這裡您可以選擇在不再需要時關閉 MessageChannel
// closeMsgChannel(msgChannel);

程式碼說明:

cos_timeout.js (Web Worker):

  • 使用 timeoutMap 來管理不同的 timeout,允許通過 key 來重置特定的 timeout。
  • onmessage 監聽來自主執行緒的訊息,訊息內容包含 key 和 timeout 時間。
  • 使用 MessageChannel 提供的 port 將 timeout 到期後的訊息回傳給主執行緒。
  • resetTimeout 函數用於清除已存在的 timeout,避免重複執行。

main.js (主執行緒):

  • 創建一個 Worker 實例來運行 cos_timeout.js。
  • setupCosTimeout 函數創建一個 MessageChannel,並將其 port2 發送給 Worker。主執行緒監聽 port1 上的訊息。
  • 當 Worker 中的 timeout 到期並發送訊息到 port2 時,主執行緒的 port1 會觸發 onmessage 事件,並執行 callback 函數。
  • closeMsgChannel 函數用於關閉 MessageChannel 的兩個 port,釋放資源。

總結:

使用 Web Worker 可以有效地避免瀏覽器在非活動狀態下對計時器進行優化所導致的執行延遲問題。通過 MessageChannel,主執行緒和 Worker 之間可以方便地進行雙向通信,簡化了訊息的傳遞和管理。這種方式適用於對時間精確度要求較高的場景,例如需要準確觸發的後台任務或動畫。

更多範例

設定多組的timeout

// example A
setupCosTimeout(‘actionA’, ()=>console.log(‘Hello’), { timeout: 3000 } );
setupCosTimeout(‘actionB’, ()=>console.log(‘world’), { timeout: 5000 } );

// 3 秒後印出: Hello
// 5 秒後印出: world

메타데이터
post_id
207fc4c71d14
slug
costimeout-可信賴的-timeout-207fc4c71d14
url
https://medium.com/@dianwu/costimeout-%E5%8F%AF%E4%BF%A1%E8%B3%B4%E7%9A%84-timeout-207fc4c71d14
canonical_url
https://medium.com/@dianwu/costimeout-%E5%8F%AF%E4%BF%A1%E8%B3%B4%E7%9A%84-timeout-207fc4c71d14
author_url
https://medium.com/@dianwu
status
ok
fetched_at
2026-06-11 05:11:55