Chiến lược lưu vào bộ nhớ đệm của nhân viên dịch vụ

Cho đến nay, mới chỉ có lượt đề cập và các đoạn mã nhỏ của giao diện Cache. Để sử dụng trình chạy dịch vụ một cách hiệu quả, bạn cần áp dụng một hoặc nhiều chiến lược lưu vào bộ nhớ đệm. Việc này đòi hỏi bạn phải làm quen với giao diện Cache.

Chiến lược lưu vào bộ nhớ đệm là sự tương tác giữa sự kiện fetch của trình chạy dịch vụ và giao diện Cache. Cách viết chiến lược lưu vào bộ nhớ đệm còn tuỳ thuộc vào cách xử lý yêu cầu đối với tài sản tĩnh thay vì tài liệu. Điều này ảnh hưởng đến cách tạo chiến lược lưu vào bộ nhớ đệm.

Trước khi tìm hiểu về các chiến lược, hãy dành chút thời gian để nói về giao diện Cache có gì, giao diện này là gì và tóm tắt nhanh một số phương thức giao diện này cung cấp để quản lý bộ nhớ đệm của trình chạy dịch vụ.

Giao diện Cache so với bộ nhớ đệm HTTP

Nếu trước đây chưa từng làm việc với giao diện Cache, thì bạn có thể nghĩ tương tự như giao diện này hoặc ít nhất là liên quan đến bộ nhớ đệm HTTP. Không phải vậy.

  • Giao diện Cache là cơ chế lưu vào bộ nhớ đệm hoàn toàn tách biệt với bộ nhớ đệm HTTP.
  • Việc bạn sử dụng cấu hình Cache-Control nào để tác động đến bộ nhớ đệm HTTP sẽ không ảnh hưởng đến những thành phần được lưu trữ trong giao diện Cache.

Bạn nên coi bộ nhớ đệm của trình duyệt được phân lớp. Bộ nhớ đệm HTTP là một bộ nhớ đệm cấp thấp do các cặp khoá-giá trị điều khiển với các lệnh được thể hiện trong tiêu đề HTTP.

Ngược lại, giao diện Cache là một bộ nhớ đệm cấp cao do API JavaScript điều khiển. Điều này mang lại tính linh hoạt cao hơn so với khi sử dụng các cặp khoá-giá trị HTTP tương đối đơn giản và là một nửa khả năng của các chiến lược lưu vào bộ nhớ đệm. Dưới đây là một số phương thức API quan trọng liên quan đến bộ nhớ đệm của trình chạy dịch vụ:

  • CacheStorage.open để tạo một thực thể Cache mới.
  • Cache.addCache.put để lưu trữ phản hồi của mạng trong bộ nhớ đệm của trình chạy dịch vụ.
  • Cache.match để xác định vị trí một phản hồi đã lưu vào bộ nhớ đệm trong một thực thể Cache.
  • Cache.delete để xoá phản hồi đã lưu vào bộ nhớ đệm khỏi một thực thể Cache.

Đây chỉ là một vài. Còn có các phương thức hữu ích khác, nhưng đây là những phương thức cơ bản mà bạn sẽ thấy được sử dụng sau này trong hướng dẫn này.

Sự kiện fetch khiêm tốn

Một nửa còn lại của chiến lược lưu vào bộ nhớ đệm là sự kiện fetch của trình chạy dịch vụ. Cho đến thời điểm này trong tài liệu này, bạn đã nghe nói một chút về cách "chặn yêu cầu mạng" và sự kiện fetch bên trong một trình chạy dịch vụ là nơi xảy ra điều này:

// Establish a cache name
const cacheName = 'MyFancyCacheName_v1';

self.addEventListener('install', (event) => {
  event.waitUntil(caches.open(cacheName));
});

self.addEventListener('fetch', async (event) => {
  // Is this a request for an image?
  if (event.request.destination === 'image') {
    // Open the cache
    event.respondWith(caches.open(cacheName).then((cache) => {
      // Respond with the image from the cache or from the network
      return cache.match(event.request).then((cachedResponse) => {
        return cachedResponse || fetch(event.request.url).then((fetchedResponse) => {
          // Add the network response to the cache for future visits.
          // Note: we need to make a copy of the response to save it in
          // the cache and use the original as the request response.
          cache.put(event.request, fetchedResponse.clone());

          // Return the network response
          return fetchedResponse;
        });
      });
    }));
  } else {
    return;
  }
});

