Web Workers i wielowątkowość JS
Dedicated Worker, Comlink (RPC), OffscreenCanvas (3D w workerze), SharedArrayBuffer + Atomics i Service Worker — ciężkie obliczenia poza main thread.
6 typów Web Workerów — porównanie
Dedicated, Shared, Service Worker, Audio Worklet, Comlink i OffscreenCanvas — API, dostęp i zastosowanie.
| Typ | API | Dostęp | Kiedy |
|---|---|---|---|
| Dedicated Worker | new Worker() | Jeden dokument | Heavy computation, image processing |
| Shared Worker | new SharedWorker() | Wiele tabs/windows | Shared state, WebSocket connection |
| Service Worker | navigator.serviceWorker.register() | Intercept requests | Offline, caching, push notifications |
| Audio Worklet | AudioWorkletNode | Web Audio API | Real-time audio processing, effects |
| Comlink RPC | wrap(worker) | Proxy abstraction | Prostszy API nad Worker, async/await |
| OffscreenCanvas | transferControlToOffscreen() | Canvas w workerze | Gry, 3D rendering, video bez blokowania |
Często zadawane pytania
Co to są Web Workers i kiedy używać?
Web Workers: JavaScript w osobnym wątku. Nie blokuje UI thread (main thread). Komunikacja przez messages (postMessage). Brak dostępu do DOM. Własny globalny scope (DedicatedWorkerGlobalScope). Kiedy używać: ciężkie obliczenia (sort 1M elementów). Image processing. Audio/video processing. Kryptografia. Parsing dużych JSON. Obliczenia matematyczne (AI, physics). Tworzenie workera: const worker = new Worker(new URL('./worker.ts', import.meta.url)). Vite: worker.ts automatycznie bundlowany. worker.postMessage({type: 'COMPUTE', data: largeArray}). worker.onmessage = (event) => { setResult(event.data.result) }. Worker kod: self.onmessage = (event) => { const {type, data} = event.data. if (type === 'COMPUTE') { const result = heavyComputation(data). self.postMessage({result}) } }. Typy workerów: Dedicated Worker — jeden dokument. Shared Worker — wiele dokumentów/tabs. Service Worker — intercept requests, offline, push. Module Worker: type: 'module'. import statements w worker. Vite: ?worker w imporcie. Transferable Objects: ArrayBuffer, ImageBitmap, MessagePort, OffscreenCanvas. Brak kopiowania — przesyłanie ownership. worker.postMessage({buffer}, [buffer]). SharedArrayBuffer: pamięć dzielona między threads. Atomics — synchronizacja. Wymaga COOP/COEP headers. Błędy: worker.onerror handler. ErrorEvent.message, filename, lineno. terminate(): zabij worker. Cleanup w useEffect return.
Comlink — prosty RPC dla Web Workers?
Comlink (Google Chrome Labs): abstrakcja nad Web Workers. RPC-like API. Async/await zamiast postMessage. Instalacja: npm install comlink. Worker strona: import {expose} from 'comlink'. const api = { async computeHeavy(data) { return doHeavyWork(data) }, async sortLarge(arr) { return arr.sort() } }. expose(api). Main strona: import {wrap} from 'comlink'. const worker = new Worker(new URL('./worker.ts', import.meta.url)). const api = wrap(worker). const result = await api.computeHeavy(bigData). Proxy: Comlink tworzy Proxy. Wywołania są async (nawet sync metody). Return values automatycznie transferowane. Klasy przez Comlink: expose(MyClass). const instance = await new RemoteClass(). await instance.method(). Proxy of proxy. Transfer helpers: transfer(data, [data.buffer]) — Transferable. proxy(callback) — callback z main do worker. Cancelable: releaseProxy() — cleanup. Typescript: typy automatycznie. Remote = Comlink.Remote. Ograniczenia: brak support dla DOM. Brak EventEmitter (callbacki przez proxy). Funkcje nie są serializable — użyj proxy(). React + Comlink hook: const workerRef = useRef(null). useEffect(() => { const worker = new Worker(...). workerRef.current = wrap(worker). return () => { worker.terminate() } }, []). useMemo hook dla heavy computations: const result = useMemo(() => api.compute(input), [input]). Ale uwaga: useMemo nie czeka na Promise. Potrzeba useState + useEffect. useWorker hook pattern: loading, error, result states.
OffscreenCanvas — rendering w Web Workerze?
OffscreenCanvas: Canvas rendering w Web Worker. Nie blokuje main thread podczas renderingu. Przekazywanie canvas do workera: const canvas = document.getElementById('myCanvas'). const offscreen = canvas.transferControlToOffscreen(). worker.postMessage({canvas: offscreen}, [offscreen]). W workerze: const canvas = event.data.canvas. const ctx = canvas.getContext('2d'). ctx.fillRect(...) — normalny Canvas API. Kiedy przydatne: gry (60fps rendering). Video processing. Complex charts. Image editing. Three.js w workerze: WebGL Context przez OffscreenCanvas. renderer = new THREE.WebGLRenderer({canvas: offscreenCanvas}). Pełny Three.js render loop w worker. requestAnimationFrame: w workerze nie ma requestAnimationFrame. Użyj setTimeout lub postMessage loop. Lub MessageChannel — high-frequency. Synchronizacja z main thread: worker wysyła rendered frame. Main wyświetla. Lub: ResizeObserver dla resize events. Fallback: nie wszystkie przeglądarki wspierają OffscreenCanvas. Sprawdź typeof OffscreenCanvas !== 'undefined'. Fallback do main thread. WebCodecs API: kodowanie/dekodowanie video w worker. VideoDecoder, VideoEncoder. EncodedVideoChunk. Bez FFmpeg.wasm overhead. Web Audio WorkletNode: AudioWorkletNode — audio processing w worker. addModule(url) — załaduj processor. createProcessor() — AudioWorkletProcessor. Niski latency, real-time audio. OPFS w workerze: Origin Private File System szybszy w worker. Synchroniczne API w dedykowanym worker. createSyncAccessHandle() — blokujący odczyt/zapis.
SharedArrayBuffer i Atomics — współdzielona pamięć między wątkami?
SharedArrayBuffer: bufor pamięci dzielony między wątkami. Brak kopiowania — zero-copy sharing. Wymaga COOP/COEP security headers: Cross-Origin-Opener-Policy: same-origin. Cross-Origin-Embedder-Policy: require-corp. Tworzenie: const sharedBuffer = new SharedArrayBuffer(1024). main: worker.postMessage({buffer: sharedBuffer}). worker: const view = new Int32Array(event.data.buffer). Oba mają referencję do tych samych bajtów. Atomics — synchronizacja: Atomics.add(typedArray, index, value) — atomowe dodawanie. Atomics.store, Atomics.load, Atomics.compareExchange. Atomics.wait(arr, 0, expectedValue) — blokujące czekanie (tylko w worker). Atomics.notify(arr, 0) — obudź oczekujące. Mutex pattern: Atomics.compareExchange(lockArray, 0, 0, 1) — lock. Atomics.store(lockArray, 0, 0) — unlock. Producer-Consumer: SharedArrayBuffer jako ring buffer. Atomics.wait + Atomics.notify. Lock-free algorithms. Bezpieczeństwo: Spectre mitigation — wyłączone domyślnie. COOP/COEP headers ponownie włączają. Jest JS: fake SharedArrayBuffer. Nie prawdziwe threads. Workerize: automatyczna konwersja funkcji do worker. import workerize from 'workerize'. const worker = workerize(() => { export function factorial(n) {return n <= 1 ? 1 : n * factorial(n-1)} }). await worker.factorial(10). Comlink vs workerize: Comlink — bardziej feature-rich. workerize — prostszy setup. Greenlet: pojedyncza funkcja jako worker. import greenlet from 'greenlet'. const asyncFn = greenlet(async (num) => { return expensiveCalc(num) }). await asyncFn(100).
Praktyczne zastosowania Web Workers w React i Next.js?
React + Web Workers patterns: useWorker hook: niestandardowy hook. State dla result, loading, error. worker.postMessage na wejście. onmessage aktualizuje state. Cleanup: worker.terminate() w useEffect. react-worker biblioteka: upraszcza. Vite Web Workers: import MyWorker from './worker?worker'. const worker = new MyWorker(). import.meta.url nie potrzebne. TypeScript: /// reference lib='webworker'. tsconfig: include worker files. lib: ['webworker']. Lub: declare var self: DedicatedWorkerGlobalScope. Next.js + Web Workers: Pages Router: new Worker(new URL('./worker', import.meta.url)). Sprawdź typeof window !== 'undefined'. App Router: 'use client' komponent. Worker działa tylko po stronie klienta. Practical examples: Wyszukiwarka offline: index dokumentów w worker. Fuse.js lub FlexSearch w worker. Main thread wysyła query. Worker zwraca wyniki. Image processing: Canvas -> ImageData -> worker. Grayscale, blur, filters. Worker -> ImageData -> Canvas. Data transformation: sortowanie 100k rekordów. Aggregacje. Statistyki. Bez blokowania UI. Compression: fflate.js (zlib) w worker. Kompresja pliku przed upload. Chunk processing. AI inference: ONNX Runtime Web w worker. TensorFlow.js w worker. Nie blokuj UI podczas inference. SQLite w OPFS w worker: sql.js lub sqlite-wasm. Pełne zapytania SQL w worker. Brak lag w UI. Pooling: wielokrotne workery. Przydział zadań. Nie za wiele — overhead. Zazwyczaj navigator.hardwareConcurrency / 2.
Powiązane artykuły
Skontaktuj się z nami
Porozmawiajmy o Twoim projekcie. Bezpłatna wycena w ciągu 24 godzin.
Wyślij zapytanie
Telefon
+48 790 814 814
Pon-Pt: 9:00 - 18:00
adam@fotz.pl
Odpowiadamy w ciągu 24h
Adres
Plac Wolności 16
61-739 Poznań
Godziny pracy
Wolisz porozmawiać?
Zadzwoń teraz i porozmawiaj z naszym specjalistą o Twoim projekcie.
Zadzwoń teraz