Service Worker'ın yaşam döngüsü

Jake Archibald
Jake Archibald

Service Worker'ın yaşam döngüsü en karmaşık kısmıdır. Ne yapmaya çalıştığını ve faydalarının ne olduğunu bilmiyorsanız sizinle savaşıyormuş gibi hissedebilirsiniz. Ancak nasıl çalıştığını öğrendikten sonra, web'in ve yerel kalıpların en iyi özelliklerini bir araya getirerek kullanıcılara sorunsuz ve gözü rahatsız etmeyen güncellemeler sunabilirsiniz.

Bu, ayrıntılı bir açıklama olsa da her bölümün başındaki maddeler bilmeniz gerekenlerin çoğunu kapsamaktadır.

Amaç

Yaşam döngüsünün amacı şudur:

  • Çevrimdışına öncelik verin.
  • Yeni hizmet çalışanının, mevcut çalışmasını kesintiye uğratmadan kendisini hazırlamasına izin verin.
  • Kapsam içi bir sayfanın, baştan sona aynı hizmet çalışanı (veya hizmet çalışanı yok) tarafından kontrol edildiğinden emin olun.
  • Sitenizin aynı anda yalnızca bir sürümünün yayınlandığından emin olun.

Sonuncusu oldukça önemli. Service Worker olmadan kullanıcılar sitenize bir sekme yükleyip daha sonra başka bir sekme açabilir. Bu durum, sitenizin iki sürümünün aynı anda çalışmasına neden olabilir. Bu bazen sorun yaratmaz, ancak depolama alanıyla ilgili sorun yaşıyorsanız, paylaşılan depolama alanının nasıl yönetileceği konusunda çok farklı görüşlere sahip iki sekmeyle karşılaşabilirsiniz. Bu da hatalara, hatta daha da kötüsü veri kaybına neden olabilir.

İlk hizmet çalışanı

Özet olarak:

  • install etkinliği, bir hizmet çalışanının aldığı ilk etkinliktir ve yalnızca bir kez gerçekleşir.
  • installEvent.waitUntil() hizmetine iletilen bir söz, yüklemenin süresini ve başarılı veya başarısız olduğunu belirtir.
  • Bir hizmet çalışanı, yüklemeyi başarıyla tamamlayıp "etkin" hale gelene kadar fetch ve push gibi etkinlikleri almaz.
  • Varsayılan olarak, sayfa isteğinin kendisi bir Service Worker'dan geçmediği sürece sayfa getirme işlemleri bir hizmet çalışanından geçmez. Bu nedenle, Service Worker'ın etkilerini görmek için sayfayı yenilemeniz gerekir.
  • clients.claim(), bu varsayılan ayarı geçersiz kılabilir ve denetlenmeyen sayfaların kontrolünü ele alabilir.

Bu HTML'yi alın:

<!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>

Bir hizmet çalışanı kaydeder ve 3 saniye sonra köpek resmini ekler.

sw.js hizmet çalışanı:

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

Bir kedinin resmini önbelleğe alır ve /dog.svg için istek olduğunda bu resmi yayınlar. Ancak, yukarıdaki örneği çalıştırırsanız sayfayı ilk kez yüklediğinizde bir köpek görürsünüz. Yenile'ye bastığınızda kediyi göreceksiniz.

Kapsam ve kontrol

Hizmet çalışanı kaydının varsayılan kapsamı, komut dosyası URL'sine göre ./ şeklindedir. Yani //example.com/foo/bar.js alanında bir hizmet çalışanı kaydederseniz bu hizmet çalışanının varsayılan kapsamı //example.com/foo/ olur.

Sayfaları, çalışanları ve paylaşılan çalışanları clients olarak adlandırıyoruz. Hizmet çalışanınız yalnızca kapsam dahilindeki istemcileri kontrol edebilir. Bir müşteri "kontrol" haline geldiğinde, getirmeleri kapsam içi hizmet çalışanı tarafından yapılır. Bir istemcinin navigator.serviceWorker.controller üzerinden kontrol edilip edilmediğini (null değer veya hizmet çalışanı örneği) tespit edebilirsiniz.

