Các kỹ thuật phổ biến để xây dựng ứng dụng ngoại tuyến

Jake Archibald
Jake Archibald

Với Service Worker, chúng tôi đã cung cấp cho nhà phát triển một cách để giải quyết vấn đề kết nối mạng. Bạn có quyề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 có thể tạo các mẫu của riêng mình. Hãy xem xét một vài mẫu có thể có riêng biệt, nhưng trên thực tế, bạn có thể sẽ sử dụng chúng song song, tuỳ thuộc vào URL và ngữ cảnh.

Để xem bản minh hoạ hoạt động của một số mẫu này, hãy xem Trained-to-thrill.

Thời điểm lưu trữ tài nguyên

Browser Support

  • Chrome: 40.
  • Edge: 17.
  • Firefox: 44.
  • Safari: 11.1.

Source

Trình chạy dịch vụ cho phép bạn xử lý các yêu cầu độc lập với việc lưu vào bộ nhớ đệm, vì vậy, tôi sẽ minh hoạ riêng các yêu cầu này. Trước tiên, hãy xác định thời điểm bạn nên sử dụng bộ nhớ đệm.

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

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

Service Worker API cung cấp cho bạn một sự kiện install. Bạn có thể dùng phương thức này để chuẩn bị những thứ cần thiết, những thứ phải được chuẩn bị trước khi bạn xử lý các sự kiện khác. Trong install, các phiên bản trước của trình chạy dịch vụ vẫn tiếp tục chạy và phân phát các trang. Bất cứ điều gì bạn làm tại thời điểm này đều không được làm gián đoạn service worker hiện có.

Lý tưởng cho: CSS, hình ảnh, phông chữ, JS, mẫu hoặc bất kỳ nội dung nào khác mà bạn cho là tĩnh đối với phiên bản đó của trang web.

Tìm nạp những thứ sẽ 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, những thứ mà một ứng dụng tương đương dành riêng cho nền tảng sẽ đưa vào quá trình 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 hứa hẹn xác định thời lượng và mức độ thành công của lượt cài đặt. Nếu lời hứa bị từ chối, thì quá trình cài đặt được coi là không thành công và Trình chạy dịch vụ này sẽ bị huỷ (nếu một phiên bản cũ hơn đang chạy, thì phiên bản đó sẽ vẫn còn nguyên vẹn). caches.open()cache.addAll() trả về các promise. Nếu không tìm nạp được bất kỳ tài nguyên nào, lệnh gọi cache.addAll() sẽ từ chối.

Trên trained-to-thrill, tôi dùng tính năng này để lưu tài sản tĩnh vào bộ nhớ đệm.

Khi cài đặt, không phải là một phần phụ thuộc

Khi cài đặt, không phải là một phần phụ thuộc.

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

Lý tưởng cho: Các tài nguyên lớn hơn không cần thiết ngay lập tức, 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ụ này không truyền lời hứa cache.addAll cho 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ẽ có sẵn khi không có mạng. Tất nhiên, bạn sẽ phải đáp ứng khả năng không có các cấp độ đó và thử lại việc lưu vào bộ nhớ đệm nếu chúng bị thiếu.

Trình chạy dịch vụ có thể bị huỷ trong khi tải các cấp 11–20 xuống vì trình chạy này đã hoàn tất việc xử lý các sự kiện, nghĩa là các cấp này sẽ không được lưu vào bộ nhớ đệm. Web Periodic Background Synchronization API (API Đồng bộ hoá định kỳ trong nền trên web) có thể xử lý những trường hợp như thế này và các lượt tải xuống có kích thước lớn hơn, chẳng hạn như phim.

Browser Support

  • Chrome: 40.
  • Edge: 17.
  • Firefox: 44.
  • Safari: 11.1.

Source

Khi kích hoạt

Khi kích hoạt.

Phù hợp với: dọn dẹp và di chuyển.

Sau khi một service worker mới được cài đặt 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 một sự kiện activate. Vì phiên bản trước đã bị xoá, nên đây là thời điểm thích hợp để xử lý các hoạt động di chuyển giản đồ trong IndexedDB, đồng thời 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 như fetch sẽ được đưa vào hàng đợi, do đó, quá trình kích hoạt kéo dài có thể chặn quá trình tải trang. Hãy giữ cho quá trình kích hoạt càng tinh gọn càng tốt và chỉ sử dụng quá trình này cho những việc mà bạn không thể làm khi phiên bản trước đang hoạt động.

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

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

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

Lý tưởng cho: khi toàn bộ trang web không thể truy cập ngoại tuyến và bạn chọn cho phép người dùng chọn nội dung mà họ muốn truy cập ngoại tuyến. Ví dụ: một video trên YouTube, một 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 người dùng 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);
      });
  });
});

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

