Hướng dẫn lưu vào bộ nhớ đệm bắt buộc

Andrew Guan
Andrew Guan
Demián Renzulli
Demián Renzulli

Một số trang web có thể cần giao tiếp với worker dịch vụ mà không cần được thông báo về kết quả. Dưới đây là một số ví dụ:

  • Một trang sẽ gửi cho trình chạy dịch vụ một danh sách URL để tải trước, nhờ đó, khi người dùng nhấp vào một đường liên kết, tài liệu hoặc tài nguyên phụ của trang đã có sẵn trong bộ nhớ đệm, giúp thao tác điều hướng tiếp theo nhanh hơn nhiều.
  • Trang này yêu cầu trình chạy dịch vụ truy xuất và lưu một nhóm các bài viết hàng đầu vào bộ nhớ đệm để có thể sử dụng khi không có mạng.

Việc uỷ quyền các loại tác vụ không quan trọng này cho worker dịch vụ sẽ giúp giải phóng luồng chính để xử lý tốt hơn các tác vụ cấp thiết hơn, chẳng hạn như phản hồi các hoạt động tương tác của người dùng.

Sơ đồ của một trang yêu cầu tài nguyên lưu vào bộ nhớ đệm cho một trình chạy dịch vụ.

Trong hướng dẫn này, chúng ta sẽ tìm hiểu cách triển khai kỹ thuật giao tiếp một chiều từ trang đến worker dịch vụ bằng cách sử dụng các API trình duyệt tiêu chuẩn và thư viện Workbox. Chúng ta sẽ gọi các loại trường hợp sử dụng này là lưu vào bộ nhớ đệm bắt buộc.

Trường hợp sản xuất

1-800-Flowers.com đã triển khai tính năng lưu vào bộ nhớ đệm bắt buộc (tải trước) bằng trình chạy dịch vụ thông qua postMessage() để tải trước các mục hàng đầu trong trang danh mục nhằm tăng tốc quá trình điều hướng tiếp theo đến trang chi tiết sản phẩm.

Biểu trưng của 1-800 Flowers.

Các trình duyệt này sử dụng phương pháp kết hợp để quyết định những mục cần tải trước:

  • Tại thời điểm tải trang, các trình này yêu cầu worker của trình cung cấp dịch vụ truy xuất dữ liệu JSON cho 9 mục hàng đầu và thêm các đối tượng phản hồi thu được vào bộ nhớ đệm.
  • Đối với các mục còn lại, các mục này sẽ nghe sự kiện mouseover để khi người dùng di chuyển con trỏ lên trên một mục, họ có thể kích hoạt quá trình tìm nạp tài nguyên theo "yêu cầu".

Các ứng dụng này sử dụng Cache API để lưu trữ các phản hồi JSON:

Biểu trưng của 1-800 Flowers.
Tìm nạp trước dữ liệu sản phẩm JSON từ các trang thông tin sản phẩm trong 1-800Flowers.com.

Khi người dùng nhấp vào một mục, dữ liệu JSON liên kết với mục đó có thể được lấy từ bộ nhớ đệm mà không cần truy cập vào mạng, giúp thao tác điều hướng nhanh hơn.

Sử dụng Workbox

Workbox cung cấp một cách dễ dàng để gửi thông báo đến trình chạy dịch vụ thông qua gói workbox-window, một tập hợp các mô-đun được thiết kế để chạy trong ngữ cảnh cửa sổ. Các gói này bổ sung cho các gói Workbox khác chạy trong worker dịch vụ.

Để giao tiếp trang với worker dịch vụ, trước tiên, hãy lấy tham chiếu đối tượng Workbox đến worker dịch vụ đã đăng ký:

const wb = new Workbox('/sw.js');
wb.register();

Sau đó, bạn có thể trực tiếp gửi thông báo theo cách khai báo mà không gặp rắc rối khi đăng ký, kiểm tra trạng thái kích hoạt hoặc suy nghĩ về API giao tiếp cơ bản:

wb.messageSW({"type": "PREFETCH", "payload": {"urls": ["/data1.json", "data2.json"]}}); });