İndirme, ayrıştırma ve yürütme

.register() çağırdığınızda ilk Service Worker'ınız indirilir. Komut dosyanız indirilemez, ayrıştırılamaz veya ilk yürütme sırasında hata verirse kayıt taahhüdü reddedilir ve hizmet çalışanı silinir.

Chrome'un Geliştirici Araçları, hatayı konsolda ve uygulama sekmesinin hizmet çalışanı bölümünde gösterir:

Service Worker Geliştirici Araçları sekmesinde hata mesajı gösteriliyor

Yükle

Bir hizmet çalışanının aldığı ilk etkinlik install. Çalışan çalışırken tetiklenir ve hizmet çalışanı başına yalnızca bir kez çağrılır. Service Worker komut dosyanızı değiştirirseniz tarayıcı bunu farklı bir hizmet çalışanı olarak algılar ve kendi install etkinliğine sahip olur. Güncellemeleri daha sonra ayrıntılı olarak ele alacağım.

install etkinliği, istemcileri kontrol edebilmek için ihtiyacınız olan her şeyi önbelleğe alma fırsatı sunar. event.waitUntil() için verdiğiniz taahhüt, yüklemenin tamamlanıp tamamlanmadığını tarayıcının bilmesini sağlar.

Sözünüz reddedilirse bu, yüklemenin başarısız olduğunu gösterir ve tarayıcı, hizmet çalışanını atar. Müşterileri hiçbir zaman kontrol etmez. Bu, fetch etkinliklerimizde cat.svg ürününün önbellekte bulunduğuna güvenebileceğimiz anlamına gelir. Bu bir bağımlılık.

Etkinleştir

Hizmet çalışanınız istemcileri kontrol etmeye ve push, sync gibi işlevsel etkinlikleri yönetmeye hazır olduğunda bir activate etkinliği alırsınız. Ancak bu, .register() adlı sayfanın kontrol edileceği anlamına gelmez.

Demoyu ilk kez yüklediğinizde, Service Worker etkinleştirildikten uzun süre sonra dog.svg istense de isteği yerine getirmez ve köpeğin resmini görmeye devam edersiniz. Varsayılan değer tutarlılıktır. Sayfanız bir hizmet çalışanı olmadan yükleniyorsa alt kaynakları da değişmez. Demoyu ikinci kez yüklerseniz (yani sayfayı yenilerseniz) kontrol edilir. Hem sayfa hem de resim fetch etkinliklerinden geçer ve bunun yerine bir kedi görürsünüz.

clients.claim

Etkinleştirildikten sonra hizmet çalışanınız içinde clients.claim() öğesini çağırarak kontrol edilemeyen istemcilerin kontrolünü elinize alabilirsiniz.

Burada, activate etkinliğinde clients.claim() değerini çağıran yukarıdaki demonun bir varyasyonunu görebilirsiniz. İlk seferinde bir kedi görmelisiniz. "Olmalı" diyorum çünkü bu konu zamanlamaya duyarlı. Bir kedi görürsünüz. Ancak hizmet çalışanı etkinleşirse ve clients.claim(), görüntü yüklenmeden önce etkinleşir.

Service Worker'ınızı sayfaları ağ üzerinden yükleyeceklerinden farklı bir şekilde yüklemek için kullanırsanız hizmet çalışanınız olmadan yüklenen bazı istemcileri kontrol edeceği için clients.claim() sorunlu olabilir.

Hizmet çalışanını güncelleme