Browser Support

  • Chrome: 40.
  • Edge: 16.
  • Firefox: 41.
  • Safari: 11.1.

Source

Phản hồi trên mạng

Về phản hồi của mạng.

Phù hợp với: các tài nguyên thường xuyên cập nhật, chẳng hạn như hộp thư đến của người dùng hoặc nội dung bài viết. Cũng hữu ích cho nội dung không thiết yếu, chẳng hạn như hình đại diện, nhưng bạn cần thận trọng.

Nếu một yêu cầu không khớp với bất kỳ nội dung nào trong bộ nhớ đệm, hãy lấy yêu cầu đó từ mạng, gửi đế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 dải URL, chẳng hạn như hình đại diện, bạn cần cẩn thận để không làm phình to bộ nhớ của nguồn gốc. Nếu người dùng cần giải phóng dung lượng ổ đĩa, bạn không nên là ứng cử viên chính. Đảm bảo bạn loại bỏ những mục không cần thiết trong bộ nhớ đệm.

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 một phản hồi/yêu cầu một lần. Mẫu mã này dùng .clone() để tạo thêm các bản sao có thể đọc riêng.

Trên trained-to-thrill, tôi dùng phương thức này để lưu vào bộ nhớ đệm hình ảnh trên Flickr.

Stale-while-revalidate

Stale-while-revalidate.

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

Nếu có phiên bản được lưu vào bộ nhớ đệm, hãy sử dụng phiên bản đó, nhưng hãy tìm nạp bản cập nhật cho 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 stale-while-revalidate của HTTP.

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

Khi có thông báo đẩy.

Push API 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 trình chạy dịch vụ được kích hoạt để phản hồi một 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ẻ cho trang web của bạn. Chỉ service worker được kích hoạt. 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 với: nội dung liên quan đến một thông báo, chẳng hạn như tin nhắn trò chuyện, tin nóng hoặc email. Ngoài ra, nội dung ít thay đổi cũng được hưởng lợi từ tính năng đồng bộ hoá ngay lập tức, chẳng hạn như nội dung cập nhật danh sách việc cần làm hoặc nội dung thay đổi lịch.

Kết quả cuối cùng thường là một thông báo. Khi người dùng nhấn vào thông báo này, một trang có liên quan sẽ mở ra và được lấy làm tiêu điểm. Việc cập nhật bộ nhớ đệm trước đó là cực kỳ quan trọng. Người dùng đang trực tuyến tại thời điểm nhận thông báo đẩy, nhưng có thể họ không trực tuyến khi tương tác với thông báo. Vì vậy, bạn cần cung cấp nội dung này ở chế độ ngoại tuyến.

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/');
  }
});

On background-sync

Bật tính năng đồng bộ hoá ở chế độ nền.

Đồng bộ hoá dưới 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 trong nền một lần hoặc theo một khoảng thời gian (rất mang tính kinh nghiệm). Điều này xảy ra ngay cả khi người dùng không mở thẻ cho trang web của bạn. Chỉ trình chạy dịch vụ được kích hoạt. 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ẽ nhận được lời nhắc.

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

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

Khả năng lưu trữ cố định của 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 để làm những gì bạn muốn. Dung lượng trống đó được chia sẻ giữa tất cả các bộ nhớ gốc: Bộ nhớ(cục bộ), IndexedDB, Quyền truy cập vào 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. Thời gian 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 số tiền mình có bằng cách:

if (navigator.storage && navigator.storage.estimate) {
  const quota = await navigator.storage.estimate();
  // quota.usage -> Number of bytes used.
  // quota.quota -> Maximum number of bytes available.
  const percentageUsed = (quota.usage / quota.quota) * 100;
  console.log(`You've used ${percentageUsed}% of the available storage.`);
  const remaining = quota.quota - quota.usage;
  console.log(`You can write up to ${remaining} more bytes.`);
}

Tuy nhiên, giống như mọi bộ nhớ trình duyệt, trình duyệt có thể loại bỏ dữ liệu của bạn nếu thiết bị chịu áp lực về bộ nhớ. Rất tiếc, trình duyệt không thể phân biệt giữa những bộ phim mà bạn muốn giữ 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. Để làm việc này, hãy sử dụng Permissions API.

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

Để hoạt động, tính năng này yêu cầu các hệ điều hành coi nguồn gốc "bền vững" tương đương với các ứng dụng dành riêng cho nền tảng trong thông tin chi tiết về 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.

Đang phân phát đề xuất

Bất kể bạn lưu vào bộ nhớ đệm bao nhiêu, service worker chỉ sử dụng bộ nhớ đệm khi bạn cho biết thời điểm và cách thức. Sau đây là một số mẫu để xử lý các yêu cầu:

Chỉ bộ nhớ đệm

Chỉ bộ nhớ đệm.

