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 faydalarının ne olduğunu bilmiyorsanız size karşı savaşıyormuş gibi gelebilir. Ancak nasıl çalıştığını öğrendikten sonra, web ve yerel kalıpların en iyi özelliklerini birleştirerek kullanıcılara sorunsuz ve göze batmayan güncellemeler sunabilirsiniz.

Bu ayrıntılı bir incelemedir ancak her bölümün başındaki maddeler bilmeniz gerekenlerin çoğunu kapsar.

Amaç

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

  • Önce çevrimdışı yaklaşımını mümkün kılma
  • Yeni bir hizmet çalışanının, mevcut çalışanı kesintiye uğratmadan kendini hazırlamasına izin verin.
  • Kapsam dahilindeki bir sayfanın baştan sona aynı hizmet çalışanı tarafından (veya hiçbir 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 önemlidir. Hizmet çalışanları olmadan kullanıcılar sitenize bir sekme yükleyebilir ve daha sonra başka bir sekme açabilir. Bu durum, sitenizin iki sürümünün aynı anda çalışmasına neden olabilir. Bazen bu durum sorun yaratmaz ancak depolama alanıyla ilgili bir işlem yapıyorsanız iki sekmenin, paylaşılan depolama alanlarının nasıl yönetilmesi gerektiği konusunda çok farklı görüşlere sahip olması kolayca mümkün olabilir. Bu durum hatalara veya daha kötüsü veri kaybına neden olabilir.

İlk hizmet çalışanı

Özet olarak:

  • install etkinliği, 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ı mı yoksa başarısız mı olduğunu gösterir.
  • Hizmet çalışanı, yükleme işlemini başarıyla tamamlayıp "etkin" hale gelene kadar fetch ve push gibi etkinlikleri almaz.
  • Varsayılan olarak, sayfa isteğinin kendisi bir hizmet çalışanından geçmediği sürece sayfanın getirme işlemleri bir hizmet çalışanından geçmez. 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'));
  }
});

Kedi 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 yüklediğinizde bir köpek görürsünüz. Sayfayı yenilediğinizde 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 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ılır. Hizmet çalışanınız yalnızca kapsam dahilindeki istemcileri kontrol edebilir. Bir istemci "kontrol edildikten" sonra, getirme işlemleri kapsam dahilindeki hizmet çalışanından geçer. Bir istemcinin navigator.serviceWorker.controller üzerinden kontrol edilip edilmediğini tespit edebilirsiniz. Bu değer null veya bir hizmet çalışanı örneği olacaktır.

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

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

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

Hizmet çalışanı Geliştirici Araçları sekmesinde gösterilen hata

Yükle

Hizmet çalışanının aldığı ilk etkinlik install etkinliğidir. Çalışan yürütülür yürütülmez 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 kabul eder ve kendi install etkinliğini alır. Güncellemeleri daha sonra ayrıntılı olarak ele alacağım.

install etkinliği, istemcileri kontrol edebilmeniz için gereken her şeyi önbelleğe alma fırsatınızdır. event.waitUntil() yöntemine ilettiğiniz Promise, 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ı, hizmet çalışanını siler. Bu hesap hiçbir zaman müşteri hesaplarını kontrol etmez. Bu, fetch etkinliklerimizde cat.svg öğesinin önbellekte bulunmasına güvenebileceğ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() işlevini çağıran sayfanın kontrol edileceği anlamına gelmez.

Demoyu ilk yüklediğinizde, dog.svg hizmet çalışanının etkinleştirilmesinden çok sonra istense bile isteği işlemez ve köpeğin resmini görmeye devam edersiniz. Varsayılan değer tutarlılık'tır. Sayfanız hizmet çalışanı olmadan yüklenirse alt kaynakları da yüklenmez. Demoyu ikinci kez yüklerseniz (başka bir deyişle sayfayı yenilerseniz) kontrol edilir. Hem sayfa hem de resim fetch etkinlikten geçer ve bunun yerine bir kedi görürsünüz.

clients.claim

Hizmet çalışanınız etkinleştirildikten sonra hizmet çalışanınızda clients.claim() yöntemini çağırarak kontrol edilmeyen istemcilerin kontrolünü ele alabilirsiniz.

activate etkinliğinde clients.claim() yöntemini çağıran yukarıdaki demoya ait bir varyasyonu burada bulabilirsiniz. İlk seferde bir kedi görmeniz gerekir. "Gerekir" diyorum çünkü bu zamanlamaya bağlıdır. Yalnızca hizmet çalışanı etkinleşirse ve resim yüklenmeye çalışmadan önce clients.claim() geçerli olursa bir kedi görürsünüz.