Özet olarak:

  • Aşağıdakilerden herhangi biri gerçekleştiğinde bir güncelleme tetiklenir:
    • Kapsam içi sayfaya gitme.
    • Son 24 saat içinde güncelleme kontrolü yapılmadıysa push ve sync gibi işlevsel etkinlikler.
    • .register() yalnızca hizmet çalışanı URL'si değiştiyse çağrılır. Ancak, çalışan URL'sini değiştirmekten kaçınmanız gerekir.
  • Chrome 68 ve sonraki sürümleri de dahil olmak üzere çoğu tarayıcı, kayıtlı hizmet çalışanı komut dosyasının güncellemelerini kontrol ederken varsayılan olarak önbelleğe alma üstbilgilerini yoksaymaktadır. Bunlar, importScripts() aracılığıyla bir hizmet çalışanının içinde yüklenen kaynakları getirirken önbelleğe alma üstbilgilerini dikkate almaya devam eder. Hizmet çalışanınızı kaydederken updateViaCache seçeneğini ayarlayarak bu varsayılan davranışı geçersiz kılabilirsiniz.
  • Hizmet çalışanınız, tarayıcının halihazırda sahip olduğu bayttan farklı bir baytsa güncellenmiş olarak kabul edilir. (Bunun kapsamını, içe aktarılan komut dosyalarını/modülleri de içerecek şekilde genişletiyoruz.)
  • Güncellenen hizmet çalışanı, mevcut çalışanla birlikte başlatılır ve kendi install etkinliğini alır.
  • Yeni çalışanınızın "tamamlandı" durum kodu varsa (örneğin, 404), ayrıştırma yapamaması, yürütme sırasında bir hata mesajı vermesi veya yükleme sırasında reddetmesi durumunda, yeni çalışan çıkarılır, ancak mevcut çalışan etkin kalır.
  • Güncellenen çalışan, başarıyla yüklendikten sonra mevcut çalışan sıfır istemciyi kontrol edene kadar wait. (Yenileme sırasında istemcilerin çakıştığını unutmayın.)
  • self.skipWaiting() bekleme süresini önler, yani hizmet çalışanı yükleme işlemi tamamlanır tamamlanmaz etkinleşir.

Service Worker komut dosyamızı kedi yerine at resmiyle yanıt verecek şekilde değiştirdiğimizi varsayalım:

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

Yukarıdaki demoya göz atın. Yine de kedi resmi göreceksiniz. Neden mi?

Yükle

static-v1 olan önbellek adını static-v2 olarak değiştirdiğimi hatırlatmak isterim. Bu sayede, eski hizmet çalışanının kullanmakta olduğu mevcut önbelleğin üzerine yazmadan yeni önbelleği kurabilirsiniz.

Bu kalıplar, yerel bir uygulamanın yürütülebilir dosya ile paketleyeceği öğelere benzer, sürüme özgü önbellekler oluşturur. avatars gibi sürüme özgü olmayan önbellekleriniz de olabilir.

Bekliyor

Güncellenen hizmet çalışanı, başarıyla yüklendikten sonra, mevcut hizmet çalışanı istemcileri kontrol etmeyi bırakana kadar etkinleştirme işlemini geciktirir. Bu durum "beklemede" olarak adlandırılır ve tarayıcı, aynı anda Service Worker'ınızın yalnızca bir sürümünün çalışmasını bu şekilde sağlar.

Güncellenen demoyu çalıştırdıysanız V2 çalışanı henüz etkinleştirilmediğinden yine bir kedi resmi görürsünüz. Yeni hizmet çalışanının "Uygulama"da sizi beklediğini görebilirsiniz Geliştirici Araçları sekmesine gidin:

Yeni hizmet çalışanının beklediğini gösteren Geliştirici Araçları

Demoya açık tek bir sekmeniz olsa bile sayfayı yenilemek, yeni sürümün devralmasını sağlamak için yeterli olmaz. Bu durum, tarayıcı gezinmelerinin çalışma şeklinden kaynaklanır. Gezinmeyi yaptığınızda, yanıt başlıkları alınana kadar geçerli sayfa kaldırılmaz. Hatta yanıtta Content-Disposition üstbilgisi bulunuyorsa geçerli sayfa kalabilir. Bu çakışma nedeniyle, geçerli hizmet çalışanı yenileme sırasında her zaman bir istemciyi kontrol eder.

Güncellemeyi almak için geçerli hizmet çalışanını kullanarak tüm sekmeleri kapatın veya sekmelerden ayrılın. Ardından, tekrar demoya gittiğinizde atı görürsünüz.

