Использование Service Worker

В данной статье содержится информация о начале работы с сервис-воркерами, включая базовую архитектуру, процесс регистрации, а также установку и активацию новых сервис-воркеров, обновление существующих сервис-воркеров, управление кешем и настраиваемые ответы и все это в контексте простого приложения с офлайн-функциональностью.

Предпосылки появления Service Workers

Одной из важнейших проблем, от которой страдали пользователи веб-приложений, была работа в условиях потери связи. Лучшее в мире веб-приложение оставит ужасное впечатление от использования, если вы не сможете его загрузить. Предпринималось много попыток создания технологий, которые бы решили эту проблему, и если верить страницам нашего Форума, некоторые из вопросов были решены. Но всё же наиважнейшей проблемой по-прежнему является отсутствие хорошего механизма для управления кешем ресурсов и настраиваемыми сетевыми запросами.

Предыдущей попыткой была технология AppCache, казавшаяся хорошей идеей, потому как позволяла действительно просто указывать ресурсы для кеширования. Однако, эта технология допускает много предположений о том, что вы пытаетесь сделать, и затем с грохотом ломается, когда ваше приложение работает не в точности с этими допущениями. Чтобы получить больше информации по этой теме, прочитайте (неудачно названную, но хорошо написанную) статью Джейка Арчибальда Application Cache is a Douchebag.

Примечание: Начиная с Firefox 44, когда используется AppCache для предоставления странице поддержки офлайн-режима, в консоли разработчика отображается предупреждение о том, что нужно использовать технологию Service Workers (Firefox bug 1204581.)

Технология Service Workers должна в итоге решить озвученные выше вопросы. Синтаксис Service Worker более сложен, чем тот же AppCache, но взамен вы можете посредством JavaScript контролировать AppCache-подразумеваемое поведение с высокой степенью детализации, что позволяет вам решить описанную проблему и многие другие. Используя Service Worker, вы можете без труда получить приложение, использующее в первую очередь кешированные ресурсы, предоставляя тем самым поведение по умолчанию в автономном режиме, до того как будет получено по сети больше данных (такой подход называется Offline First). Так обычно работают нативные приложения, что часто является причиной выбора пользователя в их пользу.

Что нужно настроить, чтобы поиграть с Service Worker

Многие функции Service Worker теперь включены по умолчанию в новых браузерах, поддерживающих эту технологию. Однако, если вы обнаружите, что демонстрационный код не работает в вашей версии браузера, вам может понадобиться их включить:

  • Firefox Nightly: Перейдите в раздел about:config и установите параметр dom.serviceWorkers.enabled в значение true; затем перезапустите браузер.
  • Chrome Canary: Перейдите в раздел chrome://flags и включите experimental-web-platform-features; перезапустите браузер (заметьте, что некоторые функции теперь включены по умолчанию в браузере Chrome.)
  • Opera: Перейдите в раздел opera://flags и включите Support for ServiceWorker; перезапустите браузер.
  • Microsoft Edge: Перейдите в раздел about:flags и поставьте галочку Enable service workers; перезапустите браузер.

Также вам необходимо предоставлять ваш код по протоколу HTTPS — Service Worker требует этого по соображениям безопасности. По этой причине GitHub — хороший выбор для экспериментов, поскольку он поддерживает протокол HTTPS по умолчанию. Для облегчения локальной разработки браузеры считают localhost также безопасным origin.

Базовая архитектура