Hizmet çalışanınızı sayfaları ağ üzerinden yükleneceklerinden farklı şekilde yüklemek için kullanırsanız clients.claim() sorunlu olabilir. Çünkü hizmet çalışanınız, kendisi olmadan yüklenen bazı istemcileri kontrol eder.

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

Özet olarak:

  • Aşağıdakilerden herhangi biri gerçekleşirse güncelleme tetiklenir:
    • Kapsam dahilindeki bir sayfaya gitme.
    • Son 24 saat içinde güncelleme kontrolü yapılmadığı sürece push ve sync gibi işlevsel etkinlikler.
    • .register(), yalnızca hizmet çalışanı URL'si değiştiğinde çağrılır. Ancak çalışan URL'sini değiştirmekten kaçınmanız gerekir.
  • Chrome 68 ve sonraki sürümler de dahil olmak üzere çoğu tarayıcı, kayıtlı hizmet çalışanı komut dosyasının güncellemelerini kontrol ederken önbelleğe alma başlıklarını varsayılan olarak yoksayar. importScripts() aracılığıyla bir hizmet çalışanına yüklenen kaynakları getirirken önbelleğe alma üstbilgilerini dikkate almaya devam ederler. 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ıda bulunan hizmet çalışanından bayt olarak farklıysa güncellenmiş olarak kabul edilir. (Bu özelliği, içe aktarılan komut dosyalarını/modülleri de kapsayacak şekilde genişletiyoruz.)
  • Güncellenen hizmet çalışanı, mevcut hizmet çalışanıyla birlikte başlatılır ve kendi install etkinliğini alır.
  • Yeni çalışanınızın durum kodu "Tamam" değilse (ör. 404), ayrıştırılamıyorsa, yürütme sırasında hata veriyorsa veya yükleme sırasında reddediliyorsa yeni çalışan silinir ancak mevcut çalışan etkin kalır.
  • Başarıyla yüklendikten sonra, güncellenen çalışan, 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ş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ıdaki adımların demosunu inceleyin. Yine de bir kedi resmi görmeniz gerekir. 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 önbellekteki öğelerin üzerine yazmadan yeni önbelleği ayarlayabilirim.

Bu kalıp, yerel bir uygulamanın yürütülebilir dosyasıyla paketleyeceği öğelere benzer şekilde sürüme özel önbellekler oluşturur. Sürüme özgü olmayan önbellekleriniz de (ör. avatars) olabilir.

Bekliyor

Başarıyla yüklendikten sonra, güncellenen hizmet çalışanı, mevcut hizmet çalışanı istemcileri kontrol etmeyi bırakana kadar etkinleştirmeyi geciktirir. Bu durum "bekleme" olarak adlandırılır ve tarayıcı, hizmet çalışanınızın aynı anda 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ği için yine bir kedi resmi görürsünüz. Yeni hizmet çalışanının DevTools'un "Uygulama" sekmesinde beklediğini görebilirsiniz:

Geliştirici Araçları'nda yeni hizmet çalışanının beklediği gösteriliyor

Demoda yalnızca bir sekme açık olsa bile sayfayı yenilemek yeni sürümün devreye girmesi için yeterli değildir. Bunun nedeni, tarayıcı gezinmelerinin işleyiş şeklidir. Gezinirken yanıt başlıkları alınana kadar mevcut sayfa kaybolmaz. Yanıtta Content-Disposition başlığı varsa mevcut 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 mevcut hizmet çalışanını kullanan tüm sekmeleri kapatın veya bu sekmelerden çıkın. Ardından, demoya tekrar gittiğinizde atı görmeniz gerekir.

Bu model, Chrome'un güncellenme şekline benzer. Chrome güncellemeleri arka planda indirilir ancak Chrome yeniden başlatılana kadar uygulanmaz. Bu süre zarfında mevcut sürümü kesintisiz olarak kullanmaya devam edebilirsiniz. Ancak bu durum geliştirme sürecinde zorluklara neden olabilir. Geliştirici Araçları'nda bu süreci kolaylaştıracak yöntemler mevcuttur. Bu yöntemleri makalenin ilerleyen bölümlerinde ele alacağız.

Etkinleştir

Bu, eski hizmet çalışanı kaldırıldıktan ve yeni hizmet çalışanınız istemcileri kontrol edebildikten sonra tetiklenir. Bu, eski çalışan kullanılırken yapamadığınız veritabanlarını taşıma ve önbellekleri temizleme gibi işlemleri yapmak için ideal zamandır.

