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 quá trình đ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 trình chạy dịch vụ có lợi ích là giải phóng luồng chính để xử lý tốt hơn các tác vụ cấp bách hơn, chẳng hạn như phản hồi 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ẽ khám phá 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 API này sử dụng API Bộ nhớ đệm để 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 chọn từ bộ nhớ đệm mà không cần kết nối mạng, giúp quá trình đ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 đủ cho nhu cầu của bạn, thì sau đây là cách bạn có thể triển khai cửa sổ để phục vụ giao tiếp của nhân viên, 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ớ"). Service worker có thể phân nhánh thành các đường dẫn thực thi khác nhau 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ó thì người dùng sẽ điều hướng nhanh hơn. Nếu không, trang vẫn cần điều hướng đế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 việc lưu bắt buộc vào bộ nhớ đệmtìm nạp 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 sang 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ìm nạp trước (tệp hoặc trang JSON) để tìm nạp URL nội bộ của nó, thì tốt hơn là bạn nên uỷ quyền hoàn toàn tác vụ này cho trình chạy 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 (ví dụ: các thẻ) sử dụng lại một chức năng phổ biến và thậm chí gọi dịch vụ cùng lúc mà không chặn luồng chính.

Tìm nạp 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 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 các trang chi tiết sản phẩm, tài nguyên không được lưu vào bộ nhớ đệm (có nghĩa là chúng 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 trình chạy 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ừ điểm cuối của máy chủ, dữ liệu đó thường chứa các URL khác cũng đáng được tìm nạp 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 đầu tiên 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ố cách xử lý ngoại lệ liên quan đến mã này trong các tình huống như lỗi 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 chi tiết hơn trong quá trình xử lý hậu kỳ của nội dung đã tìm nạp trước, giúp nội dung linh hoạt hơn và được tách biệt với dữ liệu mà nó 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:

  • Cập nhật thông báo: Gọi trang từ worker dịch vụ để thông báo về các bản 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 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.