Чтобы сделать базовую настройку Service Worker, как правило, нужно пройти следующие шаги:

  1. URL сервис-воркера опрашивается и регистрируется посредством вызова метода ServiceWorkerContainer.register().
  2. Если регистрация прошла успешно, то сервис-воркер начинает работать внутри ServiceWorkerGlobalScope (en-US); это, по сути, особый вид контекста воркера, работающий вне главного потока браузера, без доступа к DOM.
  3. Теперь сервис-воркер может обрабатывать события.
  4. Установка сервис-воркера начинается после того, как все контролируемые им страницы закешированы и доступны для последующего использования. Событие install всегда посылается первым воркеру (оно может быть использовано для запуска начальной загрузки данных в IndexedDB, для кеширования ресурсов). Данный этап сродни процедуре установки нативного или FirefoxOS-приложения — все делается доступным для использования в офлайн-режиме.
  5. Как только обработчик события oninstall завершит свою работу, сервис-воркер считается установленным.
  6. Далее следует активация. После того как воркер установлен, он получает событие onactivate, которое обычно используется для очистки ресурсов, задействованных в предыдущей версии скрипта сервис-воркера.
  7. Сервис-воркер здесь может контролировать страницы, но только в случае, если те открыты после успешного вызова register(). То есть документ может начать жизнь с сервис-воркером или даже без него и продолжать нормально работать. Поэтому документы должны быть перезагружены, чтобы действительно быть подконтрольными сервис-воркеру.

Следующий рисунок кратко показывает доступные события Service Worker:

install, activate, message, fetch, sync, push

Промисы

Промисы — отличный механизм для запуска асинхронных операций, которые могут успешно зависеть друг от друга. Промисы имеют большое значение в работе сервис-воркеров.

Промисы могут делать много всего, но сейчас вам достаточно знать, что если что-то возвращает промис, то вы можете дописать вызов метода .then(), передав ему функцию на случай удачного выполнения, и вызов метода .catch(), если хотите передать функцию при неуспешном выполнении.

Давайте сравним структуру традиционного синхронного колбэка с его промис-эквивалентом.

sync

js
try {
  const value = myFunction();
  console.log(value);
} catch (err) {
  console.log(err);
}

async

js
myFunction()
  .then((value) => {
    console.log(value);
  })
  .catch((err) => {
    console.log(err);
  });

В первом примере код, идущий за вызовом функции myFunction(), будет ждать завершения вызова и возврата значения. Во втором примере myFunction() возвращает промис для value, в этом случае, последующий код сможет выполняться, не дожидаясь завершения основной работы функции. Когда промис разрешится, код, переданный методу then, будет выполнен асинхронно.

А вот вам реальный пример: что, если мы хотим загружать изображения динамически, к тому же мы желаем удостовериться, что изображения загрузились до того, как они будут показаны? То, что мы хотим сделать, является стандартной задачей, но она всё же может доставить головной боли. Мы можем использовать .onload, чтобы показать изображение только после загрузки, но что делать с событиями, которые могут произойти до того, как мы начнём их слушать? Мы могли бы использовать .complete, но оно все ещё ненадёжно, да и что делать с повторяющимися изображениями? И наконец все это работает синхронно, блокируя главный поток.

Вместо этого мы можем написать собственный промис для работы с подобными случаями. (Вы можете найти исходный код в нашем примере Promises test или взглянуть на живое демо.)

Примечание: Реальные реализации сервис-воркеров скорее всего будут использовать onfetch, а не устаревающий XMLHttpRequest API. Эти возможности не используются здесь, так что можете сосредоточиться на изучении промисов.

js
const imgLoad = (url) => {
  return new Promise((resolve, reject) => {
    var request = new XMLHttpRequest();
    request.open("GET", url);
    request.responseType = "blob";

    request.onload = () => {
      if (request.status == 200) {
        resolve(request.response);
      } else {
        reject(
          Error(
            "Image didn't load successfully; error code:" + request.statusText,
          ),
        );
      }
    };

    request.onerror = () => {
      reject(Error("There was a network error."));
    };

    request.send();
  });
};

Мы возвращаем новый промис, созданный конструктором Promise(), который в качестве аргумента принимает функцию с параметрами resolve и reject. Где-то внутри функции мы должны определить случаи, при которых промис должен быть разрешён или отклонён, — в нашем случае, в зависимости от того, вернулся ли статус 200 ОК или нет, будут вызваны resolve в случае успеха или reject при неудаче. Последующее содержимое этой функции — вполне стандартное XHR-наполнение, поэтому на данный момент не стоит о нем волноваться.

