+6

Sử dụng Web Workers để xử lý tác vụ nặng trong trình duyệt

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 threadtă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ì tác vụ không chạy trên main thread.
  • 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 khi xử lí 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 hoặc xử lí loop đệ quy nhiều vò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 BigInt – đâ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. Ở ví dụ này ta sử dụng Web Worker để tách hàm xử lí factorial ra luồng một luồng riêng không liên quan đến main thread

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 file, 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 không những không bị block mà còn mượt mà hơn rất nhiều so với khi không sử dụng. Đơn giản hơn thì ta có thể sử dụng Thư viện https://useworker.js.org/ để code nhanh hơn mà không cần tách hàm như trê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ẹ 😄)

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 chạy những tác vụ phức tạp.

Xem thêm các bài viết tại: vunguyenit.site


All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí