Sổ tay nấu ăn ngoại tuyến

Jake Archibald
Jake Archibald

Với Service Worker, chúng tôi đã ngừng tìm cách giải quyết ngoại tuyến và giao cho nhà phát triển các phần di chuyển để tự giải quyết. Cơ chế này cho phép bạn kiểm soát việc lưu vào bộ nhớ đệm và cách xử lý các yêu cầu. Điều đó có nghĩa là bạn sẽ có thể tạo ra các mẫu của riêng mình. Hãy xem xét một vài mẫu có thể có để tách biệt, nhưng trong thực tế, bạn có thể sẽ sử dụng nhiều mẫu song song tuỳ thuộc vào URL và ngữ cảnh.

Để xem bản minh hoạ hiệu quả của một số mô hình trong số này, hãy xem phần Đào tạo để thúc đẩy cảm giác mạnhvideo này minh hoạ tác động của hiệu suất.

Máy bộ nhớ đệm – thời điểm lưu trữ tài nguyên

Service Worker cho phép bạn xử lý các yêu cầu một cách độc lập với việc lưu vào bộ nhớ đệm, vì vậy, tôi sẽ minh hoạ các yêu cầu này một cách riêng biệt. Trước tiên, lưu vào bộ nhớ đệm, khi nào quá trình này nên được thực hiện?

Khi cài đặt – dưới dạng phần phụ thuộc

Khi cài đặt – dưới dạng phần phụ thuộc.
Khi cài đặt – dưới dạng phần phụ thuộc.

Service Worker cung cấp cho bạn một sự kiện install. Bạn có thể sử dụng tính năng này để chuẩn bị sẵn nội dung, nội dung phải sẵn sàng trước khi xử lý các sự kiện khác. Mặc dù điều này xảy ra, mọi phiên bản trước đó của Service Worker vẫn đang chạy và phân phát các trang, vì vậy, những việc bạn thực hiện ở đây sẽ không làm gián đoạn hoạt động đó.

Phù hợp cho: CSS, hình ảnh, phông chữ, JS, mẫu... về cơ bản, mọi thứ mà bạn coi là tĩnh cho "phiên bản" đó của trang web.

Đây là những yếu tố khiến trang web của bạn hoàn toàn không hoạt động nếu không tìm nạp được. Một ứng dụng tương đương dành riêng cho nền tảng sẽ xuất hiện trong lần tải xuống ban đầu.

self.addEventListener('install', function (event) {
  event.waitUntil(
    caches.open('mysite-static-v3').then(function (cache) {
      return cache.addAll([
        '/css/whatever-v3.css',
        '/css/imgs/sprites-v6.png',
        '/css/fonts/whatever-v8.woff',
        '/js/all-min-v4.js',
        // etc.
      ]);
    }),
  );
});

event.waitUntil có cam kết để xác định thời lượng và sự thành công của quá trình cài đặt. Nếu lời hứa bị từ chối, quá trình cài đặt sẽ bị coi là không thành công và Service Worker này sẽ bị bỏ qua (nếu phiên bản cũ hơn đang chạy, phiên bản đó sẽ vẫn còn nguyên). Lời hứa trả lại của caches.open()cache.addAll(). Nếu không tìm nạp được bất kỳ tài nguyên nào, thì lệnh gọi cache.addAll() sẽ từ chối.

Trên traininged-to-thrill, tôi sử dụng công cụ này để lưu các tài sản tĩnh vào bộ nhớ đệm.

Khi cài đặt – không phải dưới dạng phần phụ thuộc

Khi cài đặt – không phải dưới dạng phần phụ thuộc.
Khi cài đặt – không phải dưới dạng phần phụ thuộc.

Điều này cũng tương tự như trên, nhưng sẽ không trì hoãn việc hoàn tất quá trình cài đặt và sẽ không khiến quá trình cài đặt không thành công nếu lưu vào bộ nhớ đệm không thành công.

Lý tưởng cho: các tài nguyên lớn hơn không cần thiết ngay, chẳng hạn như tài sản cho các cấp độ sau của trò chơi.

self.addEventListener('install', function (event) {
  event.waitUntil(
    caches.open('mygame-core-v1').then(function (cache) {
      cache
        .addAll
        // levels 11–20
        ();
      return cache
        .addAll
        // core assets and levels 1–10
        ();
    }),
  );
});

