Web Workers là một API JavaScript cho phép chạy các script trong background threads (luồng nền), tách biệt hoàn toàn với main thread của trình duyệt.
┌─────────────────────────────────────────────────────────┐
│ MAIN THREAD │
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
│ │ DOM │ │ UI │ │ Event │ │ User │ │
│ │ Render │ │ Updates │ │ Handlers│ │ Input │ │
│ └─────────┘ └─────────┘ └─────────┘ └─────────┘ │
└───────────────────────┬─────────────────────────────────┘
│ postMessage / onmessage
┌───────────────────────▼─────────────────────────────────┐
│ WEB WORKER THREAD │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ Heavy │ │ Data │ │ Complex │ │
│ │ Computation │ │ Processing │ │ Algorithms │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
└─────────────────────────────────────────────────────────┘
JavaScript mặc định là single-threaded, nghĩa là:
Web Workers giải quyết vấn đề này bằng cách:
Web Workers giao tiếp với main thread thông qua hệ thống message passing:
// Main Thread
const worker = new Worker('worker.js');
// Gửi message đến worker
worker.postMessage({ type: 'PROCESS', data: largeDataset });
// Nhận message từ worker
worker.onmessage = (event) => {
console.log('Kết quả:', event.data);
};
// Xử lý lỗi
worker.onerror = (error) => {
console.error('Worker error:', error.message);
};
// worker.js (Worker Thread)
self.onmessage = (event) => {
const { type, data } = event.data;
if (type === 'PROCESS') {
const result = processData(data); // Heavy computation
self.postMessage(result);
}
};
Workers chạy trong global context riêng biệt:
window, document, DOMself object thay vì windowfetch, IndexedDB, WebSockets, WebCryptoĐặc điểm:
// Tạo Dedicated Worker
const worker = new Worker('dedicated-worker.js');
Đặc điểm:
// Tạo Shared Worker
const sharedWorker = new SharedWorker('shared-worker.js');
// Giao tiếp qua port
sharedWorker.port.start();
sharedWorker.port.postMessage('Hello');
sharedWorker.port.onmessage = (e) => console.log(e.data);
// shared-worker.js
const connections = [];
self.onconnect = (e) => {
const port = e.ports[0];
connections.push(port);
port.onmessage = (event) => {
// Broadcast đến tất cả connections
connections.forEach(p => p.postMessage(event.data));
};
port.start();
};
Đặc điểm:
// Đăng ký Service Worker
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('/sw.js')
.then(reg => console.log('SW registered'))
.catch(err => console.log('SW registration failed'));
}
| Đặc điểm | Dedicated | Shared | Service |
|---|---|---|---|
| Phạm vi | 1 script | Nhiều scripts | Toàn domain |
| Lifecycle | Theo trang | Theo connections | Độc lập |
| Use case | Heavy computation | Share state | Offline/Caching |
| Network intercept | ❌ | ❌ | ✅ |
// Cách 1: External file
const worker = new Worker('worker.js');
// Cách 2: Inline với Blob (ES6+)
const workerCode = `
self.onmessage = (e) => {
const result = e.data * 2;
self.postMessage(result);
};
`;
const blob = new Blob([workerCode], { type: 'application/javascript' });
const worker = new Worker(URL.createObjectURL(blob));
// Cách 3: Module Worker (modern browsers)
const worker = new Worker('worker.js', { type: 'module' });
// Khởi tạo
const worker = new Worker('worker.js');
// Gửi message
worker.postMessage(data);
// Nhận message
worker.onmessage = (event) => { /* ... */ };
// hoặc
worker.addEventListener('message', (event) => { /* ... */ });
// Xử lý lỗi
worker.onerror = (error) => { /* ... */ };
// Terminate worker
worker.terminate();
// Nhận message từ main thread
self.onmessage = (event) => {
const data = event.data;
// Process...
};
// Gửi message về main thread
self.postMessage(result);
// Import scripts khác
importScripts('helper1.js', 'helper2.js');
// Tự đóng worker
self.close();
Để tối ưu hiệu suất với dữ liệu lớn, sử dụng Transferable Objects:
// Main Thread
const buffer = new ArrayBuffer(1024 * 1024 * 100); // 100MB
// Transfer ownership (không copy)
worker.postMessage(buffer, [buffer]);
// Sau đây buffer.byteLength === 0 (đã transfer)
// Worker Thread
self.onmessage = (e) => {
const receivedBuffer = e.data;
// Process buffer...
// Transfer ngược lại
self.postMessage(receivedBuffer, [receivedBuffer]);
};
Các Transferable Objects:
ArrayBufferMessagePortImageBitmapOffscreenCanvasVấn đề: Xử lý ảnh (resize, filter, compression) block UI
Giải pháp:
// main.js
const imageWorker = new Worker('image-worker.js');
function processImage(imageData) {
return new Promise((resolve) => {
imageWorker.postMessage({
type: 'APPLY_FILTER',
imageData: imageData,
filter: 'grayscale'
});
imageWorker.onmessage = (e) => resolve(e.data);
});
}
// image-worker.js
self.onmessage = (e) => {
const { type, imageData, filter } = e.data;
if (type === 'APPLY_FILTER') {
const processed = applyFilter(imageData, filter);
self.postMessage(processed);
}
};
function applyFilter(imageData, filter) {
const data = imageData.data;
if (filter === 'grayscale') {
for (let i = 0; i < data.length; i += 4) {
const avg = (data[i] + data[i+1] + data[i+2]) / 3;
data[i] = data[i+1] = data[i+2] = avg;
}
}
return imageData;
}
Vấn đề: Parse/transform dataset lớn (CSV, JSON) gây freeze UI
Giải pháp:
// data-worker.js
self.onmessage = async (e) => {
const { type, data } = e.data;
switch (type) {
case 'PARSE_CSV':
const rows = parseCSV(data);
self.postMessage({ type: 'PARSED', rows });
break;
case 'AGGREGATE':
const result = aggregateData(data);
self.postMessage({ type: 'AGGREGATED', result });
break;
case 'FILTER':
const filtered = data.filter(row => row.value > 100);
self.postMessage({ type: 'FILTERED', filtered });
break;
}
};
function parseCSV(csvString) {
const lines = csvString.split('\n');
const headers = lines[0].split(',');
return lines.slice(1).map(line => {
const values = line.split(',');
return headers.reduce((obj, header, i) => {
obj[header.trim()] = values[i]?.trim();
return obj;
}, {});
});
}
Vấn đề: Mã hóa/giải mã dữ liệu là tác vụ CPU-intensive
Giải pháp:
// crypto-worker.js
self.onmessage = async (e) => {
const { type, data, key } = e.data;
try {
if (type === 'ENCRYPT') {
const encrypted = await encryptData(data, key);
self.postMessage({ success: true, result: encrypted });
} else if (type === 'DECRYPT') {
const decrypted = await decryptData(data, key);
self.postMessage({ success: true, result: decrypted });
}
} catch (error) {
self.postMessage({ success: false, error: error.message });
}
};
async function encryptData(data, key) {
const encoder = new TextEncoder();
const dataBuffer = encoder.encode(JSON.stringify(data));
const cryptoKey = await crypto.subtle.importKey(
'raw',
encoder.encode(key),
{ name: 'AES-GCM' },
false,
['encrypt']
);
const iv = crypto.getRandomValues(new Uint8Array(12));
const encrypted = await crypto.subtle.encrypt(
{ name: 'AES-GCM', iv },
cryptoKey,
dataBuffer
);
return { iv, encrypted };
}
Vấn đề: Thuật toán phức tạp (Machine Learning, Path Finding, Physics Simulation)
Giải pháp:
// computation-worker.js
self.onmessage = (e) => {
const { type, params } = e.data;
switch (type) {
case 'FIBONACCI':
const fib = fibonacci(params.n);
self.postMessage({ result: fib });
break;
case 'PRIME_NUMBERS':
const primes = findPrimes(params.limit);
self.postMessage({ result: primes });
break;
case 'SORT_LARGE_ARRAY':
const sorted = mergeSort(params.array);
self.postMessage({ result: sorted });
break;
}
};
// Thuật toán tìm số nguyên tố
function findPrimes(limit) {
const sieve = new Array(limit + 1).fill(true);
sieve[0] = sieve[1] = false;
for (let i = 2; i * i <= limit; i++) {
if (sieve[i]) {
for (let j = i * i; j <= limit; j += i) {
sieve[j] = false;
}
}
}
return sieve.reduce((primes, isPrime, num) => {
if (isPrime) primes.push(num);
return primes;
}, []);
}
Vấn đề: Fetch và xử lý dữ liệu API lớn gây lag
Giải pháp:
// api-worker.js
self.onmessage = async (e) => {
const { type, url, options } = e.data;
try {
if (type === 'FETCH_AND_PROCESS') {
// Fetch data
const response = await fetch(url, options);
const data = await response.json();
// Process in background
const processed = processData(data);
// Report progress
self.postMessage({
type: 'PROGRESS',
message: 'Processing complete'
});
// Return result
self.postMessage({
type: 'COMPLETE',
result: processed
});
}
} catch (error) {
self.postMessage({
type: 'ERROR',
error: error.message
});
}
};
function processData(data) {
// Transform, aggregate, filter...
return data.map(item => ({
...item,
computed: item.value * 2,
timestamp: new Date().toISOString()
}));
}
Vấn đề: Sync dữ liệu real-time giữa nhiều users
Giải pháp với Shared Worker:
// collaboration-shared-worker.js
const clients = new Set();
let sharedState = {};
self.onconnect = (e) => {
const port = e.ports[0];
clients.add(port);
// Gửi state hiện tại cho client mới
port.postMessage({ type: 'INIT', state: sharedState });
port.onmessage = (event) => {
const { type, data } = event.data;
if (type === 'UPDATE') {
// Cập nhật shared state
sharedState = { ...sharedState, ...data };
// Broadcast đến tất cả clients
clients.forEach(client => {
client.postMessage({ type: 'STATE_CHANGED', state: sharedState });
});
}
};
port.start();
};
// ❌ Bad: Gửi nhiều messages nhỏ
for (let i = 0; i < 1000; i++) {
worker.postMessage({ index: i, value: data[i] });
}
// ✅ Good: Batch data
worker.postMessage({
type: 'BATCH_PROCESS',
data: data.slice(0, 1000)
});
// ❌ Bad: Copy buffer (chậm với data lớn)
worker.postMessage(largeBuffer);
// ✅ Good: Transfer ownership
worker.postMessage(largeBuffer, [largeBuffer]);
class WorkerPool {
constructor(workerScript, poolSize = 4) {
this.workers = [];
this.taskQueue = [];
this.availableWorkers = [];
for (let i = 0; i < poolSize; i++) {
const worker = new Worker(workerScript);
this.workers.push(worker);
this.availableWorkers.push(worker);
}
}
execute(task) {
return new Promise((resolve, reject) => {
const runTask = (worker) => {
worker.onmessage = (e) => {
resolve(e.data);
this.releaseWorker(worker);
};
worker.onerror = (e) => {
reject(e);
this.releaseWorker(worker);
};
worker.postMessage(task);
};
if (this.availableWorkers.length > 0) {
runTask(this.availableWorkers.pop());
} else {
this.taskQueue.push({ task, resolve, reject, runTask });
}
});
}
releaseWorker(worker) {
if (this.taskQueue.length > 0) {
const { runTask } = this.taskQueue.shift();
runTask(worker);
} else {
this.availableWorkers.push(worker);
}
}
terminate() {
this.workers.forEach(w => w.terminate());
}
}
// Sử dụng
const pool = new WorkerPool('computation-worker.js', 4);
const results = await Promise.all([
pool.execute({ type: 'TASK_1', data: data1 }),
pool.execute({ type: 'TASK_2', data: data2 }),
pool.execute({ type: 'TASK_3', data: data3 }),
]);
// Main Thread
worker.onerror = (error) => {
console.error('Worker error:', error.message);
console.error('File:', error.filename);
console.error('Line:', error.lineno);
// Restart worker nếu cần
worker.terminate();
worker = new Worker('worker.js');
};
// Worker Thread
self.onmessage = (e) => {
try {
const result = riskyOperation(e.data);
self.postMessage({ success: true, result });
} catch (error) {
self.postMessage({
success: false,
error: error.message,
stack: error.stack
});
}
};
// Terminate worker khi không cần
useEffect(() => {
const worker = new Worker('worker.js');
return () => {
worker.terminate(); // Cleanup khi component unmount
};
}, []);
// Worker Thread - Sẽ ERROR
self.onmessage = () => {
// ❌ Không có document
document.getElementById('app'); // ReferenceError
// ❌ Không có window
window.localStorage; // ReferenceError
};
Workers phải được load từ cùng origin với trang chủ.
// ❌ Không thể load worker từ CDN khác domain
const worker = new Worker('https://cdn.example.com/worker.js'); // Error
// ❌ Không nên dùng worker cho task nhỏ
// Overhead của việc tạo worker + serialize message > thời gian xử lý
// Task này nên chạy trên main thread
const sum = numbers.reduce((a, b) => a + b, 0);
Có thể sử dụng:
fetch, XMLHttpRequestWebSocketsIndexedDBWebCryptosetTimeout, setIntervalPromise, async/awaitKHÔNG thể sử dụng:
DOM API (document, window)localStorage, sessionStoragealert(), confirm(), prompt()// hooks/useWorker.js
import { useEffect, useRef, useCallback, useState } from 'react';
export function useWorker(workerScript) {
const workerRef = useRef(null);
const [isLoading, setIsLoading] = useState(false);
const [result, setResult] = useState(null);
const [error, setError] = useState(null);
useEffect(() => {
workerRef.current = new Worker(workerScript);
workerRef.current.onmessage = (e) => {
setResult(e.data);
setIsLoading(false);
};
workerRef.current.onerror = (e) => {
setError(e.message);
setIsLoading(false);
};
return () => {
workerRef.current?.terminate();
};
}, [workerScript]);
const postMessage = useCallback((data) => {
setIsLoading(true);
setError(null);
workerRef.current?.postMessage(data);
}, []);
return { postMessage, result, isLoading, error };
}
// Sử dụng
function DataProcessor() {
const { postMessage, result, isLoading } = useWorker('/workers/data-worker.js');
const handleProcess = () => {
postMessage({ type: 'PROCESS', data: largeDataset });
};
return (
<div>
<button onClick={handleProcess} disabled={isLoading}>
{isLoading ? 'Processing...' : 'Process Data'}
</button>
{result && <pre>{JSON.stringify(result, null, 2)}</pre>}
</div>
);
}
// Vite - Native support
const worker = new Worker(
new URL('./worker.js', import.meta.url),
{ type: 'module' }
);
// Webpack 5 - Native support
const worker = new Worker(
new URL('./worker.js', import.meta.url)
);
// worker.types.ts
interface WorkerMessage {
type: 'PROCESS' | 'CANCEL';
data?: unknown;
}
interface WorkerResponse {
success: boolean;
result?: unknown;
error?: string;
}
// data-worker.ts
const ctx: Worker = self as unknown as Worker;
ctx.onmessage = (e: MessageEvent<WorkerMessage>) => {
const { type, data } = e.data;
if (type === 'PROCESS') {
try {
const result = processData(data);
ctx.postMessage({ success: true, result } as WorkerResponse);
} catch (error) {
ctx.postMessage({
success: false,
error: (error as Error).message
} as WorkerResponse);
}
}
};
| ✅ Nên dùng | Ví dụ |
|---|---|
| Xử lý dữ liệu lớn | Parse CSV/JSON > 1MB |
| Tính toán phức tạp | Machine Learning, Pathfinding |
| Xử lý media | Image/Video processing |
| Encryption | End-to-end encryption |
| Real-time sync | Collaborative editing |
| ❌ Không nên | Lý do |
|---|---|
| DOM manipulation | Workers không truy cập được DOM |
| Task < 50ms | Overhead > benefit |
| Simple calculations | Không cần thiết |
| Heavy UI updates | Vẫn phải về main thread |