Didn't make the #ChromeDevSummit this year? Catch all the content (and more!) in the Chrome Dev Summit 2019 playlist on our Chrome Developers YouTube Channel.

Daur Hidup Pekerja Layanan

Daur hidup pekerja layanan adalah bagian yang paling rumit. Jika Anda tidak tahu apa yang harus dilakukan dan apa manfaatnya, maka hal ini bisa terasa berat bagi Anda. Namun setelah Anda mengetahui cara kerjanya, Anda bisa menghasilkan update yang mulus dan tidak kentara untuk pengguna, dengan memadukan yang aspek terbaik dari web dan pola asli.

Ini merupakan penjelasan mendalam, namun poin-poin di awal setiap bagian hampir semua membahas hal yang perlu Anda ketahui.

Maksud

Intent dari daur hidup adalah untuk:

  • Memungkinkan pengutamaan offline.
  • Memungkinkan pekerja layanan baru melakukan penyiapan mandiri tanpa mengganggu pekerja layanan saat ini.
  • Memastikan halaman dalam cakupan dikontrol oleh pekerja layanan yang sama (atau tanpa pekerja layanan) seluruhnya.
  • Memastikan hanya ada satu versi yang dijalankan untuk situs Anda pada satu waktu.

Poin terakhir merupakan hal yang sangat penting. Tanpa pekerja layanan, pengguna bisa memuat satu tab ke situs Anda, kemudian membuka tab lain nanti. Hal ini bisa mengakibatkan dua versi situs Anda dijalankan pada waktu yang sama. Terkadang hal ini boleh terjadi, namun jika Anda sedang berurusan dengan penyimpanan, Anda bisa dengan mudah mengakibatkan dua tab memiliki opini sangat berbeda mengenai cara keduanya menangani penyimpanan bersama. Hal ini bisa mengakibatkan error, atau lebih buruk lagi, kehilangan data.

Perhatian: Pengguna sangat tidak suka kehilangan data. Hal itu akan menyebabkan mereka sangat bersedih.

Pekerja layanan pertama

Secara singkat:

  • Peristiwa install adalah peristiwa pertama yang diambil pekerja layanan, dan ini hanya terjadi sekali.
  • Sebuah promise diteruskan ke installEvent.waitUntil() yang akan menunjukkan durasi serta keberhasilan atau kegagalan penginstalan.
  • Pekerja layanan tidak akan menerima peristiwa seperti fetch dan push sebelum berhasil menyelesaikan penginstalan dan menjadi "aktif".
  • Secara default, pengambilan oleh halaman tidak akan melalui pekerja layanan kecuali jika permintaan halaman itu sendiri melalui pekerja layanan. Jadi Anda perlu memuat ulang halaman untuk melihat pengaruh dari pekerja layanan.
  • clients.claim() bisa menggantikan default ini, dan mengambil kontrol atas halaman yang tidak dikontrol.

Perhatikan HTML ini:

<!DOCTYPE html>
Sebuah gambar akan muncul di sini dalam waktu 3 detik:
<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>

HTML mendaftarkan pekerja layanan dan menambahkan gambar anjing setelah 3 detik.

Inilah pekerja layanan tersebut, 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'));
  }
});

Pekerja layanan menyimpan cache gambar kucing, dan menyajikannya jika ada permintaan untuk /dog.svg. Akan tetapi, jika Anda menjalankan contoh di atas, Anda akan melihat anjing saat pertama kali memuat halaman. Klik muat ulang, dan Anda akan melihat gambar kucing tersebut.

Cakupan dan kontrol

Cakupan default pendaftaran pekerja layanan ./ dibandingkan URL skrip. Berarti, jika Anda mendaftarkan pekerja layanan di //example.com/foo/bar.js, maka akan memiliki cakupan default //example.com/foo/.