Bu kalıp, Chrome'un güncellenmesine benzer. Chrome'da yapılan güncellemeler arka planda indirilir, ancak Chrome yeniden başlatılana kadar uygulanmaz. Bu süre zarfında, mevcut sürümü kesinti yaşamadan kullanmaya devam edebilirsiniz. Ancak bu, geliştirme aşamasında yaşanan bir zahmettir. Ancak Geliştirici Araçları'nın bunu kolaylaştıracak yöntemler vardır. Bunu bu makalenin ilerleyen bölümlerinde ele alacağım.

Etkinleştir

Bu komut, eski hizmet çalışanı kullanımdan kaldırıldığında ve yeni hizmet çalışanınız istemcileri denetleyebildiğinde tetiklenir. Bu, veritabanlarını taşıma ve önbellekleri temizleme gibi eski çalışan hâlâ kullanılırken yapamayacağınız işlemleri yapmak için en uygun zamandır.

Yukarıdaki demoda, orada olmasını beklediğim önbelleklerin bir listesini tutuyorum ve activate etkinliğinde diğer önbellekleri siliyorum, bu da eski static-v1 önbelleğini kaldırır.

event.waitUntil() ürününe bir söz verirseniz söz konusu taahhüt yerine getirilene kadar işlevsel etkinlikler (fetch, push, sync vb.) arabelleğe alınır. Böylece fetch etkinliğiniz tetiklendiğinde etkinleştirme işlemi tamamen tamamlanmış olur.

Bekleme aşamasını atla

Bekleme aşamasında, aynı anda sitenizin yalnızca bir sürümünü çalıştırırsınız. Ancak bu özelliğe ihtiyacınız yoksa self.skipWaiting() numaralı telefonu arayarak yeni hizmet çalışanınızın daha erken etkinleştirilmesini sağlayabilirsiniz.

Bu durum, hizmet çalışanınızın mevcut etkin çalışanı işten çıkarmasına ve bekleme aşamasına geçer geçmez (veya zaten bekleme aşamasındaysa hemen) kendini etkinleştirmesine neden olur. Çalışanınızın yükleme işlemini atlamasına neden neden olmaz, yalnızca bekler.

Bekleme sırasında veya öncesinde olduğu sürece skipWaiting() adlı kişiyi ne zaman aramanız fark etmez. install etkinliğinde çağrılması oldukça yaygındır:

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

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

Ancak bunu hizmet çalışanına yapılan bir postMessage() işleminin sonucu olarak çağırmak isteyebilirsiniz. Tıpkı bir kullanıcı etkileşiminden sonra skipWaiting().

skipWaiting() özelliğinin kullanıldığı bir demoyu burada bulabilirsiniz. Buradan ayrılmak zorunda kalmadan ineğin resmini görmelisiniz. clients.claim() gibi bu da bir yarıştır. Bu nedenle, ineği yalnızca yeni hizmet çalışanı, sayfa resmi yüklemeye çalışmadan önce getirir, yükler ve etkinleştirirse görürsünüz.

El ile güncellemeler

Daha önce de belirttiğim gibi, tarayıcı gezinmelerden ve işlevsel etkinliklerden sonra güncellemeleri otomatik olarak kontrol eder, ancak bunları manuel olarak da tetikleyebilirsiniz:

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

Kullanıcının sitenizi yeniden yüklemeden uzun süre kullanmasını bekliyorsanız belirli bir aralıklarla (örneğin, saatlik) update() çağrısı yapmak isteyebilirsiniz.

Hizmet çalışanı komut dosyanızın URL'sini değiştirmekten kaçının

Önbelleğe alma en iyi uygulamaları hakkındaki yayınımı okuduysanız Service Worker'ınızın her sürümüne benzersiz bir URL vermeyi düşünebilirsiniz. Bunu yapmayın! Bu genellikle hizmet çalışanları için kötü bir uygulamadır. Komut dosyasını mevcut konumunda güncellemeniz yeterlidir.

