Жизненный цикл сервисного работника

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

Это глубокое погружение, но пункты в начале каждого раздела охватывают большую часть того, что вам нужно знать.

Намерение

Целью жизненного цикла является:

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

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

Первый сервисный работник

Вкратце:

  • Событие install — это первое событие, которое получает сервисный работник, и оно происходит только один раз.
  • Обещание, передаваемое в installEvent.waitUntil() сигнализирует о продолжительности и успехе или неудаче вашей установки.
  • Сервисный работник не будет получать такие события, как fetch и push , пока он успешно не завершит установку и не станет «активным».
  • По умолчанию выборка страницы не будет проходить через сервис-воркера, если только сам запрос страницы не прошел через сервис-воркера. Поэтому вам придется обновить страницу, чтобы увидеть результаты работы сервис-воркера.
  • clients.claim() может переопределить это значение по умолчанию и получить контроль над неконтролируемыми страницами.

Возьмите этот HTML:

<!DOCTYPE html>
An image will appear here in 3 seconds:
<script>
  navigator.serviceWorker.register('/sw.js')
    .then(reg => console.log('SW registered!', reg))
    .catch(err => console.log('Boo!', err));

  setTimeout(() => {
    const img = new Image();
    img.src = '/dog.svg';
    document.body.appendChild(img);
  }, 3000);
</script>

Он регистрирует сервис-воркера и через 3 секунды добавляет изображение собаки.

Вот его сервис-воркер, sw.js :

self.addEventListener('install', event => {
  console.log('V1 installing…');

  // cache a cat SVG
  event.waitUntil(
    caches.open('static-v1').then(cache => cache.add('/cat.svg'))
  );
});

self.addEventListener('activate', event => {
  console.log('V1 now ready to handle fetches!');
});

self.addEventListener('fetch', event => {
  const url = new URL(event.request.url);

  // serve the cat SVG from the cache if the request is
  // same-origin and the path is '/dog.svg'
  if (url.origin == location.origin && url.pathname == '/dog.svg') {
    event.respondWith(caches.match('/cat.svg'));
  }
});

Он кэширует изображение кошки и обслуживает его всякий раз, когда поступает запрос /dog.svg . Однако, если вы запустите приведенный выше пример , вы увидите собаку при первой загрузке страницы. Нажмите «Обновить», и вы увидите кота.

Область применения и контроль

Область регистрации сервисного работника по умолчанию — ./ относительно URL-адреса сценария. Это означает, что если вы зарегистрируете сервис-воркера на //example.com/foo/bar.js , он будет иметь область действия по умолчанию //example.com/foo/ .

Мы называем страницы, рабочие и общие рабочие процессы clients . Ваш сервисный работник может управлять только клиентами, которые находятся в области действия. Как только клиент становится «контролируемым», его выборка проходит через сервисного работника, находящегося в области действия. Вы можете определить, контролируется ли клиент через navigator.serviceWorker.controller , который будет иметь значение null или экземпляр сервисного работника.

Скачать, проанализировать и выполнить

Ваш самый первый сервис-воркер загружает, когда вы вызываете .register() . Если ваш скрипт не может загрузить, проанализировать или выдает ошибку при первоначальном выполнении, обещание регистрации отклоняется, и сервис-воркер удаляется.

Инструменты разработчика Chrome отображают ошибку в консоли и в разделе Service Worker на вкладке приложения:

Ошибка отображается на вкладке DevTools сервисного работника

Установить

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

Событие install — это ваш шанс кэшировать все, что вам нужно, прежде чем вы сможете управлять клиентами. Обещание, которое вы передаете в event.waitUntil() позволяет браузеру узнать, когда установка завершится и прошла ли она успешно.

Если ваше обещание отклоняется, это означает, что установка не удалась, и браузер удаляет сервис-воркера. Он никогда не будет контролировать клиентов. Это означает, что мы не можем полагаться на присутствие cat.svg в кеше в наших событиях fetch . Это зависимость.

Активировать

Как только ваш сервисный работник будет готов управлять клиентами и обрабатывать функциональные события, такие как push и sync , вы получите событие activate . Но это не означает, что страница, вызвавшая .register() , будет контролироваться.

При первой загрузке демо-версии , несмотря на то, что dog.svg запрашивается спустя долгое время после активации сервис-воркера, он не обрабатывает запрос, и вы все равно видите изображение собаки. По умолчанию используется согласованность . Если ваша страница загружается без сервисного работника, ее подресурсы тоже не будут загружаться. Если вы загрузите демо-версию второй раз (другими словами, обновите страницу), она будет под контролем. И страница, и изображение пройдут через события fetch , и вместо этого вы увидите кошку.

клиенты.претензия

Вы можете взять под контроль неконтролируемых клиентов, вызвав clients.claim() внутри вашего сервис-воркера после его активации.

