Tính đơn luồng của javascript?
Trong phát triển web hiện đại, hiệu năng là một yếu tố quan trọng. Khi ứng dụng cần xử lý các tác vụ phức tạp, như tính toán nặng hay xử lý dữ liệu lớn, JavaScript với mô hình single-threaded (một luồng) thường khiến giao diện bị chậm hoặc "đơ". Để giải quyết vấn đề này, Web Workers ra đời, cho phép bạn thực thi mã JavaScript trong các luồng riêng biệt, giữ cho giao diện luôn mượt mà.
Web worker là gì?
Web Worker là một API trong JavaScript cho phép bạn chạy mã JavaScript trong một luồng riêng biệt. Điều này đồng nghĩa với việc bạn có thể xử lý các tác vụ phức tạp như tính toán nặng, xử lý dữ liệu, hoặc gửi/yêu cầu API, mà không gây nghẹt hoặc "đơ" giao diện.
Cách web worker hoạt động
Hãy cùng mình xem cách sử dụng cũng như cách hoạt động của web worker qua ví dụ tính số Fibonacci dưới đây nhé.
//Main thread
<body>
<h1>Fibonacci Calculator (Using Worker)</h1>
<button id="start">Calculate Fibonacci</button>
<div id="result">Result: </div>
<div id="status">Status: Ready</div>
<script>
const worker = new Worker('worker.js');
document.getElementById('start').addEventListener('click', () => {
const resultDiv = document.getElementById('result');
const statusDiv = document.getElementById('status');
statusDiv.textContent = "Calculating...";
const start = performance.now();
worker.postMessage(100);
worker.onmessage = (event) => {
const end = performance.now();
resultDiv.textContent = `Result: ${event.data}`;
statusDiv.textContent = `Execution time: ${(end - start).toFixed(2)}ms`;
};
});
</script>
</body>
// Worker thread
self.onmessage = function (event) {
const n = event.data;
const result = calculateFibonacci(n);
self.postMessage(result);
};
function calculateFibonacci(n) {
if (n <= 1) return n;
return calculateFibonacci(n - 1) + calculateFibonacci(n - 2);
}
Khởi tạo Worker
Đầu tiên, chúng ta cần khởi tạo một Worker bằng cách tạo một đối tượng Worker mới trong mã JavaScript của main thread. Worker này sẽ thực thi script từ một file JavaScript riêng biệt, ví dụ worker.js.
const worker = new Worker('worker.js');
Khi worker được khởi tạo, một thread mới sẽ được tạo ra để thực thi script trong worker.js
Gửi tin nhắn tới Worker
Sau khi worker được khởi tạo, luồng chính có thể gửi tin nhắn đến worker để yêu cầu thực hiện một tác vụ nào đó. Điều này được thực hiện thông qua phương thức postMessage(). Trong ví dụ mình gửi message là 100 để worker tính toán Fibonacci của 100 bằng câu lệnh worker.postMessage(100);
Lắng nghe yêu cầu từ Main thread
Sau khi gửi yêu cầu đi từ main thread, tất nhiên worker thread cần có cơ chế để nhận yêu cầu đó. Trong worker thread, mình đã cài đặt việc lắng nghe bằng self.onmessage = function (event) {...}
Xử lý dữ liệu trên Worker thread
Bây giờ, ta có thể lấy dữ liệu yêu cầu const n = event.data;
rồi đem đi tính toán ở trên Worker thread thay vì main thread. Chính vì vậy mà mọi hoạt động UI ở trên main thread vẫn sẽ hoạt động bình thường.
Gửi kết quả trả về Main thread
Sau khi tính toán ra kết quả, ta sẽ cho Worker thread gửi một message ngược lại cho Main thread self.postMessage(result);
Main thread nhận kết quả trả về
Để nhận kết quả trả về từ Worker thread, trên Main thread, ta cần lắng nghe trên instance worker. worker.onmessage = (event) => {..}
Bây giờ ta có thể dùng kế quả đó cho các xử lý khác mà Main thread muốn.
Hạn chế của Web worker
Mặc dù Web Worker mang lại nhiều lợi ích, nhưng nó cũng có một số hạn chế cần lưu ý khi sử dụng. Dưới đây là những hạn chế chính của Web Worker:
Không có quyền truy cập vào DOM
Web Workers chạy trong một luồng riêng biệt, do đó chúng không thể trực tiếp truy cập hoặc thay đổi DOM (Document Object Model) của trang web. Nếu bạn muốn thay đổi giao diện người dùng, bạn sẽ phải gửi thông điệp (message) từ Web Worker đến luồng chính, và sau đó luồng chính mới có thể thực hiện thao tác trên DOM. Điều này tạo ra một số phức tạp trong việc đồng bộ hóa dữ liệu giữa worker và giao diện người dùng.
Khó khăn trong việc giao tiếp giữa các Worker và luồng chính
Web Worker giao tiếp với luồng chính thông qua cơ chế gửi và nhận tin nhắn (message-passing). Điều này có thể khá hạn chế, vì bạn không thể truyền trực tiếp các đối tượng phức tạp giữa các worker và luồng chính; thay vào đó, bạn chỉ có thể gửi dữ liệu serializable (có thể tuần tự hóa thành chuỗi), như chuỗi văn bản, số, mảng, hoặc đối tượng đơn giản.
Ngoài ra còn một số vấn đề khác như là việc xử lý bất đồng bộ, chiếm dụng CPU, debug,...
Kết luận
Trên đây là một bài viết đơn giản để mình cùng các bạn làm quen với Web Worker và cách sử dụng nó như một biện pháp cho các tác vụ nặng tính toán. Còn nhiều thứ hay ho mà bạn có thể làm với worker không chỉ đơn thuần là xử lý các tác vụ nặng. Bạn cũng có thể tìm hiểu về các loại Worker khác nhau như Shared Worker, Service Worker,...
Mozilla cũng có 1 series về các worker các bạn cũng có thể tham khảo tại đây
Notion cũng ứng dụng Web Worker cùng các công nghệ khác như Web Assembly để tăng tốc