Kita menyebutnya halaman, pekerja, dan pekerja bersama berupa clients. Pekerja layanan hanya bisa mengontrol klien yang berada dalam cakupan. Setelah klien "dikontrol", pengambilannya akan melalui pekerja layanan dalam cakupan. Anda bisa mendeteksi jika klien dikontrol lewat navigator.serviceWorker.controller yang akan berupa nol atau instance pekerja layanan.

Download, uraikan, dan jalankan

Pekerja layanan pertama Anda akan didownload jika Anda memanggil .register() Jika skrip Anda gagal mendownload, menguraikan, atau membuang error dalam eksekusi pertamanya, promise register akan menolak, dan pekerja layanan akan dihapus.

Chrome DevTools menampilkan error di konsol, dan di bagian pekerja layanan pada tab aplikasi:

Error yang ditampilkan di tab DevTools pada pekerja layanan

Instal

Peristiwa pertama yang diambil pekerja layanan adalah install. Penginstalan akan dipicu begitu pekerja layanan dieksekusi, dan hanya dipanggil sekali per pekerja layanan. Jika Anda mengubah skrip pekerja layanan, browser akan menganggapnya sebagai pekerja layanan yang berbeda, dan akan mendapatkan peristiwa install sendiri. Saya akan membahas update secara detail nanti.

Peristiwa install adalah kesempatan Anda untuk men-cache segala sesuatu yang Anda butuhkan sebelum dapat mengontrol klien. Promise yang Anda teruskan ke event.waitUntil() memungkinkan browser mengetahui kapan Anda selesai menginstal, dan apakah penginstalan itu berhasil.

Jika promise Anda ditolak, ini menandakan penginstalan gagal, dan browser membuang pekerja layanan. Itu tidak akan pernah mengontrol klien. Ini berarti kita tidak bisa 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 itu tidak berarti halaman yang disebut .register() akan dikontrol.

Saat pertama Anda memuat demo, walaupun dog.svg telah lama diminta setelah pekerja layanan diaktifkan, itu tidak menangani permintaan tersebut, dan Anda tetap melihat gambar anjing. Default-nya adalah konsistensi, jika halaman dimuat tanpa pekerja layanan, tidak ada yang akan menjadi sub-resourcenya. Jika Anda memuat demo untuk kedua kalinya (dengan kata lain, muat ulang halaman), itu akan dikontrol. Halaman dan gambar akan melalui peristiwa fetch, dan Anda akan melihat kucing sebagai gantinya.

clients.claim

Anda bisa mengontrol klien yang tidak dikontrol dengan memanggil clients.claim() dalam pekerja layanan setelah itu diaktifkan.

Inilah variasi demodi atas yang memanggil clients.claim() dalam peristiwa activate-nya. Anda seharusnya akan melihat gambar kucing untuk pertama kali. Saya katakan "seharusnya", karena ini adalah sesuatu yang sensitif terhadap waktu. Anda hanya akan melihat kucing jika pekerja layanan diaktifkan dan clients.claim() berlaku sebelum gambar berusaha dimuat.

Jika Anda menggunakan pekerja layanan untuk memuat halaman secara berbeda dengan halaman yang dimuat melalui jaringan, clients.claim() akan mengganggu, karena pekerja layanan Anda akan mengakhiri kontrol atas beberapa klien yang telah dimuat tanpa pekerja layanan.

Mengupdate pekerja layanan

