Siklus proses pekerja layanan adalah bagian yang paling rumit. Jika Anda tidak tahu apa yang ingin dilakukan dan apa manfaatnya, Anda mungkin merasa seperti sedang melawannya. Namun, setelah mengetahui cara kerjanya, Anda dapat memberikan update yang lancar dan tidak mengganggu kepada pengguna, dengan memadukan pola web dan native terbaik.
Ini adalah pembahasan mendalam, tetapi poin-poin di awal setiap bagian mencakup sebagian besar hal yang perlu Anda ketahui.
Maksud
Tujuan siklus proses adalah untuk:
- Memungkinkan offline-first.
- Mengizinkan pekerja layanan baru untuk bersiap tanpa mengganggu pekerja layanan yang ada.
- Pastikan halaman dalam cakupan dikontrol oleh pekerja layanan yang sama (atau tidak ada pekerja layanan) secara keseluruhan.
- Pastikan hanya ada satu versi situs Anda yang berjalan dalam satu waktu.
Poin terakhir ini cukup penting. Tanpa pekerja layanan, pengguna dapat memuat satu tab ke situs Anda, lalu membuka tab lain. Hal ini dapat menyebabkan dua versi situs Anda berjalan secara bersamaan. Terkadang hal ini tidak masalah, tetapi jika Anda berurusan dengan penyimpanan, Anda dapat dengan mudah berakhir dengan dua tab yang memiliki pendapat yang sangat berbeda tentang cara mengelola penyimpanan bersama. Hal ini dapat menyebabkan error, atau lebih buruk lagi, kehilangan data.
Pekerja layanan pertama
Singkatnya:
- Peristiwa
installadalah peristiwa pertama yang diterima pekerja layanan, dan hanya terjadi sekali. - Promise yang diteruskan ke
installEvent.waitUntil()menandakan durasi dan keberhasilan atau kegagalan penginstalan Anda. - Service worker tidak akan menerima peristiwa seperti
fetchdanpushhingga berhasil menyelesaikan penginstalan dan menjadi "aktif". - Secara default, pengambilan halaman tidak akan melalui pekerja layanan kecuali jika permintaan halaman itu sendiri melalui pekerja layanan. Jadi, Anda harus memuat ulang halaman untuk melihat efek service worker.
clients.claim()dapat mengganti setelan default ini, dan mengontrol halaman yang tidak dikontrol.
Lihat HTML ini:
<!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>
Halaman ini mendaftarkan pekerja layanan, dan menambahkan gambar setelah 3 detik.
Berikut adalah pekerja layanannya, 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'));
}
});
CDN meng-cache gambar kucing, dan menyajikannya setiap kali ada permintaan untuk /dog.svg. Namun, jika Anda menjalankan contoh di atas, Anda akan melihat saat pertama kali memuat halaman. Muat ulang, dan Anda akan melihat kucing.
Cakupan dan kontrol
Cakupan default pendaftaran pekerja layanan adalah ./ yang relatif terhadap URL skrip. Artinya, jika Anda mendaftarkan pekerja layanan di //example.com/foo/bar.js, pekerja layanan tersebut memiliki cakupan default //example.com/foo/.
Kami menyebut halaman, worker, dan worker bersama sebagai clients. Service worker Anda hanya dapat mengontrol klien yang berada dalam cakupan. Setelah klien "dikontrol", pengambilannya akan melalui pekerja layanan dalam cakupan. Anda dapat mendeteksi apakah klien dikontrol melalui navigator.serviceWorker.controller yang akan bernilai null atau instance pekerja layanan.
Mendownload, mengurai, dan mengeksekusi
Pekerja layanan pertama Anda akan didownload saat Anda memanggil .register(). Jika skrip Anda gagal didownload, diurai, atau menampilkan error dalam eksekusi awalnya, promise pendaftaran akan ditolak, dan pekerja layanan akan dihapus.
DevTools Chrome menampilkan error di konsol, dan di bagian pekerja layanan pada tab aplikasi:
Instal
Peristiwa pertama yang diterima pekerja layanan adalah install. Peristiwa ini dipicu segera setelah worker dijalankan, dan hanya dipanggil sekali per pekerja layanan. Jika Anda mengubah skrip pekerja layanan, browser akan menganggapnya sebagai pekerja layanan yang berbeda, dan pekerja layanan tersebut akan mendapatkan peristiwa install-nya sendiri. Saya akan membahas pembaruan secara mendetail nanti.
Peristiwa install adalah kesempatan Anda untuk meng-cache semua yang Anda perlukan sebelum dapat mengontrol klien. Promise yang Anda teruskan ke event.waitUntil() memungkinkan browser mengetahui kapan penginstalan selesai, dan apakah penginstalan berhasil.
Jika promise Anda ditolak, ini menandakan penginstalan gagal, dan browser akan menghapus pekerja layanan. Agen tidak akan pernah mengontrol klien. Artinya, kita dapat mengandalkan cat.svg yang ada di cache dalam peristiwa fetch. Ini adalah dependensi.
Aktifkan
Setelah pekerja layanan Anda siap mengontrol klien dan menangani peristiwa fungsional seperti push dan sync, Anda akan mendapatkan peristiwa activate. Namun, hal ini tidak berarti halaman yang memanggil .register() akan dikontrol.
Saat pertama kali Anda memuat demo, meskipun dog.svg diminta lama setelah pekerja layanan diaktifkan, pekerja layanan tidak menangani permintaan tersebut, dan Anda masih melihat gambar. Setelan defaultnya adalah konsistensi, jika halaman Anda dimuat tanpa pekerja layanan, subresource-nya juga tidak akan dimuat. Jika Anda memuat demo untuk kedua kalinya (dengan kata lain, memuat ulang halaman), demo tersebut akan dikontrol. Halaman dan gambar akan melewati peristiwa fetch, dan Anda akan melihat kucing.
clients.claim
Anda dapat mengontrol klien yang tidak terkontrol dengan memanggil clients.claim() dalam pekerja layanan setelah diaktifkan.
Berikut adalah variasi demo di atas yang memanggil clients.claim() dalam peristiwa activate-nya. Anda akan melihat kucing untuk pertama kalinya. Kami menggunakan kata "seharusnya", karena hal ini sensitif terhadap waktu. Anda hanya akan melihat kucing jika pekerja layanan diaktifkan dan clients.claim() diterapkan sebelum gambar mencoba dimuat.
Jika Anda menggunakan pekerja layanan untuk memuat halaman secara berbeda dari cara halaman dimuat melalui jaringan, clients.claim() dapat menimbulkan masalah, karena pekerja layanan Anda akhirnya mengontrol beberapa klien yang dimuat tanpa pekerja layanan tersebut.
Mengupdate pekerja layanan
Singkatnya:
- Pembaruan akan dipicu jika salah satu hal berikut terjadi:
- Navigasi ke halaman dalam cakupan.
- Peristiwa fungsional seperti
pushdansync, kecuali jika ada pemeriksaan update dalam 24 jam sebelumnya. - Memanggil
.register()hanya jika URL pekerja layanan telah berubah. Namun, Anda harus menghindari perubahan URL worker.
- Sebagian besar browser, termasuk Chrome 68 dan yang lebih baru, secara default mengabaikan header caching saat memeriksa update skrip pekerja layanan yang terdaftar. Header caching tetap diterapkan saat mengambil resource yang dimuat di dalam pekerja layanan melalui
importScripts(). Anda dapat mengganti perilaku default ini dengan menyetel opsiupdateViaCachesaat mendaftarkan pekerja layanan. - Service worker Anda dianggap telah diupdate jika byte-nya berbeda dengan yang sudah dimiliki browser. (Kami memperluasnya untuk menyertakan skrip/modul yang diimpor juga.)
- Service worker yang diperbarui diluncurkan bersamaan dengan yang sudah ada, dan mendapatkan peristiwa
install-nya sendiri. - Jika worker baru Anda memiliki kode status non-ok (misalnya, 404), gagal mengurai, menampilkan error selama eksekusi, atau menolak selama penginstalan, worker baru akan dihapus, tetapi worker saat ini tetap aktif.
- Setelah berhasil diinstal, worker yang diupdate akan
waithingga worker yang ada mengontrol nol klien. (Perhatikan bahwa klien tumpang-tindih selama pemuatan ulang.) self.skipWaiting()mencegah waktu tunggu, yang berarti pekerja layanan akan diaktifkan segera setelah selesai diinstal.
Misalnya, kita mengubah skrip pekerja layanan untuk merespons dengan gambar kuda, bukan kucing:
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'));
}
});
Lihat demo di atas. Anda akan tetap melihat gambar kucing. Berikut alasannya…
Instal
Perhatikan bahwa saya telah mengubah nama cache dari static-v1 menjadi static-v2. Artinya, saya dapat menyiapkan cache baru tanpa menimpa hal-hal di cache saat ini, yang masih digunakan oleh pekerja layanan lama.
Pola ini membuat cache khusus versi, mirip dengan aset yang akan dipaketkan oleh aplikasi native dengan file yang dapat dieksekusi. Anda mungkin juga memiliki cache yang tidak spesifik untuk versi tertentu, seperti avatars.
Menunggu
Setelah berhasil diinstal, pekerja layanan yang diupdate akan menunda aktivasi hingga pekerja layanan yang ada tidak lagi mengontrol klien. Status ini disebut "menunggu", dan ini adalah cara browser memastikan bahwa hanya satu versi pekerja layanan Anda yang berjalan pada satu waktu.
Jika Anda menjalankan demo yang telah diupdate, Anda akan tetap melihat gambar kucing, karena worker V2 belum diaktifkan. Anda dapat melihat service worker baru yang menunggu di tab "Application" di DevTools:
Meskipun Anda hanya membuka satu tab ke demo, memuat ulang halaman tidak cukup untuk mengizinkan versi baru mengambil alih. Hal ini disebabkan oleh cara kerja navigasi browser. Saat Anda melakukan navigasi, halaman saat ini tidak akan hilang hingga header respons diterima, dan bahkan halaman saat ini mungkin tetap ada jika respons memiliki header Content-Disposition. Karena tumpang-tindih ini, pekerja layanan saat ini selalu mengontrol klien selama pemuatan ulang.
Untuk mendapatkan update, tutup atau keluar dari semua tab yang menggunakan service worker saat ini. Kemudian, saat Anda membuka demo lagi, Anda akan melihat kuda.
Pola ini mirip dengan cara Chrome diupdate. Update Chrome didownload di latar belakang, tetapi tidak diterapkan hingga Chrome dimulai ulang. Sementara itu, Anda dapat terus menggunakan versi saat ini tanpa gangguan. Namun, hal ini merepotkan selama pengembangan, tetapi DevTools memiliki cara untuk mempermudahnya, yang akan saya bahas nanti dalam artikel ini.
Aktifkan
Ini diaktifkan setelah pekerja layanan lama hilang, dan pekerja layanan baru Anda dapat mengontrol klien. Ini adalah waktu yang ideal untuk melakukan hal-hal yang tidak dapat Anda lakukan saat worker lama masih digunakan, seperti memigrasikan database dan menghapus cache.
Dalam demo di atas, saya mengelola daftar cache yang saya harapkan ada di sana, dan dalam peristiwa activate, saya menghapus cache lainnya, yang akan menghapus cache static-v1 lama.
Jika Anda meneruskan promise ke event.waitUntil(), promise tersebut akan menyangga peristiwa fungsional (fetch, push, sync, dll.) hingga promise diselesaikan. Jadi, saat peristiwa fetch diaktifkan, aktivasi akan selesai sepenuhnya.
Melewati fase tunggu
Fase tunggu berarti Anda hanya menjalankan satu versi situs dalam satu waktu, tetapi jika tidak memerlukan fitur tersebut, Anda dapat membuat pekerja layanan baru diaktifkan lebih cepat dengan memanggil self.skipWaiting().
Hal ini menyebabkan pekerja layanan Anda mengeluarkan pekerja aktif saat ini dan mengaktifkan dirinya sendiri segera setelah memasuki fase tunggu (atau segera jika sudah dalam fase tunggu). Hal ini tidak menyebabkan worker Anda melewati penginstalan, hanya menunggu.
Tidak masalah kapan Anda memanggil skipWaiting(), asalkan selama atau sebelum menunggu. Biasanya, metode ini dipanggil dalam peristiwa install:
self.addEventListener('install', event => {
self.skipWaiting();
event.waitUntil(
// caching etc
);
});
Namun, Anda mungkin ingin memanggilnya sebagai hasil dari postMessage() ke pekerja layanan. Artinya, Anda ingin skipWaiting() setelah interaksi pengguna.
Berikut demo yang menggunakan skipWaiting(). Anda akan melihat gambar sapi tanpa harus keluar dari halaman. Seperti clients.claim(), ini adalah perlombaan, jadi Anda hanya akan melihat sapi jika pekerja layanan baru mengambil, menginstal, dan mengaktifkan sebelum halaman mencoba memuat gambar.
Pembaruan manual
Seperti yang disebutkan sebelumnya, browser memeriksa update secara otomatis setelah navigasi dan peristiwa fungsional, tetapi Anda juga dapat memicunya secara manual:
navigator.serviceWorker.register('/sw.js').then(reg => {
// sometime later…
reg.update();
});
Jika Anda memperkirakan pengguna akan menggunakan situs Anda dalam waktu lama tanpa memuat ulang, Anda dapat memanggil update() pada interval tertentu (seperti setiap jam).
Hindari mengubah URL skrip pekerja layanan Anda
Jika Anda telah membaca postingan saya tentang praktik terbaik caching, sebaiknya berikan URL unik untuk setiap versi pekerja layanan Anda. Jangan lakukan ini! Hal ini biasanya merupakan praktik yang buruk untuk pekerja layanan, cukup perbarui skrip di lokasinya saat ini.
Hal ini dapat menyebabkan masalah seperti ini:
index.htmlmendaftarkansw-v1.jssebagai pekerja layanan.sw-v1.jsmeng-cache dan menyajikanindex.htmlsehingga berfungsi secara offline-first.- Anda mengupdate
index.htmlagar mendaftarkansw-v2.jsbaru dan keren.
Jika Anda melakukan hal di atas, pengguna tidak akan pernah mendapatkan sw-v2.js, karena sw-v1.js menayangkan versi lama index.html dari cache-nya. Anda telah menempatkan diri Anda dalam posisi yang mengharuskan Anda mengupdate pekerja layanan untuk mengupdate pekerja layanan. Iiih.
Namun, untuk demo di atas, saya telah mengubah URL pekerja layanan. Hal ini dilakukan agar Anda dapat beralih di antara versi untuk keperluan demo. Hal ini bukan sesuatu yang akan saya lakukan dalam produksi.
Mempermudah pengembangan
Siklus proses pekerja layanan dibuat dengan mempertimbangkan pengguna, tetapi selama pengembangan, hal ini agak merepotkan. Untungnya, ada beberapa alat yang dapat membantu:
Update saat muat ulang
Ini favorit saya.
Hal ini mengubah siklus proses menjadi lebih mudah bagi developer. Setiap navigasi akan:
- Ambil ulang pekerja layanan.
- Instal sebagai versi baru meskipun byte-nya identik, yang berarti peristiwa
installAnda berjalan dan cache Anda diperbarui. - Lewati fase tunggu agar pekerja layanan baru diaktifkan.
- Menjelajahi halaman.
Artinya, Anda akan mendapatkan update pada setiap navigasi (termasuk muat ulang) tanpa harus memuat ulang dua kali atau menutup tab.
Lewati waktu tunggu
Jika ada worker yang menunggu, Anda dapat menekan "lewati tunggu" di DevTools untuk segera mempromosikannya ke "aktif".
Shift-muat ulang
Jika Anda memuat ulang halaman secara paksa (shift-reload), tindakan ini akan mengabaikan pekerja layanan sepenuhnya. Hal ini akan menjadi tidak terkontrol. Fitur ini ada dalam spesifikasi, sehingga berfungsi di browser lain yang mendukung service worker.
Menangani update
Service worker dirancang sebagai bagian dari web yang dapat diperluas. Idenya adalah bahwa kami, sebagai developer browser, mengakui bahwa kami tidak lebih baik dalam pengembangan web daripada developer web. Oleh karena itu, kita tidak boleh menyediakan API tingkat tinggi yang sempit yang menyelesaikan masalah tertentu menggunakan pola yang kita sukai, tetapi memberi Anda akses ke bagian dalam browser dan memungkinkan Anda melakukannya sesuai keinginan, dengan cara yang paling sesuai untuk pengguna Anda.
Jadi, untuk mengaktifkan sebanyak mungkin pola, seluruh siklus update dapat diamati:
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.
});
Siklus proses terus berlanjut
Seperti yang Anda lihat, memahami siklus proses pekerja layanan akan sangat bermanfaat—dan dengan pemahaman tersebut, perilaku pekerja layanan akan tampak lebih logis dan tidak terlalu misterius. Pengetahuan tersebut akan memberi Anda lebih banyak keyakinan saat men-deploy dan mengupdate pekerja layanan.