Tạo luồng web bằng trình chạy mô-đun

Giờ đây, việc chuyển phần công việc nặng nhọc vào các luồng trong nền nay trở nên dễ dàng hơn nhờ các mô-đun JavaScript trong nhân viên web.

JavaScript là đơn luồng, tức là mỗi lần chỉ có thể thực hiện một thao tác. Đây là trực quan và hoạt động tốt trong nhiều trường hợp trên web, nhưng có thể trở nên khó khăn khi chúng ta cần thực hiện các công việc khó khăn như xử lý dữ liệu, phân tích cú pháp, tính toán hoặc phân tích. Khi ngày càng nhiều các ứng dụng phức tạp được phân phối trên web, nên nhu cầu sử dụng đa luồng ngày càng tăng đang xử lý.

Trên nền tảng web, yếu tố nguyên gốc chính để phân luồng và song song là trường Web Workers API. Worker là một yếu tố trừu tượng nhẹ ở bên trên hệ điều hành luồng hiển thị thông báo truyền API để giao tiếp liên chuỗi. Điều này có thể cực kỳ hữu ích khi thực hiện các phép tính tốn kém hoặc hoạt động trên các tập dữ liệu lớn, cho phép luồng chính chạy trơn tru trong khi thực hiện các thao tác tiêu tốn nhiều tài nguyên trên một hoặc nhiều luồng trong nền.

Dưới đây là ví dụ điển hình về cách sử dụng trình thực thi, trong đó tập lệnh trình thực thi theo dõi các thông báo từ và phản hồi bằng cách gửi lại tin nhắn của riêng mình:

page.js:

const worker = new Worker('worker.js');
worker.addEventListener('message', e => {
  console.log(e.data);
});
worker.postMessage('hello');

worker.js:

addEventListener('message', e => {
  if (e.data === 'hello') {
    postMessage('world');
  }
});

Web Worker API đã có sẵn trong hầu hết các trình duyệt trong hơn mười năm. Trong khi đó có nghĩa là worker có trình duyệt hỗ trợ tốt và được tối ưu hoá hiệu quả, điều đó cũng có nghĩa là họ có trước các mô-đun JavaScript khác. Vì không có hệ thống mô-đun khi worker được thiết kế, nên API để tải mã vào một worker và việc soạn tập lệnh vẫn tương tự như tập lệnh đồng bộ và phổ biến nhất trong năm 2009.

Nhật ký: worker phiên bản cũ

Hàm khởi tạo Worker lấy một giá trị cổ điển tập lệnh. URL này là liên quan đến URL tài liệu. Hàm này ngay lập tức trả về tham chiếu đến thực thể worker mới, hiển thị giao diện thông báo cũng như phương thức terminate() ngay lập tức dừng và huỷ bỏ worker.

const worker = new Worker('worker.js');

Hàm importScripts() có sẵn trong trình chạy web để tải mã bổ sung, nhưng hàm này tạm dừng việc thực thi worker để tìm nạp và đánh giá từng tập lệnh. Hàm này cũng thực thi các tập lệnh ở phạm vi toàn cầu như thẻ <script> cổ điển, tức là các biến trong một tập lệnh có thể bị ghi đè bởi biến trong một biến khác.

worker.js:

importScripts('greet.js');
// ^ could block for seconds
addEventListener('message', e => {
  postMessage(sayHello());
});

greet.js:

// global to the whole worker
function sayHello() {
  return 'world';
}

Vì lý do này, trước đây, nhân viên web đã áp đặt một hiệu ứng quá lớn lên kiến trúc của một . Các nhà phát triển đã phải tạo ra những công cụ và giải pháp thông minh để có thể sử dụng nhân viên web mà không từ bỏ các phương pháp phát triển hiện đại. Ví dụ: các trình tạo gói như webpack nhúng một phương thức triển khai trình tải mô-đun nhỏ vào mã được tạo sử dụng importScripts() để tải mã, nhưng gói mô-đun trong các hàm để tránh xung đột biến và mô phỏng nhập và xuất phần phụ thuộc.

Nhập trình thực thi mô-đun

Một chế độ mới dành cho nhân viên làm việc trên web với lợi ích về công thái học và hiệu suất của JavaScript mô-đun đang được vận chuyển trong Chrome 80, được gọi là trình thực thi mô-đun. Chiến lược phát hành đĩa đơn Hàm khởi tạo Worker hiện chấp nhận tuỳ chọn {type:"module"} mới, tuỳ chọn này thay đổi quá trình tải tập lệnh và lệnh thực thi để khớp với <script type="module">.

const worker = new Worker('worker.js', {
  type: 'module'
});

Vì trình chạy mô-đun là các mô-đun JavaScript tiêu chuẩn, nên chúng có thể sử dụng câu lệnh nhập và xuất. Như với tất cả các mô-đun JavaScript, các phần phụ thuộc chỉ được thực thi một lần trong ngữ cảnh cụ thể (luồng chính, worker, v.v.) và tất cả lượt nhập trong tương lai đều tham chiếu đến thực thể mô-đun đã được thực thi. Việc tải và việc thực thi các mô-đun JavaScript cũng được trình duyệt tối ưu hoá. Các phần phụ thuộc của một mô-đun có thể là được tải trước khi mô-đun được thực thi, cho phép tải toàn bộ cây mô-đun vào song song. Quá trình tải mô-đun cũng lưu mã đã phân tích cú pháp vào bộ nhớ đệm, nghĩa là các mô-đun được dùng trên luồng và trong một worker chỉ cần được phân tích cú pháp một lần.