Trình chạy dịch vụ triển khai trình xử lý message để nghe các thông báo này. Phương thức này có thể tuỳ ý trả về một phản hồi, mặc dù trong các trường hợp như vậy, bạn không cần phải trả về:

self.addEventListener('message', (event) => {
  if (event.data && event.data.type === 'PREFETCH') {
    // do something
  }
});

Sử dụng API trình duyệt

Nếu thư viện Workbox không đủ đáp ứng nhu cầu của bạn, thì sau đây là cách bạn có thể triển khai giao tiếp giữa cửa sổ với worker dịch vụ bằng cách sử dụng API trình duyệt.

Bạn có thể sử dụng postMessage API để thiết lập cơ chế giao tiếp một chiều từ trang đến worker dịch vụ.

Trang gọi postMessage() trên giao diện worker dịch vụ:

navigator.serviceWorker.controller.postMessage({
  type: 'MSG_ID',
  payload: 'some data to perform the task',
});

Trình chạy dịch vụ triển khai trình xử lý message để nghe các thông báo này.

self.addEventListener('message', (event) => {
  if (event.data && event.data.type === MSG_ID) {
    // do something
  }
});

Thuộc tính {type : 'MSG_ID'} không bắt buộc, nhưng đây là một cách để cho phép trang gửi nhiều loại hướng dẫn đến trình chạy dịch vụ (tức là "để tải trước" so với "để xoá bộ nhớ"). Worker dịch vụ có thể phân nhánh thành nhiều đường dẫn thực thi dựa trên cờ này.

Nếu thao tác thành công, người dùng sẽ có thể hưởng lợi từ thao tác đó, nhưng nếu không, thao tác đó sẽ không làm thay đổi luồng người dùng chính. Ví dụ: khi 1-800-Flowers.com cố gắng lưu vào bộ nhớ đệm trước, trang không cần biết liệu worker dịch vụ có thành công hay không. Nếu có, người dùng sẽ có thể di chuyển nhanh hơn. Nếu không, trang vẫn cần chuyển đến trang mới. Quá trình này sẽ mất thêm một chút thời gian.

Ví dụ đơn giản về tính năng tìm nạp trước

Một trong những ứng dụng phổ biến nhất của lưu vào bộ nhớ đệm bắt buộctải trước, nghĩa là tìm nạp tài nguyên cho một URL nhất định trước khi người dùng chuyển đến URL đó để tăng tốc độ điều hướng.

Có nhiều cách để triển khai tính năng tải trước trong trang web:

Đối với các trường hợp tải trước tương đối đơn giản, chẳng hạn như tải trước tài liệu hoặc các thành phần cụ thể (JS, CSS, v.v.), những kỹ thuật đó là phương pháp tốt nhất.

Nếu cần thêm logic, chẳng hạn như phân tích cú pháp tài nguyên tải trước (một tệp JSON hoặc trang) để tìm nạp các URL nội bộ của tài nguyên đó, thì bạn nên uỷ quyền hoàn toàn tác vụ này cho worker dịch vụ.

Việc uỷ quyền các loại thao tác này cho worker dịch vụ có những ưu điểm sau:

  • Giảm tải các tác vụ nặng về việc tìm nạp và xử lý sau khi tìm nạp (sẽ được giới thiệu sau) sang một luồng phụ. Bằng cách này, luồng chính sẽ được giải phóng để xử lý các tác vụ quan trọng hơn, chẳng hạn như phản hồi các lượt tương tác của người dùng.
  • Cho phép nhiều ứng dụng khách (ví dụ: thẻ) sử dụng lại một chức năng chung và thậm chí gọi đồng thời dịch vụ mà không chặn luồng chính.

Tải trước trang chi tiết sản phẩm

Trước tiên, hãy sử dụng postMessage() trên giao diện worker dịch vụ và truyền một mảng URL vào bộ nhớ đệm:

navigator.serviceWorker.controller.postMessage({
  type: 'PREFETCH',
  payload: {
    urls: [
      'www.exmaple.com/apis/data_1.json',
      'www.exmaple.com/apis/data_2.json',
    ],
  },
});

Trong worker dịch vụ, hãy triển khai trình xử lý message để chặn và xử lý thông báo do bất kỳ thẻ nào đang hoạt động gửi:

