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 ne gibi avantajları olduğunu 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.
  • 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 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. Service Worker olmadan kullanıcılar sitenize bir sekme yükleyip 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 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 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 promise, 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 gönderilmediği sürece, sayfanın getirme işlemleri varsayılan olarak bir hizmet çalışanı üzerinden gönderilmez. 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.

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 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 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 istemci "kontrol altına alındıktan" sonra, getirme işlemleri kapsam içi servis çalışanı üzerinden gerçekleşir. 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ğrısını 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. Çalışan çalışırken tetiklenir ve hizmet çalışanı başına 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 edebilmek için ihtiyacınız olan her şeyi önbelleğe alma fırsatı sunar. 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. 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 hâlâ köpeğin resmini görürsünüz. 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 etkinliklerinden geçecek ve bunun yerine bir kedi göreceksiniz.

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. Bir kedi görürsünüz. Ancak hizmet çalışanı etkinleşirse ve clients.claim(), görüntü yüklenmeden önce etkinleşir.

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 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. (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. (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.

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ı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 üzerine yazmadan 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ş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. 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. 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, mevcut 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, demoya tekrar gittiğinizde atı görürsünüz.

Bu kalıp, Chrome'un güncellenmesine 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, 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 ö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() ü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. Bu nedenle, fetch etkinliğiniz tetiklendiğinde etkinleştirme işlemi tamamen tamamlanır.

Bekleme aşamasını atlama

Bekleme aşaması, aynı anda sitenizin 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() 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. 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 Service Worker'lar 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 adlı kuruluşu hizmet çalışanı olarak kaydediyor.
  2. sw-v1.js, index.html'u ö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ükleme

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, service-worker'ı destekleyen diğer tarayıcılarda da çalışır.

Güncellemeleri işleme

Service Worker, 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 kendi 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ü hiç bitmez

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.