Đây là ví dụ về đồ chơi (và bạn có thể tự mình xem ví dụ thực tế) – nhưng đây là ví dụ cung cấp thông tin sơ lược về những gì mà worker dịch vụ có thể làm. Mã ở trên thực hiện những việc sau:

  1. Kiểm tra thuộc tính destination của yêu cầu để xem đây có phải là yêu cầu hình ảnh hay không.
  2. Nếu hình ảnh nằm trong bộ nhớ đệm của trình chạy dịch vụ, hãy phân phát hình ảnh từ bộ nhớ đệm đó. Nếu không, hãy tìm nạp hình ảnh từ mạng, lưu trữ phản hồi trong bộ nhớ đệm rồi trả về phản hồi mạng.
  3. Tất cả các yêu cầu khác được chuyển đi qua trình chạy dịch vụ mà không có tương tác với bộ nhớ đệm.

Đối tượng event của tìm nạp chứa thuộc tính request. Đây là một số thông tin hữu ích giúp bạn xác định loại của mỗi yêu cầu:

  • url, là URL cho yêu cầu mạng hiện đang được sự kiện fetch xử lý.
  • method, là phương thức yêu cầu (ví dụ: GET hoặc POST).
  • mode mô tả chế độ của yêu cầu. Giá trị của 'navigate' thường được dùng để phân biệt các yêu cầu tài liệu HTML với các yêu cầu khác.
  • destination mô tả loại nội dung đang được yêu cầu theo cách tránh sử dụng đuôi tệp của thành phần được yêu cầu.

Một lần nữa, tính không đồng bộ là tên của trò chơi. Hãy nhớ rằng sự kiện install cung cấp phương thức event.waitUntil đưa ra lời hứa và chờ phương thức đó giải quyết trước khi tiếp tục kích hoạt. Sự kiện fetch cung cấp một phương thức event.respondWith tương tự mà bạn có thể sử dụng để trả về kết quả của yêu cầu fetch không đồng bộ hoặc phản hồi do phương thức match của giao diện Cache trả về.

Chiến lược lưu vào bộ nhớ đệm

Giờ đây, khi đã làm quen với các thực thể Cache và trình xử lý sự kiện fetch, bạn có thể tìm hiểu kỹ hơn về một số chiến lược lưu vào bộ nhớ đệm của trình chạy dịch vụ. Mặc dù thực tế thì khả năng là vô tận, nhưng hướng dẫn này sẽ bám sát các chiến lược đi kèm với Workbox, để bạn có thể nắm được những gì đang diễn ra bên trong Workbox.

Chỉ lưu vào bộ nhớ đệm

Hiển thị luồng từ trang, đến trình chạy dịch vụ, đến bộ nhớ đệm.

Hãy bắt đầu với một chiến lược lưu vào bộ nhớ đệm đơn giản mà chúng ta gọi là "Chỉ bộ nhớ đệm". Chỉ là: khi trình chạy dịch vụ có quyền kiểm soát trang, các yêu cầu so khớp sẽ chỉ được chuyển đến bộ nhớ đệm. Điều này có nghĩa là mọi tài sản đã lưu vào bộ nhớ đệm đều phải được lưu trước vào bộ nhớ đệm để mẫu có thể hoạt động. Đồng thời, các tài sản đó sẽ không bao giờ được cập nhật trong bộ nhớ đệm cho đến khi trình chạy dịch vụ được cập nhật.

// Establish a cache name
const cacheName = 'MyFancyCacheName_v1';

// Assets to precache
const precachedAssets = [
  '/possum1.jpg',
  '/possum2.jpg',
  '/possum3.jpg',
  '/possum4.jpg'
];

self.addEventListener('install', (event) => {
  // Precache assets on install
  event.waitUntil(caches.open(cacheName).then((cache) => {
    return cache.addAll(precachedAssets);
  }));
});

self.addEventListener('fetch', (event) => {
  // Is this one of our precached assets?
  const url = new URL(event.request.url);
  const isPrecachedRequest = precachedAssets.includes(url.pathname);

  if (isPrecachedRequest) {
    // Grab the precached asset from the cache
    event.respondWith(caches.open(cacheName).then((cache) => {
      return cache.match(event.request.url);
    }));
  } else {
    // Go to the network
    return;
  }
});

Ở trên, một loạt tài sản được lưu trước vào bộ nhớ đệm khi cài đặt. Khi trình chạy dịch vụ xử lý các lượt tìm nạp, chúng tôi sẽ kiểm tra xem URL yêu cầu do sự kiện fetch xử lý có nằm trong mảng tài sản được lưu trước vào bộ nhớ đệm hay không. Nếu có, chúng ta lấy tài nguyên từ bộ nhớ đệm và bỏ qua mạng. Các yêu cầu khác chuyển qua mạng và chỉ chuyển qua mạng. Để xem cách chiến lược này hoạt động hiệu quả, hãy xem bản minh hoạ này trên bảng điều khiển đang mở.

