Giới thiệu API Web Worker trong JavaScript

Khi lập trình, dev không thể bắt người dùng phải chờ từng tác vụ này thực hiện xong mới được thực hiện tác vụ khác được. Nhất là các tác vụ mất nhiều thời gian như xử lý ảnh, file có dung lượng lớn...Và với lập trình viên, chắc hẳn các bạn không lạ gì khái niệm background job hay worker.

Trong JavaScript cũng có khái niệm này, bài viết này mình tham khảo từ bài viết An Introduction to Web Workers JavaScript API.

Web Workers là một API JavaScript, cho phép bạn chạy các script trong một thread riêng biệt từ một trang chính.

Link API: Web Worker.

API Web Workers đã được hỗ trợ trong hầu hết các browser hiện nay, bạn có thể xem thêm chi tiết ở CanIUse docs. Trước khi đi vào cụ thể, hãy cùng xem qua một vài kịch bản, khi bạn có lẽ sẽ cần ử dụng API này.

Kịch bản sử dụng

Giả sử bạn đang có một lệnh nạp và xử lý một file nào đó; nếu file đó có kích thước lớn thì sẽ mất nhiều thời gian xử lý. Điều này có thể sẽ cản trở các lệnh được gọi đến sau đó.

Tuy nhiên, nếu chuyển xuống xử lý thành luồng ngầm (background) bên dưới thì các lệnh khác sẽ không bị block cho đến khi nào xử lý file của bạn chạy xong.

Tương tự, giả sử có một form rất lớn, gồm nhiều tab, khi cập nhật ở tab này thì sẽ gây ra ảnh hưởng đến các tab khác. Nếu việc cập nhật một tab mất một khoảng thời gian thì người dùng không thể liên tục sử dụng tab đó mà không có sự kiện đang được giữ.

Vì người dùng không thể nhìn thấy các tab khác khi đang điền thông tin vào một tab cụ thể. Trường hợp này cũng có thể sử dụng worker như ở trên.

Như vậy, khi gặp trường hợp mà một câu lệnh sẽ khóa một người dùng sử dụng giao diện cho đến khi thực hiện xong như 2 trường hợp trên thì ta có thể chuyển qua sử dụng worker.

Phạm vi và kiểu của worker

API Web Worker có lẽ là một trong nhưng API sử dụng đơn giản nhất. Nó có các phương pháp khá đơn giản để khởi tạo worker và gọi đến các worker đó.

Phạm vi toàn cục của một luồng worker là trong ngữ cảnh khác so với luồng chính. Bạn không thể gọi đến các phương thức và thuộc tính của đối tượng window như alert()... hay là bạn cũng không thể thay đổi DOM trực tiếp từ một luồng worker.

Tuy nhiên, bạn có thể sử dụng nhiều API nằm dưới window như Promise, Fetch... từ một luồng worker. Bạn có thể xem danh sách các API đó tại đây.

Bạn cũng có thể có nhiều luồng worker lồng nhau, một luồng worker được khởi tạo ngay bên trong một worker khác, khi đó ta gọi là subworker.

Cũng có nhiều kiểu của worker, có 2 kiểu chính là dedicatedshared.

Trong bài viết này, các ví dụ sẽ là dedicated - kiểu phổ biến nhất.

API methods

Biểu đồ bên dưới là các phương thức chính cấu thành nên API Web Worker.

  • Worker() => khởi tạo một luồng worker dedicated và trả về một đối tượng tham chiếu của nó. Sau đó, bạn có thể sử dụng các đối tượng này để làm việc với từng worker cụ thể.

  • postMessage() => Phương thức được sử dụng trong cả luồng chính và luồng worker để gửi dữ liệu giữa 2 bên với nhau. Một bên gửi dữ liệu thì bên kia sẽ nhận bằng xử lý sự kiện onmessage.

  • terminate() => Phương thức để dừng một worker từ luồng chính, phương thức này thực hiện ngay lập tức, bất kỳ lệnh nào đang thực hiện hay đang chờ cũng sẽ đều bị dùng lại hết. Phương thức close() cũng thực hiện việc tương tự nhưng được gọi từ một worker để dừng chính nó.

Code ví dụ

Kịch bản chính nằm trong thẻ <script> của file index.html, trong khi worker nằm trong file worker.js.

<button>Click me</button>
<script>
<!-- main script goes here -->
</script>

Khởi tạo một worker từ luồng chính như sau:

<script>
  w = new Worker('worker.js');
</script>

Phương thức Worker() sử dụng path của file chứa worker là một argument.

Tiếp đó, ta thêm xử lý sự kiện cho sự kiện onmessage cho worker mới được tạo ở trên để nhận data từ nó. Thuộc tính data của sự kiện e chính là nơi dữ data nhận được.

<script>
  w = new Worker('worker.js');
  w.onmessage = (e)=>{
    console.log(`Received from worker: ${e.data}`);
  }
</script>

Bây giờ ta sử dụng phương thức postMessage() để gửi data đến luồng worker. Phương thức này có thể có 2 argument:

  • arg đầu tiên có thể là bất kỳ kiểu dữ liệu (string, array,...) nào.
  • arg thứ 2 không bắt buộc phải có, nó là mảng các đối tượng có thể được sử dụng trong luồng worker.

Ta gửi data đến worker bằng cách click button.

<script>
  document.querySelector('button').onclick = ()=>{
    w.postMessage('john');
  }
</script>

Trong worker, ta cần xử lý sự kiện onmessage để nhận data được gửi đến, nối xác string nhận được, xử lý rồi trả kết quả cuối cùng về cho luồng chính.

File worker.js như sau:

console.info('worker created');
onmessage = (e)=>{
  postMessage(`Hi ${e.data}`);
}

Không cần một đối tượng tham chiếu trong luồng worker để trỏ đến luồng chính của bạn.

Ở ví dụ trên, code sẽ chạy như sau. Khi Browser load index.html, console sẽ show ra dòng worker created ngay khi worker được khởi tạo.

Khi bạn click vào button, bạn sẽ nhận được dòng eceived from worker: Hi john trong console, dòng này là kết quả trả về sau khi worker đã làm việc.

Tham khảo


Cám ơn bạn đã theo dõi bài viết này!


All Rights Reserved