Ví dụ trên không vượt qua lời hứa cache.addAll của các cấp độ 11–20 trở lại event.waitUntil, vì vậy, ngay cả khi không thành công, trò chơi vẫn sẽ chơi được khi không có mạng. Tất nhiên, bạn sẽ phải ứng phó với trường hợp có thể không có các cấp này và cố gắng lưu lại vào bộ nhớ đệm nếu chúng bị thiếu.

Service Worker có thể bị tắt khi tải xuống cấp 11–20 vì quá trình này đã xử lý xong các sự kiện, nghĩa là các sự kiện này sẽ không được lưu vào bộ nhớ đệm. Trong tương lai, API đồng bộ hoá định kỳ ở chế độ nền trên web sẽ xử lý các trường hợp như thế này và các tệp tải xuống có kích thước lớn hơn, chẳng hạn như phim. API đó hiện chỉ được hỗ trợ trên các nhánh phát triển Chromium.

Khi kích hoạt

Khi kích hoạt.
Khi kích hoạt.

Lý tưởng cho: dọn dẹp và di chuyển.

Sau khi bạn cài đặt Service Worker mới và phiên bản trước đó không được sử dụng, phiên bản mới sẽ kích hoạt và bạn sẽ nhận được sự kiện activate. Vì phiên bản cũ đã không còn được sử dụng, nên đây là thời điểm tốt để xử lý việc di chuyển giản đồ trong IndexedDB và cũng như xoá các bộ nhớ đệm không dùng đến.

self.addEventListener('activate', function (event) {
  event.waitUntil(
    caches.keys().then(function (cacheNames) {
      return Promise.all(
        cacheNames
          .filter(function (cacheName) {
            // Return true if you want to remove this cache,
            // but remember that caches are shared across
            // the whole origin
          })
          .map(function (cacheName) {
            return caches.delete(cacheName);
          }),
      );
    }),
  );
});

Trong quá trình kích hoạt, các sự kiện khác như fetch được đưa vào hàng đợi, do đó, nếu bạn kích hoạt trong thời gian dài, thì các sự kiện tải trang có thể sẽ bị chặn. Hãy sử dụng cấu hình kích hoạt sao cho tinh gọn nhất có thể và chỉ sử dụng tính năng này cho những việc mà bạn không thể làm khi phiên bản cũ đang hoạt động.

Trên traininged-to-thrill, tôi dùng cách này để xoá bộ nhớ đệm cũ.

Khi người dùng tương tác

Về tương tác của người dùng.
Khi tương tác của người dùng.

Phù hợp: khi không thể đưa toàn bộ trang web xuống thiết bị và bạn đã chọn cho phép người dùng chọn nội dung họ muốn xem khi không có mạng. Ví dụ: video trên YouTube, bài viết trên Wikipedia, một thư viện cụ thể trên Flickr.

Cung cấp cho người dùng nút "Đọc sau" hoặc "Lưu để xem khi không có mạng". Khi được nhấp vào, hãy tìm nạp những gì bạn cần từ mạng và đưa vào bộ nhớ đệm.

document.querySelector('.cache-article').addEventListener('click', function (event) {
  event.preventDefault();

  var id = this.dataset.articleId;
  caches.open('mysite-article-' + id).then(function (cache) {
    fetch('/get-article-urls?id=' + id)
      .then(function (response) {
        // /get-article-urls returns a JSON-encoded array of
        // resource URLs that a given article depends on
        return response.json();
      })
      .then(function (urls) {
        cache.addAll(urls);
      });
  });
});

API bộ nhớ đệm có sẵn từ các trang cũng như trình chạy dịch vụ, nghĩa là bạn có thể thêm vào bộ nhớ đệm ngay từ trang.

Phản hồi về mạng

Phản hồi mạng.
Trên phản hồi mạng.

Lý tưởng cho: thường xuyên cập nhật tài nguyên như hộp thư đến của người dùng hoặc nội dung bài viết. Tính năng này cũng hữu ích đối với nội dung không thiết yếu như hình đại diện, nhưng cần cẩn thận.