Lý tưởng cho: mọi thứ mà bạn cho 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 giá trị này vào bộ nhớ đệm trong sự kiện cài đặt, vì vậy, bạn có thể dựa vào các giá trị này.

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 cần xử lý trường hợp này một cách cụ thể, Bộ nhớ đệm, dự phòng cho mạng sẽ xử lý trường hợp này.

Chỉ với mạng

Chỉ dùng mạng.

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

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

...mặc dù bạn không cần xử lý trường hợp này một cách cụ thể, Bộ nhớ đệm, dự phòng cho mạng sẽ xử lý trường hợp này.

Bộ nhớ đệm, dự phòng cho mạng

Bộ nhớ đệm, quay lại mạng.

Lý tưởng cho: xây dựng ứng dụng có 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 các yêu cầu. Các mẫu khác là ngoại lệ dựa trên yêu cầu đế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 lại cho bạn hành vi "chỉ bộ nhớ đệm" cho những nội dung trong bộ nhớ đệm và hành vi "chỉ mạng" cho mọi nội dung không được lưu vào bộ nhớ đệm (bao gồm tất cả các yêu cầu không phải là GET, vì chúng không thể được lưu vào bộ nhớ đệm).

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

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

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

Với một số tổ hợp ổ cứng cũ, trình quét vi-rút và kết nối Internet nhanh hơn, việc lấy tài nguyên từ mạng có thể nhanh hơn so với việc truy cập 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ể gây lãng phí dữ liệu, vì vậy hãy lưu ý điều đó.

// Promise.race rejects when a promise rejects before fulfilling.
// To make a 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 chuyển về bộ nhớ đệm

Mạng chuyển về bộ nhớ đệm.

Phù hợp với: giải pháp nhanh chóng cho những tài nguyên thường xuyên cập nhật, nằm 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 xếp hạng trong trò chơi.

Điều này có nghĩa là người dùng trực tuyến sẽ nhận được 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 trong bộ nhớ đệm. Nếu yêu cầu mạng thành công, rất có thể bạn sẽ muốn cập nhật mục nhập bộ nhớ đệm.

Tuy nhiên, phương pháp này có những điểm hạn chế. Nếu kết nối của người dùng bị gián đoạn hoặc chậm, họ sẽ phải đợi mạng gặp sự cố thì mới nhận được nội dung hoàn toàn chấp nhận được đã có trên thiết bị của họ. Việc này có thể mất rất nhiều thời gian và gây khó chịu cho người dùng. Hãy xem mẫu tiếp theo, Cache then network (Lưu vào bộ nhớ đệm rồi kết nối 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);
    }),
  );
});

Bộ nhớ đệm rồi đến mạng

Bộ nhớ đệm rồi đế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 và bảng xếp hạng trò chơi.

Điều này đòi hỏi trang phải thực hiện 2 yêu cầu, một yêu cầu đến bộ nhớ đệm và một yêu cầu đến mạng. Ý tưởng là trước tiên hiển thị dữ liệu được lưu vào bộ nhớ đệm, sau đó cập nhật trang khi dữ liệu mạng đến (nếu có).

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

Twitter sẽ 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 duy trì một thứ tự nội dung gần như tuyến tính. Tôi đã sao chép mẫu này cho trained-to-thrill để đưa nội dung lên màn hình nhanh nhất có thể, đồng thời hiển thị nội dung mới nhất ngay khi có.

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 trình chạy dịch vụ:

Bạn phải luôn truy cập vào mạng và cập nhật bộ nhớ đệm khi truy cập.

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 trained-to-thrill, 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ợi dụng tiêu đề Chấp nhận để cho trình chạy dịch vụ biết nơi lấy kết quả (mã trang, mã trình chạy dịch vụ).

Dự phòng chung

Bản dự phòng chung.

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

Phù hợp với: hình ảnh phụ như hình đại diện, yêu cầu POST không thành công và trang "Không dùng được 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 quay lại có thể là một phần phụ thuộc khi cài đặt.

Nếu trang của bạn đang đăng một email, thì trình chạy dịch vụ 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 giữ lại thành công.

Tạo mẫu phía trình chạy dịch vụ

Tạo mẫu phía trình chạy dịch vụ.

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

Kết xuất các trang trên máy chủ nhanh hơn, nhưng điều đó có thể có nghĩa là bao gồm dữ liệu trạng thái có thể không hợp lý trong bộ nhớ đệm, chẳng hạn như trạng thái đăng nhập. Nếu trang của bạn do một trình chạy dịch vụ kiểm soát, 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 đó thay thế.

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 các yếu tố

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

Bạn chỉ cần xem xét yêu cầu và 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);
    }),
  );
});

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

Ghi công

Đối với các biểu tượng đáng yêu:

Cảm ơn Jeff Posnick vì đã phát hiện ra nhiều lỗi nghiêm trọng trước khi tôi nhấn nút "xuất bản".