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 yararlarının ne olduğunu bilmiyorsanız sizinle savaşıyormuş gibi hissedebilirsiniz. Ancak işleyiş şeklini öğrendiğinizde, web ve yerel kalıpların en iyi özelliklerini bir araya getirerek 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ı:

  • Önce çevrimdışı kullanımı mümkün kılın.
  • Yeni bir Service Worker'ın mevcut hizmeti kesintiye uğratmadan kendisini hazırlamasına izin ver.
  • Kapsam içi bir sayfanın, sayfa boyunca aynı hizmet çalışanı (veya hizmet çalışanı değil) 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'lar 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 yol açabilir. Bazen bu bir sorun teşkil etmez. Ancak, depolama alanıyla uğraşıyorsanız, paylaşılan depolama alanlarının nasıl yönetilmesi gerektiği konusunda epey farklı fikirlere sahip iki sekmeyle karşılaşabilirsiniz. Bu durum hatalara, 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() öğesine iletilen söz, yüklemenin süresini ve başarılı ya da başarısız olduğunu gösterir.
  • Yükleme işlemi başarıyla tamamlayıp "etkin" hale gelene kadar bir hizmet çalışanı fetch ve push gibi etkinlikleri almaz.
  • Varsayılan olarak, sayfa isteğinin kendisi bir Service Worker'dan geçmediği sürece, sayfanın getirme işlemleri Service Worker üzerinden gerçekleşmez. Bu nedenle, Service Worker'ın etkilerini görmek için sayfayı yenilemeniz gerekir.
  • clients.claim(), bu varsayılanı geçersiz kılabilir ve denetlenmeyen sayfaların kontrolünü ele geçirebilir.

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

Hizmet çalışanı kaydeder ve 3 saniye sonra bir köpeğin resmini ekler.

Hizmet çalışanı (sw.js) şöyle:

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 kedi resmini önbelleğe alır ve /dog.svg için bir istek olduğunda sunar. Ancak, yukarıdaki örneği çalıştırırsanız sayfayı ilk yüklediğinizde bir köpek görürsünüz. Yenile düğmesine bastığınızda kediyi göreceksiniz.

Kapsam ve kontrol

Service Worker 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 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 denetleyebilir. Bir istemci "kontrol edildikten" sonra getirme işlemleri kapsam içi hizmet çalışanı üzerinden yapılır. Bir istemcinin, boş değer veya Service Worker örneği olacak navigator.serviceWorker.controller aracılığıyla kontrol edilip edilmediğini belirleyebilirsiniz.

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

.register() numaralı telefonu aradığınızda ilk hizmet çalışanınız indirme yapar. Komut dosyanız indirilemez, ayrıştırılamaz veya ilk yürütme sırasında bir hata verirse kayıt sözü reddedilir ve Service Worker silinir.

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

Hizmet çalışanı Geliştirici Araçları sekmesinde hata görüntülendi

Yükle

Service Worker'ın aldığı ilk etkinlik install olur. Çalışan çalıştırılır çalıştırılmaz tetiklenir ve Service Worker 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 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 etmeden önce ihtiyacınız olan her şeyi önbelleğe alma fırsatı sunar. event.waitUntil() için verdiğiniz söz, tarayıcının yüklemenizin ne zaman tamamlandığını ve başarılı olup olmadığını bilmesini sağlar.

Vaadiniz reddedilirse bu, yüklemenin başarısız olduğu anlamına gelir ve tarayıcı hizmet çalışanını atar. Asla müşterileri kontrol etmeyecektir. Bu, fetch etkinliklerimizde cat.svg öğesinin önbellekte bulunduğuna güvenemeyeceğimiz anlamına gelir. Bu bir bağımlılık.

Etkinleştir

Service Worker'ı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() adlı sayfanın kontrol edileceği anlamına gelmez.

Demoyu ilk kez yüklediğinizde, hizmet çalışanı etkinleştikten uzun süre sonra dog.svg istenmesine rağmen bu istek işleme alınmaz ve köpeğin resmini görmeye devam edersiniz. Varsayılan değer tutarlılıktı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ı yenileyin) kontrol edilir. Hem sayfa hem de resim, fetch etkinliklerinden geçer ve yerine bir kedi simgesi görünür.

clients.claim

Etkinleştirildikten sonra hizmet çalışanınızdan clients.claim() yöntemini çağırarak denetlenmeyen istemcilerin kontrolünü elinize alabilirsiniz.

activate etkinliğinde clients.claim() çağıran yukarıdaki demonun bir varyasyonunu burada bulabilirsiniz. İlk seferinde bir kedi görmeniz gerekir. Zamanlamaya duyarlı olduğu için “yapmalı” diyorum. Bir kediyi yalnızca Service Worker etkinleşirse ve clients.claim() resim yüklenmeden önce etkinleşirse görürsünüz.

