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

Jake Archibald
Jake Archibald

Hizmet çalışanının yaşam döngüsü, en karmaşık kısmıdır. Ne yapmaya çalıştığını ve avantajlarını bilmiyorsanız bu özelliğin sizinle savaştığını hissedebilirsiniz. Ancak bu özelliğin işleyiş şeklini öğrendikten sonra, web ve yerel kalıpların en iyilerini birleştirerek kullanıcılara sorunsuz ve rahatsız edici olmayan güncellemeler sunabilirsiniz.

Bu makale ayrıntılı bir inceleme içeriyor ancak her bölümün başındaki maddelerde bilmeniz gerekenlerin çoğu yer alıyor.

Amaç

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

  • Çevrimdışı öncelikli kullanıma olanak tanıyın.
  • Mevcut hizmet çalışanını kesintiye uğratmadan yeni bir hizmet çalışanının hazırlanmasına izin verin.
  • Kapsam içi bir sayfanın her zaman aynı hizmet çalışanı (veya hiç hizmet çalışanı) tarafından kontrol edildiğinden emin olun.
  • Sitenizin aynı anda yalnızca bir sürümünün çalıştığından emin olun.

Sonuncusu oldukça önemli. Hizmet çalışanları olmadan kullanıcılar sitenize bir sekme yükleyebilir ve daha sonra başka bir sekme açabilir. Bu durum, sitenizin aynı anda iki sürümünün çalışmasına neden olabilir. Bu durum bazen sorun oluşturmaz ancak depolama alanıyla ilgili işlem yapıyorsanız paylaşılan depolama alanının nasıl yönetileceği konusunda iki sekmenin çok farklı fikirleri olabilir. Bu durum hatalara veya 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() işlevine iletilen bir söz, yüklemenizin süresini ve başarılı olup olmadığını belirtir.
  • Bir hizmet çalışanı, yükleme işlemini başarıyla tamamlayıp "etkin" hale gelene kadar fetch ve push gibi etkinlikleri almaz.
  • Sayfa isteği bir hizmet çalışanı üzerinden gitmediği sürece, sayfanın getirme işlemleri varsayılan olarak bir hizmet çalışanı üzerinden gitmez. Bu nedenle, hizmet çalışanının etkilerini görmek için sayfayı yenilemeniz gerekir.
  • clients.claim(), bu varsayılan ayarı geçersiz kılabilir ve kontrol edilmeyen sayfaların kontrolünü ele alabilir.

Şu HTML'yi ele alalım:

<!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 bir köpek resmi ekler.

Hizmet çalışanı 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'));
  }
});

Bir kedinin resmini önbelleğe alır ve /dog.svg için istek olduğunda bu resmi sunar. 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 düğmesine bastığınızda kediyi görürsünüz.

Kapsam ve kontrol

Bir hizmet çalışanı kaydının varsayılan kapsamı, komut dosyası URL'sine göre ./ olur. Yani //example.com/foo/bar.js adresinde bir hizmet çalışanı kaydettiğinizde varsayılan kapsamı //example.com/foo/ olur.

Sayfaları, çalışanları ve paylaşılan çalışanları clients olarak adlandırırız. Hizmet çalışanınız yalnızca kapsam dahilindeki istemcileri kontrol edebilir. "Kontrol edilen" bir istemcinin getirme işlemleri, kapsam içi servis çalışanı üzerinden gerçekleşir. Bir istemcinin navigator.serviceWorker.controller aracılığıyla kontrol edilip edilmediğini algılayabilirsiniz. Bu değer null veya bir hizmet çalışanı örneği olacaktır.

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

.register() çağrısı yaptığınızda ilk hizmet işleyiciniz indirilir. Komut dosyanız indiremez, ayrıştıramaz veya ilk yürütülmesinde hata verirse register promise reddedilir ve hizmet çalışanı atılır.

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

Hizmet çalışanı DevTools sekmesinde hata gösteriliyor

Yükle

Bir hizmet çalışanının aldığı ilk etkinlik install olur. İşçi yürütülür yürütülmez tetiklenir ve her hizmet işçisi için yalnızca bir kez çağrılır. Hizmet çalışanı komut dosyanızı değiştirirseniz tarayıcı bunu farklı bir hizmet çalışanı olarak değerlendirir ve kendi install etkinliğini alır. Güncellemeleri daha ayrıntılı olarak ele alacağım.