addEventListener('message', (event) => {
  let data = event.data;
  if (data && data.type === 'PREFETCH') {
    let urls = data.payload.urls;
    for (let i in urls) {
      fetchAsync(urls[i]);
    }
  }
});

Trong mã trước, chúng ta đã giới thiệu một hàm trợ giúp nhỏ có tên là fetchAsync() để lặp lại trên آرایه URL và đưa ra yêu cầu tìm nạp cho từng URL:

async function fetchAsync(url) {
  // await response of fetch call
  let prefetched = await fetch(url);
  // (optionally) cache resources in the service worker storage
}

Khi nhận được phản hồi, bạn có thể dựa vào các tiêu đề lưu vào bộ nhớ đệm của tài nguyên. Tuy nhiên, trong nhiều trường hợp, chẳng hạn như trong trang chi tiết sản phẩm, các tài nguyên không được lưu vào bộ nhớ đệm (tức là các tài nguyên này có tiêu đề Cache-controlno-cache). Trong những trường hợp như vậy, bạn có thể ghi đè hành vi này bằng cách lưu trữ tài nguyên đã tìm nạp trong bộ nhớ đệm của worker dịch vụ. Điều này có thêm lợi ích là cho phép phân phát tệp trong các trường hợp ngoại tuyến.

Ngoài dữ liệu JSON

Sau khi dữ liệu JSON được tìm nạp từ một điểm cuối máy chủ, dữ liệu này thường chứa các URL khác cũng đáng để tải trước, chẳng hạn như hình ảnh hoặc dữ liệu điểm cuối khác được liên kết với dữ liệu cấp một này.

Giả sử trong ví dụ của chúng ta, dữ liệu JSON được trả về là thông tin của một trang web mua sắm thực phẩm:

{
  "productName": "banana",
  "productPic": "https://cdn.example.com/product_images/banana.jpeg",
  "unitPrice": "1.99"
 }

Sửa đổi mã fetchAsync() để lặp lại danh sách sản phẩm và lưu hình ảnh chính vào bộ nhớ đệm cho từng sản phẩm:

async function fetchAsync(url, postProcess) {
  // await response of fetch call
  let prefetched = await fetch(url);

  //(optionally) cache resource in the service worker cache

  // carry out the post fetch process if supplied
  if (postProcess) {
    await postProcess(prefetched);
  }
}

async function postProcess(prefetched) {
  let productJson = await prefetched.json();
  if (productJson && productJson.product_pic) {
    fetchAsync(productJson.product_pic);
  }
}

Bạn có thể thêm một số phương thức xử lý ngoại lệ xung quanh mã này cho các trường hợp như 404. Tuy nhiên, điểm hay của việc sử dụng trình chạy dịch vụ để tải trước là việc này có thể không thành công mà không gây ra nhiều hậu quả cho trang và luồng chính. Bạn cũng có thể có logic phức tạp hơn trong quá trình xử lý sau của nội dung được tìm nạp trước, giúp nội dung linh hoạt hơn và tách biệt với dữ liệu mà nội dung đó đang xử lý. Không có giới hạn.

Kết luận

Trong bài viết này, chúng tôi đã đề cập đến một trường hợp sử dụng phổ biến của hoạt động giao tiếp một chiều giữa trang và worker dịch vụ: lưu vào bộ nhớ đệm bắt buộc. Các ví dụ được thảo luận chỉ nhằm minh hoạ một cách sử dụng mẫu này và bạn cũng có thể áp dụng phương pháp tương tự cho các trường hợp sử dụng khác, chẳng hạn như lưu các bài viết hàng đầu vào bộ nhớ đệm theo yêu cầu để sử dụng khi không có mạng, đánh dấu trang và các trường hợp khác.

Để biết thêm các mẫu giao tiếp giữa trang và worker dịch vụ, hãy xem:

  • Phát sóng nội dung cập nhật: Gọi trang từ worker dịch vụ để thông báo về các nội dung cập nhật quan trọng (ví dụ: có phiên bản mới của ứng dụng web).
  • Thông tin liên lạc hai chiều: Uỷ quyền một tác vụ cho một worker dịch vụ (ví dụ: tải xuống nhiều dữ liệu) và thông báo cho trang về tiến trình.