Sayfaları, ağ üzerinden yükleyeceklerinden farklı şekilde yüklemek için Service Worker'ınızı kullanırsanız, hizmet çalışanınız bu hizmet olmadan yüklenen bazı istemcileri kontrol edeceği için clients.claim() sorun yaratabilir.

Hizmet çalışanı güncelleniyor

Özet olarak:

  • Aşağıdakilerden herhangi biri gerçekleştiğinde güncelleme tetiklenir:
  • 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 üst bilgilerini yok sayar. Bunlar, importScripts() aracılığıyla bir hizmet çalışanının içine yüklenen kaynakları getirirken önbelleğe alma üst bilgilerini kullanmaya 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 sahip olduğu bayttan farklıysa güncellenmiş olarak kabul edilir. (Bunu içe aktarılan komut dosyalarını/modülleri de kapsayacak şekilde genişletiyoruz.)
  • Güncellenen Service Worker, mevcut çalışanla birlikte başlatılır ve kendi install etkinliğini alır.
  • Yeni çalışanınız uygun olmayan bir durum koduna sahipse (örneğin, 404), ayrıştırılamazsa, yürütme sırasında hata veriyorsa veya yükleme sırasında reddederse yeni çalışan atılır, ancak mevcut çalışan etkin olarak kalır.
  • Başarıyla yüklendikten sonra, mevcut çalışan sıfır istemciyi kontrol edene kadar güncellenen çalışan wait işlemi gerçekleştirir. (İstemcilerin yenileme sırasında çakıştığını unutmayın.)
  • self.skipWaiting() beklemeyi önler. Diğer bir ifadeyle, hizmet çalışanı yükleme işlemi biter bitmez etkinleşir.

Service Worker komut dosyamızı, yanıt olarak kedi yerine at resmiyle 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 demosunu inceleyin. Yine de bir kedi resmi görüyor olmalısınız. İşte nedeni...

Yükle

static-v1 olan önbellek adını static-v2 olarak değiştirdiğimi unutmayın. Yani eski hizmet çalışanının kullandığı mevcut önbelleğin üzerine yazmadan yeni önbelleği ayarlayabilirim.

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

Bekliyor

Başarıyla yüklendikten sonra, güncellenen hizmet çalışanı, mevcut hizmet çalışanı istemcileri denetlemeyi bırakana kadar etkinleştirmeyi geciktirir. Bu duruma "bekleniyor" denir ve tarayıcı, Service Worker'ın aynı anda yalnızca bir sürümünün çalışmasını bu şekilde sağlar.

Güncellenmiş demoyu çalıştırdıysanız, V2 çalışanı henüz etkinleştirilmediği için bir kedi resmi görmeye devam edersiniz. Yeni hizmet çalışanını, Geliştirici Araçları'nın "Uygulama" sekmesinde görebilirsiniz:

Yeni hizmet çalışanının bekleme durumunu gösteren Geliştirici Araçları

Demonun yalnızca bir sekmesi açık olsa bile sayfayı yenilemek, yeni sürümün devralmasını sağlamak için yeterli olmaz. Bunun nedeni tarayıcı gezinmelerinin çalışma şeklidir. Gezinirken, yanıt başlıkları alınana kadar geçerli sayfa kaybolmaz ve yanıtta bir Content-Disposition başlığı varsa 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 çıkın. Ardından, demoya tekrar gittiğinizde atı görmeniz gerekir.

Bu kalıp, Chrome'un güncelleme sürecine benzer. Chrome'da yapılan güncellemeler arka planda indirilir, ancak Chrome yeniden başlatılana kadar uygulanmaz. Bu sırada, mevcut sürümü kesinti yaşamadan kullanmaya devam edebilirsiniz. Ancak bu, geliştirme sürecinde zorluklara yol açsa da Geliştirici Araçları'nın bunu kolaylaştırma yolları vardır. Bunları bu makalenin ilerleyen kısımlarında ele alacağım.

Etkinleştir

Bu uyarı, eski Service Worker işten ayrıldığında ve yeni Service Worker'ınız istemcileri kontrol edebildiğinde tetiklenir. Bu, eski çalışan kullanımdayken yapamadığınız işlemleri (veritabanlarını taşıma ve önbellekleri temizleme gibi) yapmak için ideal zamandır.

Yukarıdaki demoda, orada olmasını beklediğim önbelleklerin bir listesini tutuyorum. activate durumunda ise eski static-v1 önbelleğini kaldıran diğerlerini kaldırıyorum.

event.waitUntil() adlı kullanıcıya bir sözü iletirseniz söz verilene kadar işlevsel etkinlikler (fetch, push, sync vb.) arabelleğe alınır. Dolayısıyla, fetch etkinliğiniz tetiklendiğinde etkinleştirme işlemi tamamen tamamlanmış olur.

