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.
- 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
vepush
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. 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
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:
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 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ı 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 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
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ü 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 bir 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
vesync
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ı kaydederkenupdateViaCache
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
. (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:
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ımdayken 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, hizmet işleyiciye yapılan bir postMessage()
sonucunda çağırabilirsiniz. Yani, bir kullanıcı etkileşimini takip eden skipWaiting()
işlemini yapmak istiyorsunuz.
skipWaiting()
kullanan bir demoyu aşağıda bulabilirsiniz. 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şçinizin 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:
index.html
,sw-v1.js
'ı hizmet çalışanı olarak kaydeder.sw-v1.js
,index.html
'u önbelleğe alır ve sunar. Bu nedenle, önce çevrimdışı olarak çalışır.index.html
'ü güncelleyerek yeni ve parlaksw-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.
Bu işlem, yaşam döngüsünü geliştirici dostu olacak şekilde değiştirir. Her gezinme:
- Servis çalışanını yeniden getirin.
- 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. - Yeni hizmet çalışanının etkinleşmesi için bekleme aşamasını atlayın.
- 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
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, service-worker'ı 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 istediğiniz şekilde, kendi kullanıcılarınız için en iyi şekilde işlem 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.