Aşağıdaki gibi bir sorunla karşılaşmanıza yol açabilir:

  1. index.html, sw-v1.js adlı kuruluşu hizmet çalışanı olarak kaydediyor.
  2. sw-v1.js, index.html uygulamasını önbelleğe alıp sunar ve böylece çevrimdışı öncelikli olarak çalışır.
  3. index.html uygulamasını güncelleyerek yeni ve göz alıcı sw-v2.js öğenizi kaydedersiniz.

Yukarıdakini yaparsanız kullanıcı hiçbir zaman sw-v2.js öğesini almaz, çünkü sw-v1.js, index.html ürününün eski sürümünü önbelleğinden sunar. Hizmet çalışanınızı güncellemek için hizmet çalışanınızı güncellemeniz gereken bir konuma geldiniz. Tüh.

Ancak yukarıdaki demo için Service Worker'ın URL'sini değiştirdim. Dolayısıyla, demo sırasında sürümler arasında geçiş yapabilirsiniz. Üretimde böyle bir şey yok.

Geliştirme sürecini kolaylaştırma

Service Worker'ın yaşam döngüsü, kullanıcı düşünülerek tasarlansa da geliştirme aşamasında bu süreç biraz can sıkıcıdır. Neyse ki size yardımcı olacak birkaç araç sunuyoruz:

Yeniden yüklendiğinde güncelle

Bu benim favorim.

&quot;Yeniden yüklemede güncelle&quot;yi gösteren Geliştirici Araçları

Bu işlem, yaşam döngüsünü geliştirici dostu olacak şekilde değiştirir. Her gezinme işleminde:

  1. Hizmet çalışanını yeniden getirin.
  2. Bayt farklı olsa bile bu uzantıyı yeni bir sürüm olarak yükleyin. Başka bir deyişle, install etkinliğiniz çalışır ve önbellekleriniz güncellenir.
  3. Yeni Service Worker'ın etkinleştirilmesi için bekleme aşamasını atlayın.
  4. Sayfada gezinme.

Bu sayede, güncellemelerinizi iki kez yeniden yüklemek veya sekmeyi kapatmak zorunda kalmadan her gezinmede (yenileme dahil) alabilirsiniz.

Beklemeyi atla

Geliştirici Araçları &quot;beklemeyi atla&quot; gösteriyor

Bekleyen bir çalışan varsa "beklemeyi atla" seçeneğine basabilirsiniz hemen "etkin" hale getirmek için Geliştirici Araçları'nda gösterilir.

Üst karakter-yeniden yükle

Sayfayı zorla yeniden yüklerseniz (kaydırarak yeniden yüklerseniz) hizmet çalışanını tamamen atlarsınız. Kontrolsüz olacak. Bu özellik spesifikasyonda bulunduğundan, hizmet çalışanını destekleyen diğer tarayıcılarda çalışır.

Güncellemeleri işleme

Service Worker, genişletilebilir web'in bir parçası olarak tasarlanmıştır. Tarayıcı geliştiricileri olarak, web geliştirmede web geliştiricilerinden daha iyi olmadığımızı kabul ederiz. Bu nedenle, belirli bir sorunu beğendiğimiz kalıpları kullanarak çözen dar ve üst düzey API'ler sunmamalı, bunun yerine tarayıcının içgüdülerine erişmenizi ve kullanıcılarınız için en iyi sonucu verecek şekilde bunu istediğiniz şekilde yapmanıza izin vermeliyiz.

Dolayısıyla, mümkün olduğunca fazla sayıda kalıbı etkinleştirmek için tüm güncelleme döngüsü gözlemlenebilir:

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.
});

Yaşam döngüsü hiç bitmez

Gördüğünüz gibi, Service Worker'ın yaşam döngüsünü anlamak faydalıdır. Bu anlayış sayesinde Service Worker'ların davranışları daha mantıklı ve daha az gizemli görünmelidir. Bu bilgi, Service Worker'ları dağıtıp güncellerken size daha fazla güven verir.