Chỉ mạng

Hiển thị luồng từ trang, đến trình chạy dịch vụ, đến mạng.

Trái ngược với "Chỉ bộ nhớ đệm" là "Chỉ mạng", trong đó yêu cầu được chuyển qua trình chạy dịch vụ đến mạng mà không có bất kỳ tương tác nào với bộ nhớ đệm của trình chạy dịch vụ. Đây là chiến lược hay để đảm bảo độ mới của nội dung (hãy nghĩ đến mã đánh dấu), nhưng đổi lại, nó sẽ không bao giờ hoạt động khi người dùng không có kết nối mạng.

Việc đảm bảo một yêu cầu được truyền đến mạng chỉ có nghĩa là bạn không gọi event.respondWith để có một yêu cầu so khớp. Nếu muốn nêu rõ, bạn có thể chèn một return; trống trong lệnh gọi lại sự kiện fetch cho các yêu cầu mà bạn muốn truyền qua mạng. Đây là điều sẽ xảy ra trong bản minh hoạ chiến lược "Chỉ lưu vào bộ nhớ đệm" đối với các yêu cầu không được lưu trước vào bộ nhớ đệm.

Hãy lưu vào bộ nhớ đệm trước, hãy quay lại mạng

Hiển thị luồng từ trang đến trình chạy dịch vụ, đến bộ nhớ đệm rồi đến mạng nếu không có trong bộ nhớ đệm.

Chiến lược này thúc đẩy mọi thứ tham gia nhiều hơn một chút. Đối với các yêu cầu so khớp, quy trình sẽ diễn ra như sau:

  1. Yêu cầu truy cập vào bộ nhớ đệm. Nếu nội dung nằm trong bộ nhớ đệm, hãy phân phát nội dung từ đó.
  2. Nếu yêu cầu không nằm trong bộ nhớ đệm, hãy kết nối vào mạng.
  3. Sau khi yêu cầu mạng hoàn tất, hãy thêm yêu cầu đó vào bộ nhớ đệm, rồi trả về phản hồi từ mạng.

Bạn có thể kiểm thử ví dụ sau đây về chiến lược này trong bản minh hoạ trực tiếp:

// Establish a cache name
const cacheName = 'MyFancyCacheName_v1';

self.addEventListener('fetch', (event) => {
  // Check if this is a request for an image
  if (event.request.destination === 'image') {
    event.respondWith(caches.open(cacheName).then((cache) => {
      // Go to the cache first
      return cache.match(event.request.url).then((cachedResponse) => {
        // Return a cached response if we have one
        if (cachedResponse) {
          return cachedResponse;
        }

        // Otherwise, hit the network
        return fetch(event.request).then((fetchedResponse) => {
          // Add the network response to the cache for later visits
          cache.put(event.request, fetchedResponse.clone());

          // Return the network response
          return fetchedResponse;
        });
      });
    }));
  } else {
    return;
  }
});

Mặc dù ví dụ này chỉ đề cập đến hình ảnh, nhưng đây là một chiến lược tuyệt vời để áp dụng cho tất cả các thành phần tĩnh (chẳng hạn như CSS, JavaScript, hình ảnh và phông chữ), đặc biệt là các thành phần có phiên bản hàm băm. Tính năng này giúp tăng tốc độ đối với các nội dung không thể thay đổi bằng cách thực hiện bên cạnh bất kỳ bước kiểm tra độ mới nào của nội dung với máy chủ mà bộ nhớ đệm HTTP có thể bắt đầu. Quan trọng hơn, mọi nội dung đã lưu vào bộ nhớ đệm đều sẽ sử dụng được khi không có mạng.

Trước tiên là mạng, hãy quay lại bộ nhớ đệm

Hiển thị luồng từ trang, đến trình chạy dịch vụ, đến mạng, sau đó đến bộ nhớ đệm nếu không có mạng.

Nếu bạn lật "Bộ nhớ đệm trước tiên, mạng thứ hai" trên đầu, bạn sẽ kết thúc với chiến lược "Mạng trước tiên, bộ nhớ đệm thứ hai", có dạng như sau:

  1. Trước tiên, bạn truy cập vào mạng để yêu cầu một yêu cầu rồi đặt phản hồi vào bộ nhớ đệm.
  2. Nếu sau đó không có kết nối mạng, bạn sẽ quay lại phiên bản mới nhất của phản hồi đó trong bộ nhớ đệm.