Secara singkat:

  • Update dipicu:
    • Pada navigasi ke halaman dalam-cakupan.
    • Pada peristiwa fungsional seperti push dan sync, kecuali jika ada pemeriksaan update dalam 24 jam sebelumnya.
    • Pada pemanggilan .register() hanya jika URL pekerja layanan telah berubah.
  • Sebagian besar browser, termasuk Chrome 68 dan yang lebih baru, secara default mengabaikan pemeriksaan header ketika memeriksa update skrip pekerja layanan terdaftar. Browser masih menghargai header caching ketika mengambil resource yang dimuat di dalam pekerja layanan melalui importScripts(). Anda dapat mengganti perilaku default ini dengan mengatur opsi updateViaCache saat mendaftarkan pekerja layanan Anda.
  • Pekerja layanan Anda dianggap diupdate jika berbeda sedikit saja dengan pekerja layanan yang sudah dimiliki browser. (Kita memperluasnya dengan menyertakan juga skrip/modul yang telah diimpor.)
  • Pekerja layanan yang telah diupdate diluncurkan bersama yang sudah ada, dan mendapatkan peristiwa install-nya sendiri.
  • Jika pekerja layanan baru Anda memiliki kode status bukan OK (misalnya, 404), gagal menguraikan, membuang error selama eksekusi, atau ditolak selama penginstalan, pekerja layanan baru akan dibuang, namun yang ada saat ini akan tetap aktif.
  • Setelah berhasil diinstal, pekerja layanan yang telah diupdate akan wait hingga pekerja layanan yang ada tidak mengontrol klien sama sekali. (Perhatikan, klien akan tumpang tindih selama pemuatan ulang.)
  • self.skipWaiting() mencegah proses menunggu, yang berarti pekerja layanan akan diaktifkan begitu selesai diinstal.

Anggaplah 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'));
  }
});

Instal.

Perhatikan, saya telah mengubah nama cache dari static-v1 menjadi static-v2. Ini berarti saya bisa menyiapkan cache baru tanpa menimpa apa yang ada di cache saat ini, yang masih digunakan oleh pekerja layanan lama.

Pola ini akan membuat cache versi tertentu, semacam aset yang akan dibundel oleh aplikasi asli bersama file yang dapat dieksekusi. Anda mungkin juga memiliki cache yang bukan versi tertentu, misalnya avatars.

Setelah berhasil menginstalnya, pekerja layanan yang telah diupdate akan menunda aktivasi hingga pekerja layanan yang ada tidak lagi mengontrol klien. Keadaan ini disebut "menunggu", dan inilah cara browser memastikan bahwa hanya ada satu versi pekerja layanan yang berjalan dalam satu waktu.

Jika Anda menjalankan demo yang telah diupdate, Anda seharusnya tetap melihat gambar kucing, karena pekerja layanan V2 belum diaktifkan. Anda dapat melihat pekerja layanan baru menunggu di tab "Penerapan" pada DevTools:

DevTools menampilkan pekerja layanan baru yang sedang menunggu

Bahkan jika Anda hanya memiliki satu tab dibuka ke demo, pemuatan ulang halaman tidak cukup untuk memungkinkan versi baru mengambil alih. Ini berdasarkan bagaimana navigasi browser berfungsi. Jika Anda mengarahkan, halaman saat ini tidak akan hilang hingga header respons diterima, dan bahkan halaman saat ini mungkin tetap dibuka 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 arahkan semua tab dengan menggunakan pekerja layanan saat ini. Maka, jika Anda mengarahkan ke demo lagi, Anda akan melihat gambar kuda.

Pola ini serupa dengan cara update Chrome. Update pada hasil download Chrome dilatar belakang, namun tidak diterapkan hingga Chrome dimulai ulang. Pada saat ini, Anda bisa tetap menggunakan versi saat ini tanpa kendala. Akan tetapi, hal ini menjengkelkan selama pengembangan, namun DevTools memiliki cara untuk membuatnya lebih mudah, yang akan saya bahas nanti dalam artikel ini.

Aktifkan

Ini akan aktif setelah pekerja layanan yang lama hilang, dan pekerja layanan baru dapat mengontrol klien. Inilah waktu yang ideal untuk melakukan hal-hal yang tidak bisa Anda lakukan saat pekerja layanan lama sedang digunakan, misalnya melakukan migrasi database dan mengosongkan cache.

Dalam demo di atas, saya memelihara daftar cache yang saya harapkan akan ada, dan dalam peristiwa activate saya menghilangkan yang lainnya, yang akan membuang cache static-v1 lama.

Perhatian: Anda tidak boleh mengupdate dari versi sebelumnya. Pekerja layanan mungkin memiliki banyak versi lama.

