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 trình chạy dịch vụ mà không cần được thông báo về kết quả. Sau đây là một số ví dụ:

  • Một trang sẽ gửi cho trình chạy dịch vụ danh sách URL để tìm nạp trước, nhờ đó, khi người dùng nhấp vào một đường liên kết, thì 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 vào bộ nhớ đệm một nhóm bài viết hàng đầu để có thể sử dụng các bài viết đó cho mục đích ngoại tuyến.

Việc uỷ quyền những loại tác vụ không quan trọng này cho trình chạy 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 bách hơn như phản hồi tương tác của người dùng.

Sơ đồ về một trang yêu cầu lưu tài nguyên vào bộ nhớ đệm vào 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 trình chạy 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 tôi 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ìm nạp trước) với các trình thực thi dịch vụ thông qua postMessage() để tìm nạp trước các mặt hàng hàng đầu trong các trang danh mục nhằm tăng tốc quá trình điều hướng đến trang chi tiết sản phẩm.

Biểu tượng Hoa 1-800.

Chúng sử dụng phương pháp kết hợp để quyết định mục nào cần tìm nạp trước:

  • Tại thời điểm tải trang, các trình chạy dịch vụ sẽ yêu cầu trình chạy dịch vụ truy xuất dữ liệu JSON cho 9 mục hàng đầu rồi 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, chúng theo dõi sự kiện mouseover để khi người dùng di chuyển con trỏ lên đầu một mục, họ có thể kích hoạt hoạt động tìm nạp cho tài nguyên theo "nhu cầu".

Chúng sử dụng API Bộ nhớ đệm để lưu trữ phản hồi JSON:

Biểu tượng Hoa 1-800.
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 trên 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 kết nối mạng, giúp quá trình điều hướng nhanh hơn.

Sử dụng Workbox

Workbox giúp bạn 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 dự định 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 trình chạy dịch vụ.

Để giao tiếp trang với trình chạy dịch vụ, trước tiên, hãy lấy thông tin tham chiếu đối tượng Hộp công việc cho trình chạy 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 một cách tuyên bố mà không phải lo lắng về việc đăng ký, kiểm tra 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"]}}); });

Service worker triển khai một trình xử lý message để theo dõi 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ư thế này, thì việc này là không cần thiết:

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, sau đây là cách bạn có thể triển khai cửa sổ để phục vụ hoạt động 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 trình chạy dịch vụ.

Trang này gọi postMessage() trên giao diện trình chạy dịch vụ:

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

Service worker triển khai một trình xử lý message để theo dõi 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'} là 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 cho trình chạy dịch vụ (tức là "tìm nạp trước" và "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ể nhận được lợi ích từ thao tác đó, nhưng nếu không, thao tác này 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 trước vào bộ nhớ đệm, trang không cần biết liệu trình chạy dịch vụ có thành công hay không. Nếu có, người dùng sẽ được điều hướng nhanh hơn. Nếu không, trang vẫn cần phải chuyển đến trang mới. Quá trình này sẽ lâu hơn một chút.

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

Một trong những ứng dụng phổ biến nhất của tính năng lưu vào bộ nhớ đệm bắt buộctìm nạp trước, nghĩa là tìm nạp các 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 việc tìm nạp trước trong trang web:

Đối với các trường hợp tìm nạp trước tương đối đơn giản, chẳng hạn như tìm nạp trước tài liệu hoặc thành phần cụ thể (JS, CSS, v.v.), thì các 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 (trang hoặc tệp JSON) để tìm nạp URL nội bộ, thì bạn nên uỷ quyền toàn bộ tác vụ này cho trình chạy dịch vụ.

Ủy quyền các loại hoạt động này cho trình chạy dịch vụ có những lợi ích sau:

  • Giảm tải phần lớn công việc tìm nạp và xử lý sau khi tìm nạp (sẽ được giới thiệu sau) sang luồng phụ. Bằng cách này, tính năng này giải phóng luồng chính để xử lý các tác vụ quan trọng hơn như phản hồi tương tác của người dùng.
  • Cho phép nhiều ứng dụng (ví dụ: 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 trình chạy dịch vụ và chuyển một loạt 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 trình chạy dịch vụ, hãy triển khai một trình xử lý message để chặn và xử lý thông báo được gửi từ thẻ đang hoạt động bất kỳ:

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 tôi đã giới thiệu một hàm trợ giúp nhỏ có tên là fetchAsync() để lặp lại mảng 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ư các trang chi tiết sản phẩm, các tài nguyên không được lưu vào bộ nhớ đệm (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ụ. Việc này mang đến thêm một lợi ích là cho phép phân phát tệp trong trường hợp ngoại tuyến.

Không chỉ là dữ liệu JSON

Sau khi tìm nạp dữ liệu JSON qua một điểm cuối máy chủ, dữ liệu đó thường chứa các URL khác mà cũng đáng để 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 hàng tạp hoá:

{
  "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ệ xung quanh mã này trong các trường hợp như lỗi 404. Tuy nhiên, ưu điểm của việc sử dụng trình chạy dịch vụ để tìm nạp trước là có thể không thành công mà không có nhiều hậu quả đối với 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ỳ nội dung đã tìm nạp trước, giúp nội dung này linh hoạt hơn và được tách riêng với dữ liệu đ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 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à trình chạy 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 mục đích 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 vào bộ nhớ đệm các bài viết hàng đầu theo yêu cầu để sử dụng ngoại tuyến, đánh dấu trang và các nội dung khác.

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

  • Truyền phát nội dung cập nhật: Gọi trang từ trình chạy 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).
  • Giao tiếp hai chiều: Ủy quyền nhiệm vụ cho nhân viên dịch vụ (ví dụ: tải xuống nhiều) và luôn thông báo cho trang về tiến độ.