Вот вариант приведенной выше демонстрации , в которой в событии activate вызывается clients.claim() . Вы должны увидеть кошку в первый раз. Я говорю «должен», потому что это зависит от времени. Вы увидите кота только в том случае, если активируется сервисный работник и clients.claim() вступит в силу до того, как изображение попытается загрузиться.

Если вы используете своего сервис-воркера для загрузки страниц иначе, чем они загружаются по сети, clients.claim() может вызвать проблемы, поскольку ваш сервис-воркер в конечном итоге будет контролировать некоторых клиентов, которые загружались без него.

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

Вкратце:

  • Обновление запускается, если происходит любое из следующих событий:
    • Переход на нужную страницу.
    • Функциональные события, такие как push и sync , если в течение предыдущих 24 часов не проводилась проверка обновлений.
    • Вызов .register() только в том случае, если URL-адрес сервис-воркера изменился. Однако вам следует избегать изменения рабочего URL-адреса .
  • Большинство браузеров, включая Chrome 68 и более поздние версии , по умолчанию игнорируют заголовки кэширования при проверке обновлений зарегистрированного сценария Service Worker. Они по-прежнему учитывают заголовки кэширования при извлечении ресурсов, загруженных внутри сервисного работника, через importScripts() . Вы можете переопределить это поведение по умолчанию, установив параметр updateViaCache при регистрации вашего сервис-воркера.
  • Ваш сервис-воркер считается обновленным, если он на байт отличается от того, который уже есть в браузере. (Мы расширяем эту возможность, включив в нее также импортированные скрипты/модули.)
  • Обновленный сервис-воркер запускается вместе с существующим и получает собственное событие install .
  • Если ваш новый рабочий процесс имеет недопустимый код состояния (например, 404), не может выполнить синтаксический анализ, выдает ошибку во время выполнения или отклоняет во время установки, новый рабочий процесс удаляется, но текущий остается активным.
  • После успешной установки обновленный рабочий процесс будет wait , пока существующий рабочий процесс не будет контролировать ноль клиентов. (Обратите внимание, что клиенты перекрываются во время обновления.)
  • self.skipWaiting() предотвращает ожидание, то есть сервис-воркер активируется сразу после завершения установки.

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

const expectedCaches = ['static-v2'];

self.addEventListener('install', event => {
  console.log('V2 installing…');

  // cache a horse SVG into a new cache, static-v2
  event.waitUntil(
    caches.open('static-v2').then(cache => cache.add('/horse.svg'))
  );
});

self.addEventListener('activate', event => {
  // delete any caches that aren't in expectedCaches
  // which will get rid of static-v1
  event.waitUntil(
    caches.keys().then(keys => Promise.all(
      keys.map(key => {
        if (!expectedCaches.includes(key)) {
          return caches.delete(key);
        }
      })
    )).then(() => {
      console.log('V2 now ready to handle fetches!');
    })
  );
});

self.addEventListener('fetch', event => {
  const url = new URL(event.request.url);

  // serve the horse SVG from the cache if the request is
  // same-origin and the path is '/dog.svg'
  if (url.origin == location.origin && url.pathname == '/dog.svg') {
    event.respondWith(caches.match('/horse.svg'));
  }
});

Посмотрите демо-версию вышеизложенного . Вы все равно должны увидеть изображение кошки. Вот почему…

Установить

Обратите внимание, что я изменил имя кэша со static-v1 на static-v2 . Это означает, что я могу настроить новый кеш, не перезаписывая содержимое текущего, которое все еще использует старый сервис-воркер.

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

Ожидающий

После успешной установки обновленный Service Worker откладывает активацию до тех пор, пока существующий Service Worker больше не перестанет контролировать клиентов. Это состояние называется «ожиданием», и именно так браузер гарантирует, что одновременно работает только одна версия вашего сервис-воркера.

Если вы запустили обновленную демо-версию , вы все равно должны увидеть изображение кота, поскольку рабочий V2 еще не активирован. Вы можете увидеть нового сервисного работника, ожидающего на вкладке «Приложение» DevTools:

DevTools показывает ожидание нового сервисного работника

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

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

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

Активировать

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

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

Если вы передадите обещание в event.waitUntil() , оно будет буферизовать функциональные события ( fetch , push , sync и т. д.), пока обещание не будет выполнено. Поэтому, когда срабатывает событие fetch , активация полностью завершена.

Пропустить фазу ожидания

Фаза ожидания означает, что вы одновременно запускаете только одну версию своего сайта, но если вам не нужна эта функция, вы можете активировать новый сервис-воркер раньше, вызвав self.skipWaiting() .

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

На самом деле не имеет значения, когда вы вызываете skipWaiting() , главное, чтобы это происходило во время ожидания или перед ним. Довольно часто его вызывают в событии install :