Nếu một yêu cầu không khớp với bất kỳ mục nào trong bộ nhớ đệm, hãy lấy yêu cầu đó từ mạng, gửi yêu cầu đến trang và đồng thời thêm yêu cầu đó vào bộ nhớ đệm.

Nếu thực hiện việc này cho một loạt URL, chẳng hạn như hình đại diện, bạn phải cẩn thận để không làm tăng dung lượng lưu trữ nguồn gốc. Nếu người dùng cần lấy lại dung lượng ổ đĩa, thì bạn không muốn là ứng viên chính. Hãy đảm bảo bạn loại bỏ các mục trong bộ nhớ đệm mà bạn không cần thêm nữa.

self.addEventListener('fetch', function (event) {
  event.respondWith(
    caches.open('mysite-dynamic').then(function (cache) {
      return cache.match(event.request).then(function (response) {
        return (
          response ||
          fetch(event.request).then(function (response) {
            cache.put(event.request, response.clone());
            return response;
          })
        );
      });
    }),
  );
});

Để sử dụng bộ nhớ hiệu quả, bạn chỉ có thể đọc nội dung của phản hồi/yêu cầu một lần. Mã ở trên sử dụng .clone() để tạo thêm các bản sao có thể đọc riêng.

Khi luyện tập từ từ để chạy thử đến cảm giác mạnh, tôi sử dụng công cụ này để lưu hình ảnh trên Flickr vào bộ nhớ đệm.

Phiên bản cũ trong khi xác thực lại

Cũ trong khi xác thực lại.
Đã lỗi thời trong khi xác thực lại.

Phù hợp: thường xuyên cập nhật tài nguyên mà không cần phiên bản mới nhất. Hình đại diện có thể thuộc danh mục này.

Nếu có phiên bản đã lưu vào bộ nhớ đệm, hãy sử dụng phiên bản đó nhưng tìm nạp bản cập nhật để dùng vào lần sau.

self.addEventListener('fetch', function (event) {
  event.respondWith(
    caches.open('mysite-dynamic').then(function (cache) {
      return cache.match(event.request).then(function (response) {
        var fetchPromise = fetch(event.request).then(function (networkResponse) {
          cache.put(event.request, networkResponse.clone());
          return networkResponse;
        });
        return response || fetchPromise;
      });
    }),
  );
});

Điều này rất giống với quá trình xác thực lại trong khi xác thực lại của HTTP.

Trên thông báo đẩy

Trên thông báo đẩy.
Trên thông báo đẩy.

Push API (API Đẩy) là một tính năng khác được xây dựng dựa trên Service Worker. Điều này cho phép Service Worker được đánh thức để phản hồi thông báo từ dịch vụ nhắn tin của hệ điều hành. Điều này xảy ra ngay cả khi người dùng không mở thẻ nào trên trang web của bạn. Chỉ có Trình chạy dịch vụ được đánh thức. Bạn yêu cầu quyền để thực hiện việc này từ một trang và người dùng sẽ được nhắc.

Phù hợp: nội dung liên quan đến thông báo, chẳng hạn như tin nhắn trò chuyện, bản tin nổi bật hoặc email. Cũng không thường xuyên thay đổi nội dung hưởng lợi từ việc đồng bộ hoá tức thì, chẳng hạn như cập nhật danh sách việc cần làm hoặc sửa đổi lịch.

Kết quả cuối cùng phổ biến là một thông báo (khi được nhấn sẽ mở/tập trung vào một trang liên quan), nhưng việc cập nhật bộ nhớ đệm trước khi điều này xảy ra là extremely. Rõ ràng là người dùng đang trực tuyến tại thời điểm nhận được thông báo đẩy, nhưng có thể họ không trực tuyến vào lúc tương tác với thông báo đó. Vì vậy, việc đặt nội dung này ở chế độ có thể sử dụng khi không có mạng là rất quan trọng.

Mã này cập nhật bộ nhớ đệm trước khi hiển thị thông báo:

self.addEventListener('push', function (event) {
  if (event.data.text() == 'new-email') {
    event.waitUntil(
      caches
        .open('mysite-dynamic')
        .then(function (cache) {
          return fetch('/inbox.json').then(function (response) {
            cache.put('/inbox.json', response.clone());
            return response.json();
          });
        })
        .then(function (emails) {
          registration.showNotification('New email', {
            body: 'From ' + emails[0].from.name,
            tag: 'new-email',
          });
        }),
    );
  }
});