Вызывая функцию imgLoad(), мы ожидаемо передаём в качестве параметра url изображения, которое хотим загрузить, но далее код немного отличается:

js
let body = document.querySelector("body");
let myImage = new Image();

imgLoad("myLittleVader.jpg").then(
  (response) => {
    var imageURL = window.URL.createObjectURL(response);
    myImage.src = imageURL;
    body.appendChild(myImage);
  },
  (Error) => {
    console.log(Error);
  },
);

После вызова функции мы "цепляем" к ней вызов промис-метода then(), которому в качестве параметров передаём две функции - первая будет вызвана в случае выполнения промиса, созданного вызовом функции imgLoad(), вторая функция будет вызвана в случае отклонения этого промиса. В случае выполнения мы показываем изображение в элементе myImage, который прикрепляем к body (аргументом является request.response, помещённый в промис-методе resolve); в случае отклонения промиса в консоли будет отображено сообщение об ошибке.

Все это происходит асинхронно.

Примечание: вы можете также объединять вызов нескольких промис-методов в одну цепочку, как в этом примере: myPromise().then(success, failure).then(success).catch(failure);

Примечание: вы можете получить гораздо больше информации о промисах, прочитав превосходную статью Джейка Арчибальда (Jake Archibald's) JavaScript Promises: there and back again.

Демонстрация Service Workers

Чтобы продемонстрировать только базовые моменты регистрации и установки сервис-воркеров, мы создали простое демо-приложение, названое sw-test. Это простая галерея изображений "Star wars Lego". Оно использует промис-функции, чтобы прочитать из JSON-объекта и загрузить, используя технологию Ajax, изображения, находящиеся далее нижнего края страницы, до того как они будут показаны. В приложении также ещё регистрируется, устанавливается и активируется сервис-воркер, и, в случае если браузер поддерживает спецификацию Service Worker, запрашиваемые ресурсы будут закешированы, и приложение будет работать в офлайн-режиме!

Вы можете посмотреть исходный код на GitHub, а также этот живой пример. Единственное, что мы тут рассмотрим, это промис (смотрите app.js строки 22-47), модифицированная версия того, о котором вы читали выше в разделе Тестовая демонстрация промисов. Разница в следующем:

  1. Ранее мы передавали параметром лишь URL изображения, которое мы хотели загрузить. Теперь же, мы передаём JSON-фрагмент, содержащий все данные для изображения (посмотрите, как это выглядит в image-list.js). Это сделано потому, что все данные для выполнения каждого промиса должны быть переданы ему, так как он выполняется асинхронно. Если же вы передали лишь URL, а чуть позже попытались получить доступ к другим атрибутам в JSON-фрагменте внутри цикла for(), это бы не сработало, так как этот промис не был бы выполнен во время текущей итерации цикла (это синхронный процесс).
  2. Теперь мы выполняем промис с параметром-массивом, так как дальше мы хотим сделать загруженные данные изображения доступными для разрешающей функции, одновременно с именем файла, данными авторства и замещающим текстом (см. app.js строки 31-34). Промисы будут выполняться со всего одним аргументом, поэтому, если вы хотите выполнить их с несколькими параметрами, вы должны использовать массив/объект.
  3. Затем, чтобы получить доступ к выполненным значениям промисов, мы обращаемся к ним так, как было задумано (смотрите app.js строки 60-64). По началу это может выглядеть немного странно, но именно так и работают промисы.

Погружение в Service Worker

Итак, переходим к Service Worker!

Регистрация воркеров

Ниже представлен первый блок кода файла app.js. Это точка входа в Service Worker.

js
if ("serviceWorker" in navigator) {
  navigator.serviceWorker
    .register("./sw-test/sw.js", { scope: "./sw-test/" })
    .then((reg) => {
      // регистрация сработала
      console.log("Registration succeeded. Scope is " + reg.scope);
    })
    .catch((error) => {
      // регистрация прошла неудачно
      console.log("Registration failed with " + error);
    });
}
  1. Внешний условный блок выполняет проверку на поддержку Service Worker, чтобы убедиться что технология доступна, до того как начать регистрацию.
  2. Далее, чтобы зарегистрировать сервис-воркера для этого сайта, мы используем функцию ServiceWorkerContainer.register(). Сервис-воркер представляет собой JavaScript-файл приложения (обратите внимание, что URL указывается относительно "корня", а не места расположения JS-файла, регистрирующего сервис-воркер).
  3. Параметр scope - не обязателен, он может быть использован для указания подмножества контента, которое вы хотите отдать под контроль сервис-воркера. В нашем случае, мы указали './sw-test/'. Если вы не укажете его, то будет использовано значение по умолчанию; мы же указали его только в целях иллюстрации.
  4. Метод .then() был использован для обработки успешной регистрации. Если промис разрешится успешно, то код, переданный этому методу, будет выполнен.
  5. Ну и наконец, в конец нашего промиса мы добавляем функцию .catch(), которая будет выполнена в случае, если промис будет отклонён.

Предыдущий код регистрирует сервис-воркера, который работает в worker-контексте, и следовательно, не имеет доступа к DOM. Затем вы запускаете код в сервис-воркере, вне ваших страниц, чтобы контролировать их загрузку.

Один сервис-воркер может контролировать несколько страниц. Каждый раз, когда загружается страница, находящаяся в пределах области видимости, сервис-воркер будет установлен на ней и начнёт работу. Поэтому будьте осторожны с применением глобальных переменных в скриптах сервис-воркеров, потому как у каждой страницы нет своего уникального экземпляра сервис-воркера.

Примечание: Сервис-воркеры функционально похожи на прокси-серверы, они позволяют модифицировать запросы и ответы, замещая записями из собственного кеша, и многое другое.

Примечание: Есть одна очень хорошая особенность при работе с сервис-воркерами. В случае, если вы используете функциональность проверки поддержки Service Worker, то приложение в браузерах, не имеющих поддержки, продолжат нормально работать с ожидаемым поведением. Кроме того, если браузер поддерживает только AppCache, то будет использована эта технология. В случае, если браузер поддерживает и AppCache и Service Worker, то будет использована Service Worker.

Почему мой сервис-воркер не прошёл регистрацию?

Это может произойти по следующим причинам:

  1. Приложение загружено не по протоколу HTTPS.
  2. Путь к сервис-воркеру указан некорректно — он должен быть написан относительно origin запроса, а не вашей корневой директории с приложением. В нашем примере воркер расположен в https://mdn.github.io/sw-test/sw.js, корневая папка — https://mdn.github.io/sw-test/. Но в качестве пути к сервис-воркеру нужно указывать /sw-test/sw.js, а не /sw.js.
  3. Origin сервис-воркера отличается от origin вашего приложения. Это также запрещено.

Также обратите внимание:

  • В сервис-воркер будут попадать только те запросы, которые соответствуют его области видимости.
  • Максимальная область видимость сервис-воркера равна его location.
  • Если ваш сервис-воркер работает на клиенте, которому был передан заголовок Service-Worker-Allowed, вы можете указать список максимальных областей видимости для этих воркеров.

Установка и активация: заполнение кеша

После того как ваш сервис-воркер будет зарегистрирован, браузер может попробовать установить его и активировать на странице/сайте.

Событие install возникает после того как установка успешно завершится. Это событие используется главным образом для того, чтобы заполнить кеш браузера ресурсами, необходимыми для успешного запуска в офлайн-режиме. Для этого используется новый API хранилища Service Worker — cache — глобальный для всех сервис-воркеров, который позволяет нам хранить результаты запросов, используя в качестве ключа для их получения сами запросы. Этот API работает аналогично стандартному кешу браузера, но только для вашего домена. Данные в кеше сохраняются до тех пор, пока вы сами не решите их удалить — вы имеете полный контроль.

Примечание: Cache API поддерживается не всеми браузерами (смотрите раздел Browser support чтобы получить больше информации). Если вы хотите сейчас использовать эту технологию, то можете рассмотреть возможность использования полифила, который доступен в Google's Topeka demo, или можете хранить ресурсы в IndexedDB.

Давайте начнём этот раздел посмотрев на фрагмент кода ниже — это первый блок кода, который вы увидите в нашем сервис-воркере:

js
self.addEventListener("install", (event) => {
  event.waitUntil(
    caches.open("v1").then((cache) => {
      return cache.addAll([
        "./sw-test/",
        "./sw-test/index.html",
        "./sw-test/style.css",
        "./sw-test/app.js",
        "./sw-test/image-list.js",
        "./sw-test/star-wars-logo.jpg",
        "./sw-test/gallery/",
        "./sw-test/gallery/bountyHunters.jpg",
        "./sw-test/gallery/myLittleVader.jpg",
        "./sw-test/gallery/snowTroopers.jpg",
      ]);
    }),
  );
});
  1. Здесь мы добавляем обработчик события install к сервис-воркеру (отныне self), и затем вызываем метод ExtendableEvent.waitUntil() объекта события. Такая конструкция гарантирует, что сервис-воркер не будет установлен, пока код, переданный внутри waitUntil(), не завершится с успехом.
  2. Внутри waitUntil() мы используем метод caches.open(), чтобы создать новый кеш, который назовём v1, это будет первая версия кеша ресурсов. Этот метод возвращает промис для созданного кеша; когда он выполнится, у объекта созданного кеша мы вызовем метод addAll(), который в качестве параметра ожидает получить массив origin-относительных URL всех ресурсов, которые мы хотим хранить в кеше.
  3. Если промис будет отклонён, то установка будет завершена неудачно, и воркер ничего не сделает. Это хорошо, потому как вы можете исправить свой код и затем попробовать провести регистрацию в следующий раз.
  4. После успешной установки сервис-воркер активируется. Этот момент не очень важен при первоначальной установке/активации сервис-воркера, в то же время он имеет большое значение, когда происходит обновление воркера (смотрите раздел Обновление вашего сервис-воркера, находящийся ниже).

Примечание: localStorage (en-US) работает схожим образом, но в синхронном режиме, поэтому запрещён в сервис-воркерах.

Примечание: При необходимости хранить данные в сервис-воркерах вы можете использовать IndexedDB.

Настраиваемые ответы на запросы

Теперь ресурсы вашего сайта находятся в кеше и вам необходимо указать сервис-воркеру, что делать с этим контентом. Это легко сделать, обработав событие fetch.

Событие fetch возникает каждый раз, когда запрашиваются любые подконтрольные сервис-воркеру ресурсы, к которым относятся документы из области видимости и другие ресурсы, связанные с этими документами (например, если в index.html происходит кросс-доменный запрос для загрузки изображения, то он тоже попадёт в сервис-воркер).

Вы можете подключить к сервис-воркеру обработчик события fetch и внутри него на объекте события вызвать метод respondWith(), чтобы заменить ответы и показать собственную "магию".

js
self.addEventListener("fetch", (event) => {
  event
    .respondWith
    // магия происходит здесь
    ();
});

Для начала, на каждый сетевой запрос мы можем отдать в ответ ресурс, чей url соответствует запросу:

js
self.addEventListener("fetch", (event) => {
  event.respondWith(caches.match(event.request));
});

caches.match(event.request) позволяет нам проверять сетевой запрос ресурса на соответствие какому-либо доступному в кеше ресурсу, если такой ресурс имеется. Соответствие проверяется по url и изменяемым заголовкам.

Давайте рассмотрим несколько других вариантов реализации нашей магии (чтобы получить больше информации об интерфейсах Request и Response смотрите документацию к Fetch API.)

  1. Конструктор Response() позволяет вам создавать собственные ответы. В данном случае, мы всего лишь возвращаем простую текстовую строку:
    js
    new Response("Hello from your friendly neighbourhood service worker!");
    
    В этом более сложном объекте Response показано, как вы можете передать набор заголовков в свой ответ, эмулируя стандартный HTTP-ответ. Здесь мы просто сообщаем браузеру, чем является содержимое ответа:
    js
    new Response(
      "<p>Hello from your friendly neighbourhood service worker!</p>",
      {
        headers: { "Content-Type": "text/html" },
      },
    );
    
  2. Если совпадение не было найдено в кеше, вы можете попросить браузер загрузить (en-US) тот же ресурс, чтобы получить новый файл через обычную сеть, если она доступна:
    js
    fetch(event.request);
    
  3. Если информация, соответствующая запросу, в кеше не найдена, а также сеть не доступна, то вы можете просто ответить на запрос какой-либо страницей по умолчанию, которая хранится в кеше, используя match():
    js
    caches.match("./fallback.html");
    
  4. Вы можете получить больше информации о каждом запросе, используя для этого свойства объекта Request, который можно получить как свойство объекта FetchEvent:
    js
    event.request.url;
    event.request.method;
    event.request.headers;
    event.request.body;
    

Восстановление неудачных запросов

Итак, caches.match(event.request) отработает как нужно только в том случае, если в кеше сервис-воркера будет найдено соответствие запросу. Но что произойдёт, если такого соответствия не будет найдено? Если мы не предоставим никакого механизма обработки такой ситуации, то промис выполнится со значением undefined и мы не получим никакого значения.

К счастью, сервис-воркеры имеют структуру основанную на промисах, что делает тривиальной такую обработку и предоставляет большое количество способов успешно обработать запрос:

js
self.addEventListener("fetch", (event) => {
  event.respondWith(
    caches.match(event.request).then((response) => {
      return response || fetch(event.request);
    }),
  );
});

Если промис будет отклонён, функция catch() вернёт обычный сетевой запрос к внешнему ресурсу. Это значит, что, если сеть доступна, то ресурс просто загрузится с сервера.

Если же мы были достаточно умны, то мы не стали бы просто возвращать сетевой запрос, а сохранили бы его результат в кеше, чтобы иметь возможность получить его в офлайн-режиме. В случае с нашим демо-приложением "Star Wars gallery", это означает, что, если в галерею будет добавлено ещё одно изображение, то оно будет получено и сохранено в кеше:

js
self.addEventListener("fetch", (event) => {
  event.respondWith(
    caches.match(event.request).then((resp) => {
      return (
        resp ||
        fetch(event.request).then((response) => {
          return caches.open("v1").then((cache) => {
            cache.put(event.request, response.clone());
            return response;
          });
        })
      );
    }),
  );
});

Здесь мы возвращаем обычный сетевой запрос, который возвращён вызовом fetch(event.request); этот запрос также является промисом. Когда промис разрешится, мы получим кеш вызвав caches.open('v1'); этот метод также возвращает промис. Когда разрешится уже второй промис, будет использован вызов cache.put(), чтобы поместить ресурс в кеш. Ресурс получен через event.request, а ответ — через клонирование response.clone(). Клон помещается в кеш, а оригинальный ответ передаётся браузеру, который передаёт его странице, которая запросила ресурс.

Почему? Потому что потоки запроса и ответа могут быть прочитаны только единожды. Чтобы ответ был получен браузером и сохранён в кеше, нам нужно клонировать его. Так оригинальный объект отправится браузеру, а клон будет закеширован. Оба они будут прочитаны единожды.

У нас все ещё остаётся единственная проблема - если на какой-либо запрос в кеше не будет найдено соответствие, и в этот момент сеть не доступна, то наш запрос завершится неудачно. Давайте реализуем запасной вариант по умолчанию, при котором пользователь, в описанном случае, будет получать хоть что-нибудь:

js
self.addEventListener("fetch", (event) => {
  event.respondWith(
    caches
      .match(event.request)
      .then((resp) => {
        return (
          resp ||
          fetch(event.request).then((response) => {
            let responseClone = response.clone();
            caches.open("v1").then((cache) => {
              cache.put(event.request, responseClone);
            });

            return response;
          })
        );
      })
      .catch(() => {
        return caches.match("./sw-test/gallery/myLittleVader.jpg");
      }),
  );
});

Здесь мы решили обрабатывать только картинки, потому что единственные запросы, которые могут не удастся — это загрузка новых картинок, так как все остальное было закешировано во время обработки события install, которое мы обсуждали ранее.

Обновление вашего сервис-воркера

Если после того, как ваш сервис-воркер был установлен, стала доступна его новая версия, при обновлении или загрузке страницы она будет установлена в фоновом режиме, но не будет активирована. Она будет активирована, лишь когда не останется ни одной страницы, использующей старую версию сервис-воркера.

Вы можете обновить обработчик события install в новой версии сервис-воркера, чтобы получить примерно такое (обратите внимание на номер новой версии):

js
self.addEventListener('install', (event) => {
  event.waitUntil(
    caches.open('v2').then((cache) => {
      return cache.addAll([
        './sw-test/',
        './sw-test/index.html',
        './sw-test/style.css',
        './sw-test/app.js',
        './sw-test/image-list.js',// подключение прочих ресурсов для новой версии...
      ]);
    })
  );
});

В то время, как этот код работает, предыдущая версия также доступна. Новая версия устанавливается в фоновом режиме. Мы можем работать с версией кеша v2, в то время как версия v1 не будет разрушена.

Когда ни одна страница не будет использовать текущую версию, новый воркер активируется и станет ответственным за обработку всех запросов.

Удаление старого кеша

Вы можете обрабатывать событие activate. Оно обычно используется в случае, если нужно выполнить такие действия, которые бы нарушили работу воркеров предыдущей версии, если они все ещё работают со старым кешем. Также это событие полезно использовать для того, чтобы удалить ненужные данные, чтобы освободить место, занимаемое на диске, потому что каждый браузер имеет жёсткие ограничения на размер хранилища кеша, которое доступно для использования сервис-воркерами.

Promise, переданный в waitUntil(), заблокирует другие события до своего завершения, поэтому можно быть уверенным, что процесс очистки закончится раньше, чем выполнится первое событие fetch на основе нового кеша.

js
self.addEventListener("activate", (event) => {
  var cacheKeeplist = ["v2"];

  event.waitUntil(
    caches.keys().then((keyList) => {
      return Promise.all(
        keyList.map((key) => {
          if (cacheKeeplist.indexOf(key) === -1) {
            return caches.delete(key);
          }
        }),
      );
    }),
  );
});

Инструменты разработчика

В браузере Chrome есть раздел chrome://inspect/#service-workers, где можно увидеть активность сервис-воркеров и их хранилища. Также есть раздел chrome://serviceworker-internals, в котором можно получить более детальную информацию, запустить, остановить и отладить процессы сервис-воркеров. В будущем ожидается поддержка эмуляции различного качества сетевых соединений и их отсутствия, что будет весьма кстати.

Firefox также начал реализовывать полезные инструменты для разработки сервис-воркеров:

  • Вы можете пройти в раздел about:debugging , чтобы посмотреть зарегистрированные сервис-воркеры и обновить или удалить их.
  • Во время тестирования вы можете снять ограничение HTTPS, поставив флажок "Enable Service Workers over HTTP (when toolbox is open)" в настройках Firefox Developer Tools.
  • Кнопка "Забыть", включаемая в настройках «Персонализация» в Firefox, позволяет удалить все сервис-воркеры и очистить их кеши.

Примечание: вы можете держать своё приложение на http://localhost (например, используя me@localhost:/my/app$ python -m SimpleHTTPServer) для локальной разработки. См. Security considerations

Смотрите также