Yukarıdaki demoda, orada olmasını beklediğim önbelleklerin bir listesini tutuyorum ve activate etkinliğinde diğerlerinden kurtuluyorum. Bu da eski static-v1 önbelleğini kaldırıyor.

event.waitUntil() işlevine bir promise iletirseniz promise çözümlenene kadar işlevsel etkinlikleri (fetch, push, sync vb.) arabelleğe alır. Yani fetch etkinliğiniz tetiklendiğinde etkinleştirme tamamen tamamlanmış olur.

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() yöntemini çağırarak yeni hizmet çalışanınızın daha erken etkinleştirilmesini sağlayabilirsiniz.

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

skipWaiting() işlevini bekleme sırasında veya beklemeden önce çağırdığınız sürece ne zaman çağırdığınızın bir önemi yoktur. Bu işlevi install etkinliğinde çağırmak oldukça yaygındır:

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

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

Ancak hizmet çalışanına postMessage() gönderdikten sonra bu yöntemi çağırmak isteyebilirsiniz. Yani bir kullanıcı etkileşiminden sonra skipWaiting() istiyorsunuz.

skipWaiting() kullanan bir demoyu burada bulabilirsiniz. Başka bir sayfaya gitmenize gerek kalmadan bir inek resmi görmeniz gerekir. 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ı, 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ğırmak isteyebilirsiniz.

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

Önbelleğe alma ile ilgili en iyi uygulamalar hakkındaki yayınımı okuduysanız hizmet çalışanı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.

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

  1. index.html, sw-v1.js dosyasını hizmet çalışanı olarak kaydeder.
  2. sw-v1.js, index.html öğesini önbelleğe alır ve sunar. Böylece öncelikle çevrimdışı çalışır.
  3. index.html uygulamasını güncelleyerek yeni ve parlak sw-v2.js cihazınızı kaydetmesini sağlarsınız.

Yukarıdaki adımları uygularsanız kullanıcı hiçbir zaman sw-v2.js almaz. Bunun nedeni, sw-v1.js uygulamasının index.html öğesinin eski sürümünü önbelleğinden sunmasıdır. Hizmet çalışanınızı güncellemek için hizmet çalışanınızı güncellemeniz gereken bir duruma düşersiniz. Öyk.

Ancak yukarıdaki demoda hizmet çalışanının URL'sini değiştirdim. Bu sayede, demo için sürümler arasında geçiş yapabilirsiniz. Üretimde yapacağım bir şey değil.

Geliştirme sürecini kolaylaştırma

Hizmet çalışanı yaşam döngüsü, kullanıcı düşünülerek oluşturulmuştur ancak geliştirme sırasında biraz zahmetlidir. Neyse ki bu konuda yardımcı olabilecek birkaç araç var:

Yeniden yüklendiğinde güncelle

En sevdiğim bu.

Geliştirici Araçları'nda &quot;yeniden yüklendiğinde güncelle&quot; seçeneği gösteriliyor

Bu sayede yaşam döngüsü, geliştirici dostu hale gelir. Her navigasyon:

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

Bu sayede, sayfayı iki kez yeniden yüklemeniz veya sekmeyi kapatmanız gerekmeden her gezinmede (yenileme dahil) güncellemelerinizi alırsınız.

Beklemeyi atla

Geliştirici Araçları'nda &quot;beklemeyi atla&quot; gösteriliyor

Bekleyen bir çalışanınız varsa Geliştirici Araçları'nda "beklemeyi atla"yı tıklayarak hemen "etkin" duruma yükseltebilirsiniz.

Üst Karakter-Yeniden Yükle

Sayfayı yeniden yüklemeye zorlarsanız (üst karakter-yeniden yükleme) hizmet çalışanı tamamen atlanır. Bu durumda kontrol edilemez. Bu özellik spesifikasyonda yer aldığından, hizmet çalışanları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. Tarayıcı geliştiricileri olarak, web geliştirme konusunda web geliştiricilerinden daha iyi olmadığımızı kabul ediyoruz. Bu nedenle, beğendiğimiz kalıpları kullanarak belirli bir sorunu çözen dar kapsamlı üst düzey API'ler sunmak yerine tarayıcının iç kısımlarına erişmenizi ve istediğiniz şekilde, kullanıcılarınız için en uygun şekilde yapmanızı sağlamalıyız.

Mümkün olduğunca çok 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ü 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ünür. Bu bilgi, hizmet çalışanlarını dağıtırken ve güncellerken size daha fazla güven verecektir.