self.addEventListener('notificationclick', function (event) {
  if (event.notification.tag == 'new-email') {
    // Assume that all of the resources needed to render
    // /inbox/ have previously been cached, e.g. as part
    // of the install handler.
    new WindowClient('/inbox/');
  }
});

Khi đồng bộ hoá dưới nền

Khi đồng bộ hoá dưới nền.
Khi đồng bộ hoá dưới nền.

Đồng bộ hoá trong nền là một tính năng khác được xây dựng dựa trên Service Worker. API này cho phép bạn yêu cầu đồng bộ hoá dữ liệu nền dưới dạng một lần hoặc trên một khoảng thời gian (cực kỳ phỏng đoán). Tình trạng này xảy ra ngay cả khi người dùng không mở thẻ nào vào trang web của bạn. Chỉ có Trình chạy dịch vụ được đánh thức. Bạn yêu cầu cấp quyền để thực hiện việc này từ một trang và người dùng sẽ được nhắc.

Phù hợp: các bản cập nhật không khẩn cấp, đặc biệt là những bản cập nhật diễn ra thường xuyên đến mức mà thông báo đẩy cho mỗi bản cập nhật có thể quá thường xuyên đối với người dùng, chẳng hạn như dòng thời gian trên mạng xã hội hoặc tin bài.

self.addEventListener('sync', function (event) {
  if (event.id == 'update-leaderboard') {
    event.waitUntil(
      caches.open('mygame-dynamic').then(function (cache) {
        return cache.add('/leaderboard.json');
      }),
    );
  }
});

Lưu trữ cố định bộ nhớ đệm

Nguồn gốc của bạn được cấp một lượng dung lượng trống nhất định để thực hiện những việc mong muốn. Dung lượng trống đó được dùng chung giữa mọi bộ nhớ gốc: (local) Storage (Cục bộ), IndexedDB, File System Access (Quyền truy cập hệ thống tệp) và tất nhiên là Bộ nhớ đệm.

Số tiền bạn nhận được không được chỉ định cụ thể. Giá này sẽ khác nhau tuỳ thuộc vào thiết bị và điều kiện lưu trữ. Bạn có thể biết mình đã kiếm được bao nhiêu thông qua:

navigator.storageQuota.queryInfo('temporary').then(function (info) {
  console.log(info.quota);
  // Result: <quota in bytes>
  console.log(info.usage);
  // Result: <used data in bytes>
});

Tuy nhiên, giống như tất cả bộ nhớ của trình duyệt, trình duyệt có thể xoá dữ liệu của bạn nếu thiết bị vẫn còn áp lực bộ nhớ. Thật không may, trình duyệt không thể phân biệt giữa các phim mà bạn muốn giữ lại bằng mọi giá và trò chơi mà bạn không thực sự quan tâm.

Để giải quyết vấn đề này, hãy sử dụng giao diện StorageManager:

