iT邦幫忙

2024 iThome 鐵人賽

DAY 13
2
JavaScript

可愛又迷人的 Web API系列 第 13

Day13. 使用 Web Workers API 走出自己的路

  • 分享至 

  • xImage
  •  

Web 應用程式能做到很多強大的功能,但也因此讓程式變得愈來愈複雜,靠著瀏覽器幫我們處理大量的資料和複雜的計算。雖然瀏覽器很強大,但當每個分頁的應用程式都很複雜時,就容易產生效能問題,甚至讓瀏覽器或分頁當機。想要解決這類的問題,我們可以使用 Web Workers API 讓應用程式在後台執行緒執行任務,從而保持主線的暢通,走出屬於自己的快速通道 XD。

Web Workers 的類型與使用情境

Web Workers API 可以讓 JavaScript 在獨立的執行緒中工作,而不會影響主執行緒的效能,以提升 Web 應用的回應速度和使用者體驗。

Web Workers 提供了在獨立執行緒中執行 JavaScript 的能力,以避免阻塞主執行緒。Web Workers 分為三種主要類型:Dedicated Workers、Shared Workers 和 Service Workers,每種類型都有特定的用途和特性,未來可以視需求選擇要使用的類型。

Dedicated Workers

Dedicated 的中文是專門、專用的意思,表示他只能給一個主執行緒使用,而他也是最常見的 Web Worker 類型,用於執行計算複雜或需要長時間處理的任務,例如資料處理、圖片處理,或複雜的演算法 ... 等等,所以很適合在單個頁面中使用,進行獨立的後台任務處理。

以下是一個簡單的範例,首先在 main.js 建立一個 Worker 實例,當 worker postMessage 時,可以用 onmessage 監聽接收,有一點 web socket 的感覺。

// main.js
const worker = new Worker('worker.js');
worker.onmessage = function (e) {
  console.log('主執行緒收到結果:', e.data);
};
worker.postMessage(10); // 發送消息到 Worker

接著建立 worker 檔案,透過 postMessage 將結果傳回給主執行緒

// worker.js
self.onmessage = function (e) {
  const result = e.data * 3.1415926; // 假設任務是做一個複雜的計算
  self.postMessage(result);
};

如果要終止 worker 服務,可以使用 terminate 方法:

worker.terminate();

和 web socket 相同,我們也能使用 onerror 取得錯誤處理函式,不管是在 worker 腳本或是主執行緒都能使用:

// main.js
worker.onerror = function (e) {
  console.log('Error in Worker:', e.message);
};


// worker.js
self.onerror = function (e) {
  console.log('Error in Worker:', e.message);
};

Dedicated Workers 的特色是:

  • 簡單好上手,適合處理單一任務
  • 只能被建立他的主執行緒(main.js)使用,不能跨頁面共享任務

透過以上特色不難發現,如果想要跨頁面共享任務,就必須用 Shared Workers。

Shared Workers

Shared Workers 可以讓多個主執行緒 (多個頁面或 iframe) 共享同一個 Worker 實例,可以同時被多個主執行緒存取使用,如果是同個域名下的多頁面或應用,需要共享後台任務處理的話,就非常適合使用 Shared Workers。

main.js 連接到 Shared Workers

// main.js
const worker = new SharedWorker('shared-worker.js');

worker.port.onmessage = function (e) {
    console.log('主執行緒收到結果:', e.data);
};

worker.port.start();
worker.port.postMessage({ source: 'main', message: 'Hello MUKI in main.js' });

event.js 也連接 Shared Workers

// event.js
const worker = new SharedWorker('shared-worker.js');

worker.port.onmessage = function (e) {
    console.log('主執行緒收到結果:', e.data);
};

worker.port.start();
worker.port.postMessage({ source: 'event', message: 'Hello MUKI in event.js' });

在發送消息時,跟 Dedicated Workers 的差別在於 Shared Workers 會使用 port 來傳遞消息

接著建立 shared-worker.js ,來處理 Shared Workers 邏輯。

// shared-worker.js
let connections = 0;

self.onconnect = function (e) {
  const port = e.ports[0];
  connections++;
  port.postMessage(`Worker 服務已連接,總連接數量:${connections}`);

  port.onmessage = function (event) {
    // 處理從各個連接發送過來的消息
    const message = `來源:${event.data.source},內容:${event.data.message}`;
    // 將消息發送回所有連接的 port
    self.clients.matchAll().then(clients => {
      clients.forEach(client => {
        client.postMessage(message);
    });
  });
};

  port.onclose = function () {
    connections--;
  };
};

特別注意的是,Shared Worker 必須遵守瀏覽器的同源政策,也就是必須在同一個域名和同一個 port 才能互相連接,共享同一個 Shared Worker 實例。

Service Workers

如果你有印象,會發現我已在 Web Notifications API 搭配 Service Workers 發送通知 介紹過 Service Workers 了。Service Workers 的特性就是處理網路的請求和快取資源,他們會在後台獨立運作,不會被頁面的生命週期影響,所以能在不開啟網頁,也就是應用離線的情況下繼續提供服務。

首先一樣要註冊 Service Workers:

// main.js
if ('serviceWorker' in navigator) {
  navigator.serviceWorker.register('/service-worker.js').then(function (registration) {
    console.log('註冊成功', registration);
  }).catch(function (error) {
    console.log('註冊失敗', error);
});
}

service-worker.js 中處理各種事件,例如安裝 (install),啟用 (activate),以及網路請求 (fetch):

self.addEventListener('install', function (event) {
  console.log('Service Worker 已安裝');
});

self.addEventListener('activate', function (event) {
  console.log('Service Worker 已啟用');
});

self.addEventListener('fetch', function (event) {
  console.log('Fetching:', event.request.url);
  event.respondWith(fetch(event.request).then(function (response) {
    return response;
  }).catch(function () {
    return new Response('網路請求失敗');
  }));
});

只要瀏覽器在有 install 且有 activate 的 Service Worker 作用域內做了任何的網路請求,fetch 事件就會自動觸發,觸發方式包括但不限於:

  • 第一次載入頁面:當瀏覽器載入頁面時,相關的資源請求如 HTML, CSS, JavaScript, 圖片... 等,都會觸發 fetch 事件
    https://mukiwu.github.io/web-api-demo/img/13-1.png
  • 切換路由:當有新的頁面請求時,也會觸發 fetch 事件
  • 動態請求:透過 JavaScript 發送的如 fetchXMLHttpRequest 也會觸發 fetch 事件。

小結

Web Workers API 的三種服務,都可以幫助我們在執行緒塞車的時候,多出一條小路給後面的車子(X)通過,以拿到分流的目的。

希望這篇文章能讓大家更了解 Web Workers API,有任何問題也歡迎留言討論。


上一篇
Day12. 用 Audio 和 Video API 打造自己的影音平台 II
下一篇
Day14. Web Workers API 的限制與效能優化處理
系列文
可愛又迷人的 Web API20
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言