Bekleme aşamasını atlayın

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

Bu, hizmet çalışanınızın mevcut etkin çalışanı devre dışı bırakmasına ve bekleme aşamasına geçer geçmez (veya zaten bekleme aşamasındaysa) kendini etkinleştirmesine neden olur. Bu, çalışanınızın yükleme işlemini atlamasına, yalnızca biraz beklemesine neden olmaz.

Bekleme sırasında veya beklemede olduğu sürece, skipWaiting() numaralı telefonu ne zaman aradığınızın gerçekten önemi yoktur. install etkinliğinde çağırmak oldukça yaygın bir uygulamadır:

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

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

Ancak bunu, hizmet çalışanı için bir postMessage() sonucu olarak çağırmak isteyebilirsiniz. Örneğin, bir kullanıcı etkileşiminden sonra skipWaiting().

skipWaiting() kullanılan bir demo. Sayfadan ayrılmanıza gerek kalmadan bir inek resmi görmeniz gerekir. clients.claim() gibi bu bir yarış olduğu için 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 aralıklarla (ör. saatlik) update() çağırmak isteyebilirsiniz.

Service Worker 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 hizmet çalışanınızın her sürümüne benzersiz bir URL vermeyi düşünebilirsiniz. Bunu yapmayın! Bu, hizmet çalışanları için genellikle kötü bir uygulamadır. Komut dosyasını mevcut konumunda güncellemeniz yeterlidir.

Aşağıdaki gibi bir sorunla karşılaşabilirsiniz:

  1. index.html, sw-v1.js adlı iş ortağını hizmet çalışanı olarak kaydediyor.
  2. sw-v1.js, index.html önce çevrimdışı çalışması için önbelleğe alır ve sunar.
  3. Yeni ve etkileyici sw-v2.js öğenizi kaydetmek için index.html öğesini güncellersiniz.

sw-v1.js, önbelleğinden index.html eski sürümünü sunduğundan yukarıdakileri yaparsanız kullanıcı hiçbir zaman sw-v2.js ürününü almaz. Kendinizi Service Worker'ınızı güncellemek için hizmet çalışanınızı güncellemeniz gereken bir konuma yerleştirdiniz. Ew.

Ancak, yukarıdaki demoda hizmet çalışanının URL'sini değiştirdim. Demo olması açısından sürümler arasında geçiş yapabilirsiniz. Üretimde böyle bir şey olmayacak.

Geliştirmeyi kolaylaştırma

Service Worker'ın yaşam döngüsü, kullanıcı göz önünde bulundurularak oluşturulur ancak geliştirme sırasında biraz sıkıntı yaşanır. Neyse ki size yardımcı olabilecek birkaç araç var:

Yeniden yüklemede güncelle

En sevdiğim yöntem.

Geliştirici Araçları, &quot;yeniden yüklendiğinde güncelle&quot; mesajını gösteriyor

Bu, yaşam döngüsünü geliştiricilere daha uygun olacak şekilde değiştirir. Her gezinme şunları yapar:

  1. Hizmet çalışanını yeniden getirin.
  2. Bayt olarak aynı olsa bile yeni sürüm olarak yükleyin. Bu, install etkinliğinizin çalıştığı ve önbelleklerinizin güncellendiği anlamına gelir.
  3. Yeni Service Worker'ın etkinleştirilmesi için bekleme aşamasını atlayın.
  4. Sayfada gezinme.

Bu sayede, iki kez yeniden yüklemek veya sekmeyi kapatmak zorunda kalmadan her gezinmede (yenileme dahil) güncellemelerinizi alırsınız.

Beklemeyi atla

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

Bekleyen bir çalışanınız varsa Geliştirici Araçları'nda "beklemeyi atla" seçeneğiyle hemen "etkin" durumuna yükseltilebilir.

Üst karakter-yeniden yükle

Sayfayı zorunlu olarak yeniden yüklerseniz (üst karakter-yeniden yükleme) Service Worker'ı tamamen atlar. Bu işlem kontrol edilemez. Bu özellik spesifikasyonda olduğu için hizmet çalışanları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. Buradaki fikir, tarayıcı geliştiricileri olarak web geliştirme alanında web geliştiricilerinden daha iyi olmadığımızı kabul etmektir. Dolayısıyla, beğendiğimiz kalıpları kullanarak belirli bir sorunu çözen, dar kapsamlı ve üst düzey API'ler sunmamalı, bunun yerine tarayıcının iç sesine ulaşmanızı ve istediğiniz şekilde, kullanıcılarınız için en iyi sonucu almanızı sağlayan API'leri sunmalıyız.

Bu nedenle, mümkün olduğunca fazla 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ü

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