install etkinliği, istemcileri kontrol etmeden önce ihtiyacınız olan her şeyi önbelleğe alma şansınızdır. event.waitUntil() işlevine ilettiğiniz söz, tarayıcıya yüklemenizin ne zaman tamamlandığını ve başarılı olup olmadığını bildirir.

Sözünüz reddedilirse bu, yüklemenin başarısız olduğunu gösterir ve tarayıcı servis çalışanını atar. Bu API, müşterileri hiçbir zaman kontrol etmez. Bu, cat.svg değerinin fetch etkinliklerimizdeki önbellekte bulunduğundan emin olabileceğimiz anlamına gelir. Bu bir bağımlılıktır.

Etkinleştir

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

Demoyu ilk kez yüklediğinizde, hizmet çalışanı etkinleştirildikten çok sonra dog.svg istenmesine rağmen istek işlenmez 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üklenirse alt kaynakları da yüklenmez. Demoyu ikinci kez yüklerseniz (yani sayfayı yenilerseniz) kontrol edilir. Hem sayfa hem de resim fetch etkinliğinden geçer ve bunun yerine bir kedi görürsünüz.

clients.claim

Etkinleştirildikten sonra hizmet işleyicinizde clients.claim() çağrısı yaparak kontrol edilmeyen istemcilerin kontrolünü ele alabilirsiniz.

Aşağıda, activate etkinliğinde clients.claim() çağrısı yapan yukarıdaki demonun bir varyasyonu verilmiştir. İlk kez bir kedi göreceksiniz. Zamanlama açısından hassas olduğu için "gereklidir" diyorum. Yalnızca hizmet çalışanı etkinleşirse ve clients.claim() resim yüklenmeye çalışmadan önce geçerli olursa kedi görürsünüz.

Hizmet çalışanınızı, sayfaları ağ üzerinden yüklendikleri şekilden farklı şekilde yüklemek için kullanıyorsanız hizmet çalışanınız, kendisi olmadan yüklenen bazı istemcileri kontrol ettiğinden clients.claim() sorunlu olabilir.

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