// From a page:
navigator.storage.persist()
.then(function(persisted) {
  if (persisted) {
    // Hurrah, your data is here to stay!
  } else {
   // So sad, your data may get chucked. Sorry.
});

Tất nhiên, người dùng phải cấp quyền. Để thực hiện việc này, hãy sử dụng Permissions API.

Việc cho phép người dùng tham gia quy trình này là rất quan trọng vì giờ đây, chúng ta có thể kỳ vọng họ có quyền kiểm soát quá trình xoá. Nếu thiết bị của họ gặp áp lực về bộ nhớ và việc xoá các dữ liệu không cần thiết không giải quyết được vấn đề, thì người dùng sẽ đánh giá xem cần giữ lại và xoá mục nào.

Để làm được điều này, hệ điều hành phải xem các nguồn gốc "bền vững" là tương đương với các ứng dụng dành riêng cho nền tảng trong bảng phân tích mức sử dụng bộ nhớ, thay vì báo cáo trình duyệt dưới dạng một mục duy nhất.

Đề xuất phân phát – phản hồi các yêu cầu

Bất kể bạn lưu bộ nhớ đệm nhiều lần đến mức nào, trình chạy dịch vụ sẽ không sử dụng bộ nhớ đệm trừ phi bạn cho biết thời điểm và cách thức. Dưới đây là một số mẫu để xử lý yêu cầu:

Chỉ bộ nhớ đệm

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

Lý tưởng cho: mọi thứ mà bạn coi là tĩnh đối với một "phiên bản" cụ thể của trang web. Bạn nên lưu các truy vấn này vào bộ nhớ đệm trong sự kiện cài đặt để có thể phụ thuộc vào việc chúng có ở đó hay không.

self.addEventListener('fetch', function (event) {
  // If a match isn't found in the cache, the response
  // will look like a connection error
  event.respondWith(caches.match(event.request));
});

...mặc dù bạn không thường xuyên phải xử lý trường hợp này cụ thể, nhưng phần Cache, back to network (Bộ nhớ đệm, quay lại mạng) sẽ giải quyết vấn đề.

Chỉ với mạng

Chỉ mạng.
Chỉ mạng.

Lý tưởng cho: những tính năng không có phiên bản ngoại tuyến tương đương, chẳng hạn như ping phân tích, các yêu cầu không GET.

self.addEventListener('fetch', function (event) {
  event.respondWith(fetch(event.request));
  // or simply don't call event.respondWith, which
  // will result in default browser behavior
});

...mặc dù bạn không thường xuyên phải xử lý trường hợp này cụ thể, nhưng phần Cache, back to network (Bộ nhớ đệm, quay lại mạng) sẽ giải quyết vấn đề.

Vào bộ nhớ đệm, quay trở lại mạng

Vào bộ nhớ đệm, quay lại sử dụng mạng.
Bộ nhớ đệm, quay lại sử dụng mạng.

Lý tưởng cho: xây dựng chế độ ngoại tuyến. Trong những trường hợp như vậy, đây là cách bạn sẽ xử lý phần lớn yêu cầu. Các mẫu khác sẽ được coi là ngoại lệ dựa trên yêu cầu được gửi đến.

self.addEventListener('fetch', function (event) {
  event.respondWith(
    caches.match(event.request).then(function (response) {
      return response || fetch(event.request);
    }),
  );
});

Điều này mang đến cho bạn hành vi "chỉ lưu vào bộ nhớ đệm" cho mọi nội dung trong bộ nhớ đệm và hành vi "chỉ mạng" đối với mọi nội dung không được lưu vào bộ nhớ đệm (bao gồm mọi yêu cầu không phải GET, vì chúng không thể được lưu vào bộ nhớ đệm).

Cuộc đua với bộ nhớ đệm và mạng

Bộ nhớ đệm và cuộc đua mạng.
Bộ nhớ đệm và cuộc đua mạng.

Lý tưởng cho: những tài sản nhỏ mà bạn đang theo đuổi hiệu suất trên những thiết bị có tốc độ truy cập ổ đĩa chậm.

Với một số tổ hợp ổ đĩa cứng cũ, trình quét vi-rút và kết nối Internet nhanh hơn, việc nhận tài nguyên từ mạng có thể nhanh hơn so với đi vào ổ đĩa. Tuy nhiên, việc truy cập vào mạng khi người dùng có nội dung trên thiết bị của họ có thể là lãng phí dữ liệu, vì vậy hãy ghi nhớ điều đó.

// Promise.race is no good to us because it rejects if
// a promise rejects before fulfilling. Let's make a proper
// race function:
function promiseAny(promises) {
  return new Promise((resolve, reject) => {
    // make sure promises are all promises
    promises = promises.map((p) => Promise.resolve(p));
    // resolve this promise as soon as one resolves
    promises.forEach((p) => p.then(resolve));
    // reject if all promises reject
    promises.reduce((a, b) => a.catch(() => b)).catch(() => reject(Error('All failed')));
  });
}

self.addEventListener('fetch', function (event) {
  event.respondWith(promiseAny([caches.match(event.request), fetch(event.request)]));
});

Mạng trở lại bộ nhớ đệm

Mạng sẽ quay lại bộ nhớ đệm.
Mạng sẽ quay lại sử dụng bộ nhớ đệm.

Lý tưởng cho: bản sửa lỗi nhanh cho các tài nguyên cập nhật thường xuyên, bên ngoài "phiên bản" của trang web. Ví dụ: bài viết, hình đại diện, dòng thời gian trên mạng xã hội và bảng dẫn đầu trò chơi.

Điều này có nghĩa là bạn cung cấp cho người dùng trực tuyến nội dung mới nhất, nhưng người dùng ngoại tuyến sẽ nhận được phiên bản cũ hơn đã lưu vào bộ nhớ đệm. Nếu yêu cầu mạng thành công, nhiều khả năng bạn sẽ muốn cập nhật mục bộ nhớ đệm.

Tuy nhiên, phương pháp này có một số lỗi. Nếu người dùng có kết nối gián đoạn hoặc chậm, họ sẽ phải chờ mạng gián đoạn trước khi nhận được nội dung hoàn toàn chấp nhận được trên thiết bị của mình. Quá trình này có thể mất nhiều thời gian và gây khó chịu cho người dùng. Xem mẫu tiếp theo, Bộ nhớ đệm rồi đến mạng để có giải pháp tốt hơn.

self.addEventListener('fetch', function (event) {
  event.respondWith(
    fetch(event.request).catch(function () {
      return caches.match(event.request);
    }),
  );
});

Lưu vào bộ nhớ đệm rồi chọn mạng

Lưu vào bộ nhớ đệm rồi chọn mạng.
Lưu vào bộ nhớ đệm rồi chọn mạng.

Phù hợp với: nội dung cập nhật thường xuyên. Ví dụ: bài viết, dòng thời gian trên mạng xã hội, trò chơi. bảng xếp hạng.

Điều này đòi hỏi trang thực hiện hai yêu cầu, một yêu cầu đến bộ nhớ đệm và một yêu cầu đến mạng. Bạn nên hiển thị dữ liệu đã lưu vào bộ nhớ đệm trước, sau đó cập nhật trang khi/nếu có dữ liệu mạng.

Đôi khi, bạn chỉ cần thay thế dữ liệu hiện tại khi có dữ liệu mới (ví dụ: bảng xếp hạng trò chơi). Tuy nhiên, điều này có thể gây gián đoạn cho những nội dung lớn hơn. Về cơ bản, đừng "làm biến mất" nội dung mà có thể người dùng đang đọc hoặc tương tác.

Twitter thêm nội dung mới lên trên nội dung cũ và điều chỉnh vị trí cuộn để người dùng không bị gián đoạn. Điều này có thể xảy ra vì Twitter chủ yếu giữ lại thứ tự nội dung gần như theo tuyến tính. Tôi đã sao chép mẫu này cho training-to-thrill (được huấn luyện từ cảm giác mạnh) để lấy nội dung trên màn hình nhanh nhất có thể, đồng thời cho thấy nội dung cập nhật ngay khi đến.

Đoạn mã trong trang:

var networkDataReceived = false;

startSpinner();

// fetch fresh data
var networkUpdate = fetch('/data.json')
  .then(function (response) {
    return response.json();
  })
  .then(function (data) {
    networkDataReceived = true;
    updatePage(data);
  });

// fetch cached data
caches
  .match('/data.json')
  .then(function (response) {
    if (!response) throw Error('No data');
    return response.json();
  })
  .then(function (data) {
    // don't overwrite newer network data
    if (!networkDataReceived) {
      updatePage(data);
    }
  })
  .catch(function () {
    // we didn't get cached data, the network is our last hope:
    return networkUpdate;
  })
  .catch(showErrorMessage)
  .then(stopSpinner);

Mã trong Service Worker:

Bạn phải luôn kết nối với mạng và cập nhật bộ nhớ đệm trong khi sử dụng.

self.addEventListener('fetch', function (event) {
  event.respondWith(
    caches.open('mysite-dynamic').then(function (cache) {
      return fetch(event.request).then(function (response) {
        cache.put(event.request, response.clone());
        return response;
      });
    }),
  );
});

Trong phần traininged-to-thrill (được huấn luyện theo tiết lộ), tôi đã giải quyết vấn đề này bằng cách sử dụng XHR thay vì tìm nạp, và lạm dụng tiêu đề Accept (Chấp nhận) để cho Service Worker biết nơi lấy kết quả (mã trang, mã Service Worker).

Dự phòng chung

Dự phòng chung.
Dự phòng chung chung.

Nếu không phân phát được nội dung nào đó từ bộ nhớ đệm và/hoặc mạng, bạn nên cung cấp một dự phòng chung.

Lý tưởng cho: hình ảnh phụ như hình đại diện, các yêu cầu POST không thành công và trang "Không hoạt động khi không có mạng".

self.addEventListener('fetch', function (event) {
  event.respondWith(
    // Try the cache
    caches
      .match(event.request)
      .then(function (response) {
        // Fall back to network
        return response || fetch(event.request);
      })
      .catch(function () {
        // If both fail, show a generic fallback:
        return caches.match('/offline.html');
        // However, in reality you'd have many different
        // fallbacks, depending on URL and headers.
        // Eg, a fallback silhouette image for avatars.
      }),
  );
});

Mục mà bạn dự phòng có thể là một phần phụ thuộc về chế độ cài đặt.

Nếu trang của bạn đang đăng email, trình chạy dịch vụ của bạn có thể quay lại lưu trữ email trong 'hộp thư đi' IndexedDB và phản hồi bằng cách cho trang biết rằng quá trình gửi không thành công nhưng dữ liệu đã được lưu giữ thành công.

Tạo mẫu phía worker dịch vụ

Tạo mẫu phía ServiceWorker.
Tạo mẫu phía ServiceWorker.

Phù hợp cho: những trang không thể lưu phản hồi của máy chủ vào bộ nhớ đệm.

Việc hiển thị các trang trên máy chủ giúp mọi thứ diễn ra nhanh chóng, nhưng điều đó có nghĩa là bao gồm cả dữ liệu trạng thái không có ý nghĩa trong bộ nhớ đệm, ví dụ: "Đã đăng nhập là...". Nếu trang của bạn do một trình chạy dịch vụ kiểm soát, thì bạn có thể chọn yêu cầu dữ liệu JSON cùng với một mẫu và hiển thị dữ liệu đó.

importScripts('templating-engine.js');

self.addEventListener('fetch', function (event) {
  var requestURL = new URL(event.request.url);

  event.respondWith(
    Promise.all([
      caches.match('/article-template.html').then(function (response) {
        return response.text();
      }),
      caches.match(requestURL.path + '.json').then(function (response) {
        return response.json();
      }),
    ]).then(function (responses) {
      var template = responses[0];
      var data = responses[1];

      return new Response(renderTemplate(template, data), {
        headers: {
          'Content-Type': 'text/html',
        },
      });
    }),
  );
});

Kết hợp

Bạn không bị giới hạn ở một trong những phương pháp này. Trên thực tế, bạn có thể sẽ sử dụng nhiều thành phần tuỳ thuộc vào URL của yêu cầu. Ví dụ: traininged-to-thrill sử dụng:

Bạn chỉ cần xem yêu cầu đó rồi quyết định việc cần làm:

self.addEventListener('fetch', function (event) {
  // Parse the URL:
  var requestURL = new URL(event.request.url);

  // Handle requests to a particular host specifically
  if (requestURL.hostname == 'api.example.com') {
    event.respondWith(/* some combination of patterns */);
    return;
  }
  // Routing for local URLs
  if (requestURL.origin == location.origin) {
    // Handle article URLs
    if (/^\/article\//.test(requestURL.pathname)) {
      event.respondWith(/* some other combination of patterns */);
      return;
    }
    if (/\.webp$/.test(requestURL.pathname)) {
      event.respondWith(/* some other combination of patterns */);
      return;
    }
    if (request.method == 'POST') {
      event.respondWith(/* some other combination of patterns */);
      return;
    }
    if (/cheese/.test(requestURL.pathname)) {
      event.respondWith(
        new Response('Flagrant cheese error', {
          status: 512,
        }),
      );
      return;
    }
  }

  // A sensible default pattern
  event.respondWith(
    caches.match(event.request).then(function (response) {
      return response || fetch(event.request);
    }),
  );
});

...bạn hiểu được hình ảnh.

Ghi công

...cho các biểu tượng đáng yêu:

Cảm ơn Jeff Posnick đã phát hiện được nhiều lỗi hú trước khi tôi nhấn vào "phát hành".

Tài liệu đọc thêm