Chiến lược này phù hợp với các yêu cầu HTML hoặc API khi bạn muốn sử dụng phiên bản tài nguyên mới nhất, nhưng vẫn muốn cấp quyền truy cập ngoại tuyến vào phiên bản mới nhất có sẵn. Khi áp dụng cho yêu cầu HTML, bạn có thể thấy những thông tin sau:

// Establish a cache name
const cacheName = 'MyFancyCacheName_v1';

self.addEventListener('fetch', (event) => {
  // Check if this is a navigation request
  if (event.request.mode === 'navigate') {
    // Open the cache
    event.respondWith(caches.open(cacheName).then((cache) => {
      // Go to the network first
      return fetch(event.request.url).then((fetchedResponse) => {
        cache.put(event.request, fetchedResponse.clone());

        return fetchedResponse;
      }).catch(() => {
        // If the network is unavailable, get
        return cache.match(event.request.url);
      });
    }));
  } else {
    return;
  }
});

Bạn có thể dùng thử tính năng này trong bản minh hoạ. Trước tiên, hãy truy cập vào trang. Bạn có thể cần phải tải lại trước khi phản hồi HTML được đặt vào bộ nhớ đệm. Sau đó, trong công cụ cho nhà phát triển, hãy mô phỏng kết nối ngoại tuyến rồi tải lại lần nữa. Phiên bản mới nhất có sẵn sẽ được phân phát ngay từ bộ nhớ đệm.

Trong trường hợp khả năng ngoại tuyến đóng vai trò quan trọng, nhưng bạn cần cân bằng chức năng đó với quyền truy cập vào phiên bản mới nhất của một chút dữ liệu đánh dấu hoặc API, "Mạng trước tiên, bộ nhớ đệm thứ hai" là một chiến lược vững chắc để đạt được mục tiêu đó.

Cũ trong khi xác thực lại

Hiển thị luồng từ trang, đến trình chạy dịch vụ, đến bộ nhớ đệm, rồi từ mạng sang bộ nhớ đệm.

Trong số các chiến lược chúng tôi đã đề cập đến nay, chiến lược "Đã lỗi thời trong khi xác thực lại" là chiến lược phức tạp nhất. Theo một cách nào đó, quy trình này tương tự như 2 chiến lược cuối cùng, nhưng quy trình này ưu tiên tốc độ truy cập cho một tài nguyên, trong khi vẫn duy trì cập nhật tài nguyên đó ở chế độ nền. Chiến lược này áp dụng như sau:

  1. Trong yêu cầu đầu tiên cho một nội dung, hãy tìm nạp nội dung đó từ mạng, đặt nội dung đó vào bộ nhớ đệm rồi trả về phản hồi mạng.
  2. Trong các yêu cầu tiếp theo, trước tiên, hãy phân phát nội dung từ bộ nhớ đệm, sau đó "ở chế độ nền" rồi yêu cầu lại nội dung từ mạng và cập nhật mục nhập bộ nhớ đệm của tài sản.
  3. Đối với các yêu cầu sau đó, bạn sẽ nhận được phiên bản gần đây nhất được tìm nạp từ mạng được đặt vào bộ nhớ đệm ở bước trước.

Đây là một chiến lược tuyệt vời cho những thông tin quan trọng cần cập nhật nhưng không quan trọng. Hãy nghĩ đến những thứ như hình đại diện cho trang mạng xã hội. Các API này được cập nhật khi người dùng thực hiện việc đó, nhưng không nhất thiết phải có phiên bản mới nhất trong mọi yêu cầu.

// Establish a cache name
const cacheName = 'MyFancyCacheName_v1';

self.addEventListener('fetch', (event) => {
  if (event.request.destination === 'image') {
    event.respondWith(caches.open(cacheName).then((cache) => {
      return cache.match(event.request).then((cachedResponse) => {
        const fetchedResponse = fetch(event.request).then((networkResponse) => {
          cache.put(event.request, networkResponse.clone());

          return networkResponse;
        });

        return cachedResponse || fetchedResponse;
      });
    }));
  } else {
    return;
  }
});

Bạn có thể thấy tính năng này trong thực tế một bản minh hoạ trực tiếp khác, đặc biệt nếu bạn chú ý đến thẻ mạng trong công cụ cho nhà phát triển của trình duyệt và trình xem CacheStorage của trình duyệt đó (nếu công cụ cho nhà phát triển của trình duyệt có công cụ như vậy).

Hãy chuyển sang Workbox!

Tài liệu này tóm tắt bài đánh giá của chúng tôi về API của trình chạy dịch vụ, cũng như các API liên quan, có nghĩa là bạn đã tìm hiểu đủ về cách trực tiếp sử dụng trình chạy dịch vụ để bắt đầu sửa đổi với Workbox!