Jika Anda meneruskan promise ke event.waitUntil() akan menjadi buffering peristiwa fungsional (fetch, push, sync dll.) hingga promise teratasi. Jadi, jika peristiwa fetch Anda dipicu, aktivasi akan selesai sepenuhnya.

Perhatian: Cache Storage API adalah "penyimpanan asal" (seperti localStorage, dan IndexedDB). Jika Anda menjalankan banyak situs pada asal yang sama (misalnya, yourname.github.io/myapp), berhati-hatilah agar Anda tidak menghapus cache untuk situs Anda yang lainnya. Untuk menghindarinya, berikan awalan yang unik pada nama cache Anda pada situs saat ini, misalnya myapp-static-v1, dan jangan sentuh cache kecuali jika memulai dengan myapp-.

Lewati tahap menunggu

Tahap menunggu berarti Anda hanya menjalankan satu versi situs saat itu, namun jika tidak membutuhkan fitur itu, Anda bisa mengaktifkan pekerja layanan baru lebih dini dengan memanggil self.skipWaiting().

Ini menyebabkan pekerja layanan Anda menyingkirkan pekerja layanan yang saat ini aktif dan mengaktifkannya sendiri begitu memasuki tahap menunggu (atau segera jika sudah dalam tahap menunggu). Ini tidak menyebabkan pekerja layanan Anda melewati penginstalan, hanya sedang menunggu.

Hal ini tidak begitu penting jika Anda memanggil skipWaiting(), asalkan pemanggilan dilakukan selama menunggu atau sebelum menunggu. Sudah cukup umum memanggilnya dalam peristiwa install:

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

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

Namun, Anda mungkin perlu memanggilnya sebagai hasil postMessage() ke pekerja layanan. Anda mungkin perlu interaksi pengguna berikut skipWaiting().

Inilah demo yang menggunakan skipWaiting(). Anda seharusnya akan melihat gambar sapi tanpa harus mengarahkan navigasi ke lain. Seperti clients.claim() menjadi sebuah pertandingan, jadi Anda hanya akan melihat sapi jika pekerja layanan baru mengambil, menginstal, dan mengaktifkan sebelum halaman berusaha memuat gambar.

Perhatian: skipWaiting() berarti pekerja layanan baru Anda mungkin saja mengontrol halaman yang telah dimuat bersama versi lama. Ini berarti sebagian pengambilan halaman akan ditangani oleh pekerja layanan yang lama, namun pekerja layanan baru akan menangani pengambilan selanjutnya. Jika ini akan merusak hal lain, jangan gunakan skipWaiting().

Update manual

Sebagaimana disebutkan sebelumnya, browser akan memeriksa update secara otomatis setelah peristiwa fungsional dan navigasi, namun Anda juga bisa memicunya secara manual:

navigator.serviceWorker.register('/sw.js').then(reg => {
  // sometime later…
  reg.update();
});

Jika Anda memperkirakan pengguna akan menggunakan situs dalam waktu lama tanpa memuat ulang, Anda mungkin perlu memanggil update() dengan interval (misalnya setiap jam).

Hindari mengubah URL skrip pekerja layanan

Jika Anda telah membaca postingan saya tentang praktik terbaik dalam cache, Anda dapat mempertimbangkan untuk memberikan setiap versi URL unik pekerja layanan Anda. Jangan lakukan ini! Ini biasanya praktik buruk bagi pekerja layanan, cukup update script di lokasi saat ini.

Ini akan menghadapkan Anda pada masalah seperti ini:

  1. index.html mendaftarkan sw-v1.js sebagai pekerja layanan.
  2. sw-v1.js men-cache and menayangkan index.html sehingga itu bekerja offline terlebih dahulu.
  3. Anda mengupdate index.html sehingga itu mendaftarkan sw-v2.js baru dan mengkilap Anda.