self.addEventListener('install', event => {
  self.skipWaiting();

  event.waitUntil(
    // caching etc
  );
});

Но вы можете вызвать его в результате вызова postMessage() сервисному работнику. Например, вы хотите skipWaiting() после взаимодействия с пользователем.

Вот демо, в котором используется skipWaiting() . Вы должны увидеть изображение коровы, не отходя от нее. Как и в случае с clients.claim() это гонка, поэтому вы увидите корову только в том случае, если новый сервисный работник загрузит, установит и активирует до того, как страница попытается загрузить изображение.

Обновления вручную

Как я упоминал ранее, браузер автоматически проверяет наличие обновлений после навигации и функциональных событий, но вы также можете запускать их вручную:

navigator.serviceWorker.register('/sw.js').then(reg => {
  // sometime later…
  reg.update();
});

Если вы ожидаете, что пользователь будет использовать ваш сайт в течение длительного времени без перезагрузки, вы можете вызывать update() через определенный интервал (например, каждый час).

Не меняйте URL-адрес сценария сервисного работника.

Если вы прочитали мой пост о лучших методах кэширования , вы можете рассмотреть возможность предоставления каждой версии вашего сервис-воркера уникальный URL-адрес. Не делай этого! Обычно это плохая практика для сервисных работников: просто обновите скрипт в его текущем местоположении.

Это может привести к такой проблеме:

  1. index.html регистрирует sw-v1.js как сервис-воркера.
  2. sw-v1.js кэширует и обслуживает index.html , поэтому он работает в первую очередь в автономном режиме.
  3. Вы обновляете index.html , чтобы он зарегистрировал ваш новый и блестящий sw-v2.js .

Если вы сделаете вышеописанное, пользователь никогда не получит sw-v2.js , поскольку sw-v1.js обслуживает старую версию index.html из своего кеша. Вы оказались в ситуации, когда вам необходимо обновить своего сервис-воркера, чтобы обновить своего сервис-воркера. Фу.

Однако для приведенной выше демонстрации я изменил URL-адрес сервис-воркера. Это так, ради демки можно переключаться между версиями. Я бы не стал этим заниматься в производстве.

Упрощение разработки

Жизненный цикл сервис-воркера построен с учетом потребностей пользователя, но во время разработки это вызывает некоторые затруднения. К счастью, есть несколько инструментов, которые могут помочь:

Обновление при перезагрузке

Это мой любимый.

DevTools показывает «обновление при перезагрузке»

Это меняет жизненный цикл и делает его более удобным для разработчиков. Каждая навигация будет:

  1. Восстановите сервисного работника.
  2. Установите его как новую версию, даже если она идентична по байтам, что означает, что запускается событие install и обновляются кеши.
  3. Пропустите фазу ожидания, чтобы активировать новый сервисный работник.
  4. Перемещайтесь по странице.

Это означает, что вы будете получать обновления при каждой навигации (включая обновление) без необходимости дважды перезагружать или закрывать вкладку.

Пропустить ожидание

DevTools показывает «пропустить ожидание»

Если у вас есть ожидающий рабочий процесс, вы можете нажать «пропустить ожидание» в DevTools, чтобы немедленно повысить его статус «активный».

Shift-перезагрузка

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

Обработка обновлений

Service Worker был разработан как часть расширяемой сети . Идея состоит в том, что мы, как разработчики браузеров, признаем, что мы не лучше веб-разработчиков, чем веб-разработчики. И поэтому мы не должны предоставлять узкие высокоуровневые API, которые решают конкретную проблему с использованием шаблонов, которые нам нравятся, а вместо этого должны предоставлять вам доступ к внутренностям браузера и позволять вам делать это так, как вы хотите, и таким способом, который работает лучше всего. для ваших пользователей.

Итак, чтобы включить как можно больше шаблонов, можно наблюдать весь цикл обновления:

navigator.serviceWorker.register('/sw.js').then(reg => {
  reg.installing; // the installing worker, or undefined
  reg.waiting; // the waiting worker, or undefined
  reg.active; // the active worker, or undefined

  reg.addEventListener('updatefound', () => {
    // A wild service worker has appeared in reg.installing!
    const newWorker = reg.installing;

    newWorker.state;
    // "installing" - the install event has fired, but not yet complete
    // "installed"  - install complete
    // "activating" - the activate event has fired, but not yet complete
    // "activated"  - fully active
    // "redundant"  - discarded. Either failed install, or it's been
    //                replaced by a newer version

    newWorker.addEventListener('statechange', () => {
      // newWorker.state has changed
    });
  });
});

navigator.serviceWorker.addEventListener('controllerchange', () => {
  // This fires when the service worker controlling this page
  // changes, eg a new worker has skipped waiting and become
  // the new active worker.
});

Жизненный цикл продолжается постоянно

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