Özet olarak:

  • Aşağıdakilerden herhangi biri gerçekleştiğinde güncelleme tetiklenir:
    • Kapsam içi bir sayfaya yönlendirme.
    • Önceki 24 saat içinde bir güncelleme kontrolü yapılmadıysa push ve sync gibi işlevsel etkinlikler.
    • Hizmet işçisi URL'si yalnızca değiştiğinde .register() çağrılır. Ancak çalışan URL'sini değiştirmekten kaçınmanız gerekir.
  • Chrome 68 ve sonraki sürümler 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 yoksayar. importScripts() aracılığıyla bir hizmet çalışanına yüklenen kaynakları getirirken önbelleğe alma başlıklarına yine de saygı duyarlar. Hizmet çalışanınızı kaydederken updateViaCache seçeneğini belirleyerek bu varsayılan davranışı geçersiz kılabilirsiniz.
  • Hizmet çalışanınız, tarayıcıda bulunandan bayt olarak farklıysa güncellenmiş olarak kabul edilir. (Bu özelliği, 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 işleyicinizin durum kodu "ok" değilse (örneğin, 404), ayrıştırma işlemi başarısız olursa, yürütme sırasında hata verirse veya yükleme sırasında reddedilirse yeni işleyici atılır ancak mevcut işleyici etkin kalır.
  • Güncellenen işleyici, başarıyla yüklendikten sonra mevcut işleyici sıfır istemciyi kontrol edene kadar wait olur. (Yenileme sırasında istemcilerin çakıştığını unutmayın.)
  • self.skipWaiting(), beklemeyi önler. Yani hizmet çalışanı, yükleme işlemi tamamlanır tamamlanmaz etkinleştirilir.

Hizmet çalışanı 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ıdakilerin bir demosunu inceleyin. Yine de bir kedi resmi görürsünüz. Bunun nedeni şudur:

Yükle

Önbellek adını static-v1 yerine static-v2 olarak değiştirdiğimi unutmayın. Bu sayede, eski hizmet çalışanının hâlâ kullandığı mevcut önbelleğin verilerini silmeden yeni önbelleği ayarlayabilirim.

Bu kalıplar, doğal bir uygulamanın yürütülebilir dosyasıyla birlikte paketleyeceği öğelere benzer şekilde sürüme özel önbellekler oluşturur. avatars gibi sürüme özgü olmayan önbellekleri 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şmeyi geciktirir. Bu durum "bekleme" olarak adlandırılır ve tarayıcının, hizmet çalışanınızın aynı anda yalnızca bir sürümünün çalıştığından emin olmasını sağlar.

Güncellenen denemeyi çalıştırırsanız V2 çalışanı henüz etkinleştirilmediği için bir kedi resmi görmeye devam edersiniz. DevTools'un "Uygulama" sekmesinde bekleyen yeni hizmet çalışanını görebilirsiniz:

Yeni hizmet çalışanının beklediğini gösteren DevTools

Demo için yalnızca bir sekmeniz açık olsa bile sayfayı yenilemek, yeni sürümün devreye girmesini sağlamak için yeterli değildir. Bunun nedeni, tarayıcı gezinmelerinin işleyiş şeklidir. Gezerken, yanıt başlıkları alınana kadar geçerli sayfa kaybolmaz. Yanıtta Content-Disposition başlığı varsa geçerli sayfa kaybolmaz. Bu çakışma nedeniyle, mevcut hizmet çalışanı yenileme sırasında her zaman bir istemciyi kontrol eder.

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

Bu model, Chrome'un güncellenme şekline benzer. Chrome'daki güncellemeler arka planda indirilir ancak Chrome yeniden başlatılana kadar uygulanmaz. Bu süre zarfında mevcut sürümü kesintisiz bir şekilde kullanmaya devam edebilirsiniz. Ancak bu, geliştirme sırasında can sıkıcı bir durumdur. DevTools'un bunu kolaylaştırmanın yolları vardır. Bu konuyu bu makalenin ilerleyen bölümlerinde ele alacağız.

Etkinleştir

Bu olay, eski servis çalışanı kaldırıldıktan ve yeni servis çalışanınız müşterileri kontrol edebildikten sonra tetiklenir. Bu, eski işleyici hâlâ kullanılıyorken yapamadığınız işlemleri (ör. veritabanlarını taşıma ve önbellekleri temizleme) yapmanın ideal zamanıdır.

Yukarıdaki demoda, orada olmasını beklediğim önbellekleri listeleyip activate etkinliğinde diğer tüm önbellekleri kaldırıyorum. Böylece eski static-v1 önbelleği kaldırılır.

event.waitUntil()'e bir söz verirseniz söz çözülene kadar işlevsel etkinlikler (fetch, push, sync vb.) arabelleğe alınır. Bu nedenle, fetch etkinliğiniz tetiklendiğinde etkinleştirme işlemi tamamen tamamlanır.

Bekleme aşamasını atlama

Bekleme aşaması, sitenizin aynı anda yalnızca bir sürümünü çalıştırdığınız anlamına gelir. Ancak bu özelliğe ihtiyacınız yoksa self.skipWaiting() çağrısı yaparak yeni hizmet çalışanınızı daha erken etkinleştirebilirsiniz.

Bu, hizmet çalışanınızın bekleme aşamasına girer girmez mevcut etkin çalışanı atmasına ve kendisini etkinleştirmesine (veya zaten bekleme aşamasındaysa hemen etkinleşmesine) neden olur. Bu durum, çalışanınızın yüklemeyi atlamasına neden olmaz, yalnızca beklemesine neden olur.

skipWaiting()'yi ne zaman aradığınız önemli değildir. Önemli olan, bekleme sırasında veya öncesinde aramayı yapmanızdır. Bu işlevi install etkinliğinde çağırmanız oldukça yaygındır:

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

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

Ancak bunu, servis çalışanına yapılan bir postMessage() sonucunda çağırabilirsiniz. Yani, bir kullanıcı etkileşimini takip eden skipWaiting() işlemini yapmak istiyorsunuz.

skipWaiting() kullanan bir demo aşağıda verilmiştir. Sayfadan ayrılmanıza gerek kalmadan bir ineğin resmini görürsünüz. clients.claim()'te olduğu gibi bu da bir yarıştır. Bu nedenle, ineği yalnızca yeni hizmet çalışanı sayfa resmini yüklemeye çalışmadan önce getirip yüklerse ve etkinleştirirse görürsünüz.

El ile güncellemeler

Daha önce de belirttiğim gibi, tarayıcı gezinme 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 update() işlevini belirli aralıklarla (ör. saatlik) çağırabilirsiniz.

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

Önbelleğe almayla ilgili en iyi uygulamalarla ilgili gönderimimi okuduysanız hizmet işleyicinizin her bir sürümüne benzersiz bir URL verebilirsiniz. Bunu yapmayın! Bu, genellikle hizmet çalışanları için kötü bir uygulamadır. Komut dosyasını mevcut konumunda güncellemeniz yeterlidir.

Bu, aşağıdaki gibi bir sorunla karşılaşmanıza neden olabilir:

  1. index.html, sw-v1.js'ı hizmet çalışanı olarak kaydeder.
  2. sw-v1.js, index.html'i önbelleğe alır ve sunar. Bu nedenle, önce çevrimdışı olarak çalışır.
  3. index.html'ü güncelleyerek yeni ve parlak sw-v2.js cihazınızı kaydedersiniz.

Yukarıdakileri yaparsanız sw-v1.js, index.html'nin eski sürümünü önbelleğinden yayınladığı için kullanıcı hiçbir zaman sw-v2.js'i almaz. Kendinizi, hizmet işleyicinizi güncellemek için hizmet işleyicinizi güncellemeniz gereken bir duruma soktunuz. İğrenç.

Ancak yukarıdaki demo için servis çalışanının URL'sini değiştirdim. Bu sayede, demo için sürümler arasında geçiş yapabilirsiniz. Üretimde bunu yapmam.

Geliştirmeyi kolaylaştırma

Hizmet çalışanı yaşam döngüsü, kullanıcı göz önünde bulundurularak tasarlanmıştır ancak geliştirme sırasında biraz can sıkıcı olabilir. Neyse ki size yardımcı olacak birkaç araç var:

Yeniden yüklenirken güncelle

Bu benim favorim.

&quot;Yeniden yüklenirken 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:

  1. Servis çalışanını yeniden getirin.
  2. Bayt olarak aynı olsa bile yeni bir sürüm olarak yükleyin. Bu durumda install etkinliğiniz çalışır ve önbelleğiniz güncellenir.
  3. Yeni hizmet çalışanının etkinleşmesi için bekleme aşamasını atlayın.
  4. Sayfada gezinin.

Bu sayede, sekmeyi kapatmak veya iki kez yeniden yüklemek zorunda kalmadan her gezinme işleminde (yeniden yükleme dahil) güncellemelerinizi alırsınız.

Bekleme süresini atlama

DevTools&#39;ta &quot;beklemeyi atla&quot; mesajı gösteriliyor

Beklemede olan bir işçiniz varsa DevTools'ta "beklemeyi atla"yı tıklayarak hemen "etkin" durumuna geçirebilirsiniz.

Üst karakter + yeniden yükle

Sayfayı zorla yeniden yüklerseniz (Üst Karakter tuşuna basarak yeniden yükleme) hizmet çalışanı tamamen atlanır. Kontrolsüz olacaktır. Bu özellik spesifikasyonda yer aldığından, hizmet çalışanını destekleyen diğer tarayıcılarda da çalışır.

Güncellemeleri işleme

Hizmet çalışanı, genişletilebilir web'in bir parçası olarak tasarlanmıştır. Buradaki amaç, tarayıcı geliştiricileri olarak web geliştirme konusunda web geliştiricilerinden daha iyi olmadığımızı kabul etmektir. Bu nedenle, belirli bir sorunu bizim beğendiğimiz kalıpları kullanarak çözen dar kapsamlı üst düzey API'ler sağlamamalıyız. Bunun yerine, size tarayıcının kalbine erişim izni verip bunu sizin kullanıcılarınız için en iyi şekilde, istediğiniz şekilde yapmanıza izin vermeliyiz.

Bu nedenle, mümkün olduğunca fazla kalıbı etkinleştirmek için güncelleme döngüsünün tamamı 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ü devam eder

Gördüğünüz gibi, hizmet çalışanı yaşam döngüsünü anlamak faydalıdır. Bu anlayışla, hizmet çalışanı davranışları daha mantıklı ve daha az gizemli görünecektir. Bu bilgiler, servis çalışanlarını dağıtırken ve güncellerken size daha fazla güven verecektir.