Việc di chuyển sang các mô-đun JavaScript cũng cho phép sử dụng chức năng động nhập để tải từng phần mã mà không chặn thực thi trình thực thi. Tính năng nhập động sẽ trình bày rõ ràng hơn nhiều so với việc sử dụng importScripts() để tải các phần phụ thuộc, vì dữ liệu xuất của mô-đun đã nhập sẽ được trả về thay vì dựa vào các biến toàn cục.

worker.js:

import { sayHello } from './greet.js';
addEventListener('message', e => {
  postMessage(sayHello());
});

greet.js:

import greetings from './data.js';
export function sayHello() {
  return greetings.hello;
}

Để đảm bảo đạt được hiệu suất cao, phương thức importScripts() cũ sẽ không hoạt động trong mô-đun nhân viên. Việc chuyển đổi worker sang sử dụng các mô-đun JavaScript có nghĩa là tất cả mã đều được tải trong nghiêm ngặt chế độ xem. Khác thay đổi đáng chú ý là giá trị của this trong phạm vi cấp cao nhất của mô-đun JavaScript là undefined, còn ở worker kiểu cũ, giá trị là phạm vi chung của worker. Rất may là đã có luôn là một self toàn cục cung cấp tệp tham chiếu đến phạm vi toàn cục. Video này có phiên bản mọi loại nhân viên, bao gồm cả nhân viên dịch vụ, cũng như trong DOM.

Tải trước trình thực thi bằng modulepreload

Một điểm cải tiến đáng kể về hiệu suất đi kèm với trình thực thi mô-đun là khả năng tải trước trình thực thi và phần phụ thuộc của chúng. Với trình chạy mô-đun, tập lệnh được tải và thực thi theo chuẩn Các mô-đun JavaScript, có nghĩa là bạn có thể tải trước và thậm chí là được phân tích cú pháp trước bằng modulepreload:

<!-- preloads worker.js and its dependencies: -->
<link rel="modulepreload" href="worker.js">

<script>
  addEventListener('load', () => {
    // our worker code is likely already parsed and ready to execute!
    const worker = new Worker('worker.js', { type: 'module' });
  });
</script>

Cả luồng chính và trình thực thi mô-đun cũng có thể sử dụng các mô-đun tải trước. Thông tin này hữu ích cho các mô-đun được nhập trong cả hai ngữ cảnh hoặc trong trường hợp không thể biết trước liệu một mô-đun sẽ được sử dụng trên luồng chính hay trong một worker.

Trước đây, các tuỳ chọn có sẵn để tải trước tập lệnh trình chạy web bị giới hạn và không đáng tin cậy. Trình thực thi cổ điển có "nhân viên" của riêng mình loại tài nguyên để tải trước, nhưng không có trình duyệt đã triển khai <link rel="preload" as="worker">. Do đó, kỹ thuật chính có sẵn để tải trước nhân viên web là sử dụng <link rel="prefetch">, vốn dựa hoàn toàn vào trên bộ nhớ đệm HTTP. Khi được sử dụng kết hợp với đúng tiêu đề lưu vào bộ nhớ đệm, tính năng này đã giúp để tránh tạo thực thể worker phải chờ tải xuống tập lệnh worker. Tuy nhiên, không giống như modulepreload kỹ thuật này không hỗ trợ quá trình tải trước các phần phụ thuộc hoặc phân tích cú pháp trước.

Còn nhân viên dùng chung thì sao?

Nhân viên dùng chung có đã được cập nhật để hỗ trợ các mô-đun JavaScript kể từ Chrome 83. Giống như những nhân viên chuyên trách, việc tạo worker dùng chung bằng tuỳ chọn {type:"module"} giờ đây sẽ tải tập lệnh worker dưới dạng thay vì một tập lệnh cổ điển:

const worker = new SharedWorker('/worker.js', {
  type: 'module'
});

Trước khi hỗ trợ các mô-đun JavaScript, hàm khởi tạo SharedWorker() chỉ dự kiến có một URL và một đối số name không bắt buộc. Tính năng này sẽ tiếp tục hoạt động đối với chế độ sử dụng chung của worker theo cách cũ; tuy nhiên việc tạo trình thực thi dùng chung mô-đun yêu cầu sử dụng đối số options mới. Chiến dịch có sẵn các lựa chọn cũng giống như đối với một worker chuyên trách, bao gồm cả tuỳ chọn name thay thế đối số name trước.

Còn trình chạy dịch vụ thì sao?

Thông số của trình chạy dịch vụ đã được đã cập nhật để hỗ trợ chấp nhận mô-đun JavaScript làm điểm truy cập, sử dụng cùng tuỳ chọn {type:"module"} làm trình thực thi mô-đun, tuy nhiên, thay đổi này vẫn chưa được triển khai trong các trình duyệt. Khi điều đó xảy ra, có thể để tạo thực thể cho trình chạy dịch vụ bằng mô-đun JavaScript bằng mã sau:

navigator.serviceWorker.register('/sw.js', {
  type: 'module'
});

Giờ đây, khi quy cách đã được cập nhật, các trình duyệt sẽ bắt đầu triển khai hành vi mới. Quá trình này cần thời gian vì có thêm một số rắc rối liên quan đến việc đưa JavaScript các mô-đun cho trình chạy dịch vụ. Đăng ký trình chạy dịch vụ cần so sánh các tập lệnh đã nhập với các phiên bản đã lưu vào bộ nhớ đệm trước đó khi xác định xem có kích hoạt bản cập nhật hay không và cần triển khai cập nhật này cho các mô-đun JavaScript khi được dùng cho trình chạy dịch vụ. Ngoài ra, service worker cần có khả năng bỏ qua lưu vào bộ nhớ đệm cho các tập lệnh trong một số trường hợp khi đang kiểm tra bản cập nhật.

Tài nguyên bổ sung và tài liệu đọc thêm