Jika Anda melakukan hal di atas, pengguna tidak akan mendapatkan sw-v2.js, karena sw-v1.js menyajikan versi lama index.html dari cache-nya. Anda menempatkan diri pada posisi di mana Anda perlu mengupdate pekerja layanan agar dapat mengupdatenya. Ew.

Akan tetapi, untuk demo di atas, saya telah mengubah URL pekerja layanan. Oleh karena itu, demi demo ini, Anda bisa beralih antar versi. Ini bukan sesuatu yang akan saya lakukan di produksi.

Memudahkan pengembangan

Daur hidup pekerja layanan dibuat dengan mempertimbangkan pengguna, namun selama pengembangan ini agak menjengkelkan. Syukurlah ada beberapa fitur untuk membantu:

Update saat memuat ulang

Ini adalah favorit saya.

DevTools menampilkan 'update pada pemuatan ulang'

Ini mengubah daur hidup menjadi mudah digunakan developer. Setiap navigasi akan:

  1. Mengambil ulang pekerja layanan.
  2. Menginstalnya sebagai versi baru meskipun itu byte-identik, artinya peristiwa install Anda berjalan dan cache Anda diupdate.
  3. Melewati fase menunggu sehingga pekerja layanan baru diaktifkan.
  4. Menavigasikan halaman. Ini berarti Anda akan mendapatkan update pada setiap navigasi (termasuk pemuatan ulang) tanpa harus memuat ulang atau menutup tab.

Lewati proses menunggu

DevTools menampilkan 'lewati proses menunggu'

Jika Anda memiliki pekerja layanan yang sedang menunggu, Anda bisa memilih "lewati proses menunggu" di DevTools untuk segera mengembangkannya menjadi "aktif".

Ganti muat ulang

Jika Anda memaksa muat ulang halaman (ganti muat ulang) ini akan melewati pekerja layanan sama sekali. Ini tidak akan dikontrol. Fitur ini ada dalam spesifikasi, sehingga akan berfungsi di browser lain yang mendukung pekerja layanan.

Menangani update

Pekerja layanan didesain sebagai bagian dari web yang dapat diperluas. Gagasannya adalah karena kita, sebagai developer browser, mengakui bahwa kita tidak lebih baik dalam hal pengembangan web dibandingkan developer web. Dan dengan demikian, kita seharusnya tidak menyediakan API tingkat tinggi sempit yang mengatasi masalah tertentu dengan menggunakan pola yang kita sukai, dan sebagai gantinya memberi Anda akses ke pusat browser dan memungkinkan Anda melakukannya sesuka hati, dengan cara yang terbaik bagi para pengguna Anda.

Jadi, untuk memungkinkan banyak pola sebisa mungkin, daur update keseluruhan 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.
});

Anda berhasil!

Fiuh! Ada banyak sekali teori teknis. Tetap ikuti dalam beberapa minggu ke depan karena kita akan mendalami beberapa penerapan praktis dari hal tersebut di atas.

Masukan

Was this page helpful?
Yes
What was the best thing about this page?
It helped me complete my goal(s)
Thank you for the feedback. If you have specific ideas on how to improve this page, please create an issue.
It had the information I needed
Thank you for the feedback. If you have specific ideas on how to improve this page, please create an issue.
It had accurate information
Thank you for the feedback. If you have specific ideas on how to improve this page, please create an issue.
It was easy to read
Thank you for the feedback. If you have specific ideas on how to improve this page, please create an issue.
Something else
Thank you for the feedback. If you have specific ideas on how to improve this page, please create an issue.
No
What was the worst thing about this page?
It didn't help me complete my goal(s)
Thank you for the feedback. If you have specific ideas on how to improve this page, please create an issue.
It was missing information I needed
Thank you for the feedback. If you have specific ideas on how to improve this page, please create an issue.
It had inaccurate information
Thank you for the feedback. If you have specific ideas on how to improve this page, please create an issue.
It was hard to read
Thank you for the feedback. If you have specific ideas on how to improve this page, please create an issue.
Something else
Thank you for the feedback. If you have specific ideas on how to improve this page, please create an issue.