
May 07, 2025
Sử dụng Web Workers để xử lý tác vụ nặng trong trình duyệt
Tìm hiểu cách Web Workers giúp tách tác vụ nặng ra khỏi luồng chính nhằm tăng hiệu suất và trải nghiệm người dùng.
Giới thiệu
Trong các ứng dụng web hiện đại, hiệu suất là một yếu tố quan trọng ảnh hưởng trực tiếp đến trải nghiệm người dùng. Một trong những nguyên nhân chính gây ra tình trạng giật lag, hoặc block UI là do các tác vụ tính toán nặng (heavy tasks) được thực thi trực tiếp ngay trên (main thread).
Để giải quyết vấn đề này, JavaScript cung cấp giải pháp Web Workers, cho phép chạy các đoạn mã song song trong một luồng riêng biệt, giúp tránh xử lí trên main thread và tăng hiệu suất ứng dụng.
1. Web Workers là gì?
Web Workers là một API của trình duyệt cho phép bạn thực thi JavaScript trong một luồng riêng biệt với luồng chính của ứng dụng.
- Web Workers có thể giao tiếp với main thread thông qua
postMessage
. - Phù hợp để xử lý: tính toán nặng, phân tích dữ liệu, xử lý ảnh, nén file,...
2. Tại sao nên sử dụng Web Workers?
Lợi ích:
- Không làm block UI: vì không chạy trên luồng chính.
- Cải thiện UX: giao diện vẫn phản hồi trong khi xử lý tác vụ.
- Tăng hiệu suất cho các tác vụ tính toán phức tạp.
Khi không sử dụng Web Workers:
- UI dễ bị đơ khi xử lý JSON lớn, vòng lặp nhiều, hoặc xử lí thuật toán nặng.
- User có thể nhầm tưởng ứng dụng bị crash nếu không phản hồi.
3. Ví dụ về cách sử dụng Web Workers cho bài toán tính giai thừa
Giả sử bạn cần thực hiện phép tính giai thừa (n!
) với các số lớn – đây là một tác vụ có thể gây chậm hoặc "đơ" giao diện nếu xử lý trực tiếp trong main thread. Web Worker sẽ giúp tách việc xử lý ra luồng riêng, đảm bảo UI luôn mượt mà.
Chúng ta sẽ xây dựng một ví dụ đơn giản gồm:
- Một file Web Worker để tính giai thừa.
- Một component React (TSX) gồm ô input, button nhấn để gửi dữ liệu vào worker, và các thẻ p để hiển thị kết quả.
🧮 factorialWorker.ts
– File worker
self.onmessage = function (e) {
const number = e.data;
const result = factorial(number);
self.postMessage(result);
};
function factorial(n: number): BigInt {
let result = BigInt(1);
for (let i = 2; i <= n; i++) {
result *= BigInt(i);
}
return result;
}
export {};
🧑💻 FactorialCalculator.tsx
– React component
import React, { useState, useRef } from "react";
const FactorialCalculator: React.FC = () => {
const [input, setInput] = useState<number>(100000);
const [result, setResult] = useState<number | null>(null);
const [loading, setLoading] = useState(false);
const [totalTime, setTotalTime] = useState(0);
const workerRef = useRef<Worker | null>(null);
const handleCalculate = () => {
setResult(null);
setTotalTime(0);
setLoading(true);
const startTime = performance.now(); // Start timing
const worker = new Worker(new URL("@src/worker/factorialWorker", import.meta.url));
workerRef.current = worker;
worker.postMessage(input);
worker.onmessage = (e: MessageEvent<number>) => {
const endTime = performance.now(); // End timing
setTotalTime(endTime - startTime); // Calculate total time in milliseconds
setResult(e.data);
setLoading(false);
worker.terminate();
};
};
return (
<div>
<input
type="number"
value={input}
onChange={(e) => setInput(Number(e.target.value))}
placeholder="Nhập số nguyên dương"
/>
<button onClick={handleCalculate} disabled={loading}>
{loading ? "Đang tính..." : "Tính giai thừa"}
</button>
{result !== null && <p>Kết quả: {result}</p>}
{totalTime > 0 && <p>Thời gian tính toán: {totalTime.toFixed(3)} ms</p>}
</div>
);
};
export default FactorialCalculator;
Kết quả:
Khi không sử dụng Web Worker | Khi sử dụng Web Worker |
---|---|
![]() | ![]() |
Ta có thể thấy khi sử dụng web-worker, UI mượt mà hơn rất nhiều so với khi không sử dụng nó.
4. Lưu ý khi sử dụng web worker
- Không thể truy cập đến DOM từ trong worker file.
- Đảm bảo luồng giao tiếp
postMessage
rõ ràng và không gây race condition. - Có thể dùng
Comlink
để đơn giản hóa giao tiếp.
5. Khi nào nên dùng Web Workers?
Tình huống | Có nên dùng Worker? |
---|---|
Tính toán nặng như mã hóa, nén file | ✅ Cần |
Xử lý file JSON lớn (>1MB) | ✅ Cần |
Cập nhật UI đơn giản | ❌ Không cần |
Fetch API, gọi server | ❌ Không cần (dùng async/await cho lẹ :D) |
6. Các option nâng cao khác
- SharedWorker: chia sẻ worker giữa nhiều tab.
- Service Worker: xử lý request, cache – khác với Web Worker.
- OffscreenCanvas: render canvas trong worker (đặc biệt hữu ích với đồ họa).
Kết luận
Web Workers là công cụ mạnh mẽ giúp cải thiện hiệu suất của các ứng dụng web hiện đại. Việc tách biệt các tác vụ nặng ra khỏi luồng chính sẽ giúp UI luôn mượt mà, thân thiện với người dùng. Hãy cân nhắc sử dụng Web Workers khi bắt đầu thấy trình duyệt bị "lag" khi xử tác vụ phức tạp.
Using Web Workers to Handle Heavy Tasks in the Browser
Learn how Web Workers help offload heavy computations from the main thread to improve performance and user experience.
Introduction
In modern web applications, performance is a critical factor that directly affects the user experience. One of the main causes of lagging or UI blocking is due to heavy tasks being executed directly on the main thread.
To address this issue, JavaScript provides the Web Workers API, which allows running code in parallel in a separate thread, helping avoid blocking the main thread and improving application performance.
1. What are Web Workers?
Web Workers are a browser API that allows you to execute JavaScript in a separate thread from the main application thread.
- Web Workers can communicate with the main thread via
postMessage
. - Ideal for handling: heavy computation, data analysis, image processing, file compression, etc.
2. Why use Web Workers?
Benefits:
- No UI blocking: since it doesn't run on the main thread.
- Better UX: the UI remains responsive while the task is being processed.
- Improved performance for complex calculations.
Without Web Workers:
- The UI can easily freeze when processing large JSON files, long loops, or complex algorithms.
- Users may think the app crashed if there's no response.
3. Example: Using Web Workers to Calculate Factorial
Suppose you need to calculate factorial (n!
) of large numbers — this can be a task that slows down or freezes the UI if processed on the main thread. Web Workers help offload this task to a separate thread, ensuring a smooth UI.
We’ll build a simple example including:
- A Web Worker file to calculate factorial.
- A React (TSX) component with an input box, a button to send data to the worker, and
<p>
tags to display the result.
🧮 factorialWorker.ts
– Worker file
self.onmessage = function (e) {
const number = e.data;
const result = factorial(number);
self.postMessage(result);
};
function factorial(n: number): BigInt {
let result = BigInt(1);
for (let i = 2; i <= n; i++) {
result *= BigInt(i);
}
return result;
}
export {};
🧑💻 FactorialCalculator.tsx
– React component
import React, { useState, useRef } from "react";
const FactorialCalculator: React.FC = () => {
const [input, setInput] = useState<number>(100000);
const [result, setResult] = useState<number | null>(null);
const [loading, setLoading] = useState(false);
const [totalTime, setTotalTime] = useState(0);
const workerRef = useRef<Worker | null>(null);
const handleCalculate = () => {
setResult(null);
setTotalTime(0);
setLoading(true);
const startTime = performance.now(); // Start timing
const worker = new Worker(new URL("@src/worker/factorialWorker", import.meta.url));
workerRef.current = worker;
worker.postMessage(input);
worker.onmessage = (e: MessageEvent<number>) => {
const endTime = performance.now(); // End timing
setTotalTime(endTime - startTime); // Calculate total time in milliseconds
setResult(e.data);
setLoading(false);
worker.terminate();
};
};
return (
<div>
<input
type="number"
value={input}
onChange={(e) => setInput(Number(e.target.value))}
placeholder="Enter a positive integer"
/>
<button onClick={handleCalculate} disabled={loading}>
{loading ? "Calculating..." : "Calculate Factorial"}
</button>
{result !== null && <p>Result: {result}</p>}
{totalTime > 0 && <p>Calculation time: {totalTime.toFixed(3)} ms</p>}
</div>
);
};
export default FactorialCalculator;
Result:
Without Web Worker | With Web Worker |
---|---|
![]() | ![]() |
As you can see, using a Web Worker results in a much smoother UI compared to not using one.
4. Notes When Using Web Workers
- You cannot access the DOM from inside a worker file.
- Make sure
postMessage
communication is clear and avoids race conditions. - You can use
Comlink
to simplify communication.
5. When Should You Use Web Workers?
Scenario | Should You Use a Worker? |
---|---|
Heavy computation like encryption, file compression | ✅ Yes |
Processing large JSON files (>1MB) | ✅ Yes |
Simple UI updates | ❌ No |
Fetch API, server calls | ❌ No (just use async/await 😄) |
6. Other Advanced Options
- SharedWorker: share a worker across multiple tabs.
- Service Worker: handles requests, caching – different from Web Worker.
- OffscreenCanvas: render canvas inside a worker (great for graphics).
Conclusion
Web Workers are a powerful tool to improve performance in modern web applications. Offloading heavy tasks from the main thread helps keep the UI smooth and user-friendly. Consider using Web Workers when you notice your browser lagging during complex operations.