Siklus proses pekerja layanan

Jake Archibald
Jake Archibald

Siklus proses pekerja layanan adalah bagian yang paling rumit. Jika Anda tidak tahu apa yang harus dilakukan dan apa manfaatnya, hal ini bisa terasa berat bagi Anda. Namun setelah mengetahui cara kerjanya, Anda bisa memberikan update yang mulus dan tidak mengganggu kepada pengguna, dengan memadukan yang terbaik dari web dan pola native.

Ini adalah pembahasan mendalam, tetapi poin-poin di awal setiap bagian membahas sebagian besar hal yang perlu Anda ketahui.

Intent

Intent siklus proses adalah untuk:

  • Memungkinkan offline-first.
  • Memungkinkan pekerja layanan baru menyiapkan dirinya sendiri tanpa mengganggu pekerja layanan saat ini.
  • Pastikan halaman dalam cakupan dikontrol oleh pekerja layanan yang sama (atau tanpa pekerja layanan) seluruhnya.
  • Pastikan hanya ada satu versi untuk situs Anda yang dijalankan pada satu waktu.

Yang terakhir ini cukup penting. Tanpa pekerja layanan, pengguna dapat memuat satu tab ke situs Anda, lalu membuka tab lain nanti. Hal ini dapat mengakibatkan dua versi situs Anda dijalankan secara bersamaan. Terkadang hal ini tidak menjadi masalah, tetapi jika Anda berurusan dengan penyimpanan, Anda akan dengan mudah mendapati dua tab memiliki opini yang sangat berbeda tentang bagaimana seharusnya penyimpanan bersama keduanya dikelola. Hal ini dapat mengakibatkan error, atau lebih buruk lagi, kehilangan data.

Pekerja layanan pertama

Singkatnya:

  • Peristiwa install adalah peristiwa pertama yang didapatkan pekerja layanan, dan hanya terjadi sekali.
  • Promise yang diteruskan ke installEvent.waitUntil() akan menunjukkan durasi serta keberhasilan atau kegagalan penginstalan Anda.
  • 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 harus memuat ulang halaman untuk melihat efek pekerja layanan.
  • clients.claim() dapat mengganti setelan default ini dan mengontrol halaman yang tidak dikontrol.

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

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

Kucing ini menyimpan gambar kucing dalam cache dan menayangkannya setiap kali ada permintaan untuk /dog.svg. Namun, jika menjalankan contoh di atas, Anda akan melihat seekor saat pertama kali memuat halaman. Tekan refresh, dan Anda akan melihat gambar kucing tersebut.

Ruang lingkup dan kontrol

Cakupan default pendaftaran pekerja layanan adalah ./ relatif terhadap URL skrip. Artinya, jika Anda mendaftarkan pekerja layanan di //example.com/foo/bar.js, cakupan default-nya adalah //example.com/foo/.

Kami menyebut halaman, pekerja, dan pekerja bersama sebagai clients. Pekerja layanan hanya bisa 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 berupa null atau instance pekerja layanan.

Download, urai, dan jalankan

Pekerja layanan pertama akan didownload saat Anda memanggil .register(). Jika skrip Anda gagal mendownload, mengurai, atau menampilkan error dalam eksekusi awal, 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

Menginstal

Peristiwa pertama yang didapatkan pekerja layanan adalah install. Ini 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 pembaruan secara mendetail nanti.

Peristiwa install adalah kesempatan Anda untuk meng-cache semua yang Anda butuhkan 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 membuang pekerja layanan. Itu tidak akan pernah mengontrol klien. Ini berarti kita tidak dapat mengandalkan cat.svg yang ada di cache dalam peristiwa fetch. Ini adalah dependensi.

Aktifkan

Setelah pekerja layanan 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 kali Anda memuat demo, meskipun dog.svg diminta lama setelah pekerja layanan aktif, demo tersebut tidak menangani permintaan, dan Anda masih dapat melihat gambar anjingnya. Defaultnya adalah konsistensi. Jika halaman Anda dimuat tanpa pekerja layanan, subresource-nya juga tidak akan. Jika Anda memuat demo untuk kedua kalinya (dengan kata lain, memuat ulang halaman), demo tersebut akan dikontrol. Halaman dan gambar akan melalui peristiwa fetch, dan Anda akan melihat kucing sebagai gantinya.

clients.claim

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

Berikut adalah variasi demo di atas yang memanggil clients.claim() dalam peristiwa activate. Anda seharusnya melihat kucing untuk pertama kali. Saya katakan "seharusnya", karena ini adalah hal yang sensitif terhadap waktu. Anda hanya akan melihat kucing jika pekerja layanan diaktifkan dan clients.claim() berlaku sebelum gambar mencoba dimuat.

Jika Anda menggunakan pekerja layanan untuk memuat halaman secara berbeda dengan halaman yang dimuat melalui jaringan, clients.claim() dapat menjadi masalah, karena pekerja layanan Anda akan mengontrol beberapa klien yang dimuat tanpa pekerja layanan tersebut.

Mengupdate pekerja layanan

Singkatnya:

  • Update akan dipicu jika salah satu dari hal berikut terjadi:
    • Navigasi ke halaman dalam cakupan.
    • Peristiwa fungsional seperti push dan sync, kecuali jika ada pemeriksaan pembaruan dalam 24 jam sebelumnya.
    • Memanggil .register() hanya jika URL pekerja layanan telah berubah. Namun, Anda harus menghindari mengubah URL pekerja.
  • Sebagian besar browser, termasuk Chrome 68 dan yang lebih baru, secara default mengabaikan header penyimpanan cache saat memeriksa update skrip pekerja layanan yang terdaftar. Fungsi tersebut masih mematuhi header penyimpanan cache saat mengambil resource yang dimuat di dalam pekerja layanan melalui importScripts(). Anda dapat mengganti perilaku default ini dengan menyetel 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 untuk menyertakan skrip/modul yang diimpor juga.)
  • Pekerja layanan yang diupdate diluncurkan bersama yang sudah ada, dan mendapatkan peristiwa install-nya sendiri.
  • Jika pekerja baru Anda memiliki kode status bukan OK (misalnya, 404), gagal diurai, menampilkan error selama eksekusi, atau ditolak selama penginstalan, pekerja baru akan dibuang, tetapi pekerja yang ada saat ini akan tetap aktif.
  • Setelah berhasil diinstal, pekerja yang diupdate akan wait hingga pekerja yang ada mengontrol nol klien. (Perhatikan bahwa klien akan 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 seharusnya masih melihat gambar kucing. Inilah alasannya...

Menginstal

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

Pola ini membuat cache versi tertentu, mirip dengan aset yang akan dipaketkan oleh aplikasi native dengan file yang dapat dieksekusi. Anda mungkin juga memiliki cache yang bukan versi spesifik, misalnya avatars.

Waiting

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

Jika menjalankan demo yang diperbarui, Anda akan tetap melihat gambar kucing, karena pekerja V2 belum diaktifkan. Anda bisa melihat pekerja layanan baru menunggu di tab "Application" pada DevTools:

DevTools menampilkan pekerja layanan baru yang sedang menunggu

Meskipun Anda hanya memiliki satu tab terbuka untuk demo, memuat ulang halaman tidak cukup untuk memungkinkan versi baru mengambil alih. Hal ini disebabkan oleh cara kerja navigasi browser. Saat Anda menavigasi, halaman saat ini tidak akan hilang hingga header respons diterima, dan meskipun halaman saat ini dapat 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 menggunakan pekerja layanan saat ini. Kemudian, saat membuka demo lagi, Anda akan melihat gambar kuda.

Pola ini mirip dengan cara update Chrome. Update pada download Chrome di latar belakang, namun tidak diterapkan hingga Chrome dimulai ulang. Pada saat ini, Anda dapat terus menggunakan versi saat ini tanpa gangguan. Namun, hal ini menjengkelkan selama pengembangan, tetapi 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 dapat Anda lakukan saat pekerja lama masih digunakan, seperti memigrasikan database dan menghapus cache.

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

Jika Anda meneruskan promise ke event.waitUntil(), promise tersebut akan menyangga peristiwa fungsional (fetch, push, sync, dll.) hingga promise di-resolve. Jadi, jika peristiwa fetch Anda diaktifkan, aktivasi akan selesai sepenuhnya.

Lewati tahap menunggu

Tahap menunggu berarti Anda hanya menjalankan satu versi situs sekaligus, namun jika tidak memerlukan fitur tersebut, Anda dapat mengaktifkan pekerja layanan baru lebih cepat dengan memanggil self.skipWaiting().

Hal ini menyebabkan pekerja layanan menyingkirkan pekerja aktif saat ini dan mengaktifkan dirinya sendiri segera setelah memasuki fase menunggu (atau segera jika sudah dalam tahap menunggu). Ini tidak menyebabkan pekerja Anda melewati penginstalan, hanya sedang menunggu.

Tidak masalah jika Anda menelepon skipWaiting(), selama panggilan tersebut dilakukan selama menunggu atau sebelum menunggu. Sudah cukup umum untuk 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. Seperti misalnya, Anda ingin skipWaiting() mengikuti interaksi pengguna.

Berikut adalah demo yang menggunakan skipWaiting(). Anda akan melihat gambar sapi tanpa harus menavigasi keluar. Seperti clients.claim(), ini adalah sebuah balapan, jadi Anda hanya akan melihat sapi jika pekerja layanan baru mengambil, menginstal, dan mengaktifkan sebelum halaman mencoba memuat gambar.

Pembaruan manual

Seperti yang saya sebutkan sebelumnya, browser akan 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 dalam waktu lama tanpa memuat ulang, sebaiknya panggil update() pada interval (misalnya setiap jam).

Hindari mengubah URL skrip pekerja layanan

Jika Anda telah membaca postingan saya tentang praktik terbaik penyimpanan cache, Anda dapat mempertimbangkan untuk memberikan URL unik ke setiap versi pekerja layanan Anda. Jangan lakukan ini! Ini biasanya merupakan praktik yang buruk bagi pekerja layanan, cukup perbarui skrip di lokasinya saat ini.

Hal ini bisa mengarahkan Anda pada masalah seperti ini:

  1. index.html mendaftarkan sw-v1.js sebagai pekerja layanan.
  2. sw-v1.js men-cache dan menayangkan index.html sehingga berfungsi secara offline-first.
  3. Anda mengupdate index.html agar mendaftarkan sw-v2.js baru dan menarik.

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

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

Mempermudah pengembangan

Siklus proses pekerja layanan dibuat dengan mempertimbangkan pengguna, namun selama pengembangan ini agak menyakitkan. Untungnya ada beberapa alat untuk membantu:

Update saat memuat ulang

Yang ini adalah favorit saya.

DevTools menampilkan &#39;update saat dimuat ulang&#39;

Hal ini mengubah siklus proses agar mudah digunakan oleh developer. Setiap navigasi akan:

  1. Mengambil ulang pekerja layanan.
  2. Instal sebagai versi baru meskipun versi byte-nya identik, yang berarti peristiwa install Anda berjalan dan cache Anda diupdate.
  3. Lewati fase menunggu sehingga pekerja layanan baru diaktifkan.
  4. Buka halaman.

Artinya, Anda akan mendapatkan update pada setiap navigasi (termasuk refresh) tanpa harus memuat ulang dua kali atau menutup tab.

Lewati proses menunggu

DevTools menampilkan &#39;lewati proses menunggu&#39;

Jika ada pekerja yang sedang menunggu, Anda bisa menekan "lewati waktu tunggu" di DevTools untuk segera mempromosikannya menjadi "aktif".

Muat ulang shift

Jika Anda memuat ulang secara paksa halaman (ganti muat ulang), ini akan mengabaikan pekerja layanan sepenuhnya. Ini tidak akan terkendali. Fitur ini sesuai spesifikasi, jadi berfungsi di browser lain yang mendukung pekerja layanan.

Menangani update

Pekerja layanan dirancang sebagai bagian dari web yang dapat diperluas. Idenya adalah kita, sebagai developer browser, mengakui bahwa kita tidak lebih baik dalam hal pengembangan web dibandingkan dengan developer web. Dengan demikian, kita tidak boleh menyediakan API tingkat tinggi sempit yang memecahkan masalah tertentu menggunakan pola yang kita sukai, dan justru memberi Anda akses ke isi browser dan memungkinkan Anda melakukannya sesuka hati, dengan cara yang paling sesuai bagi 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 akan terus berlanjut

Seperti yang Anda lihat, ada manfaatnya untuk memahami siklus hidup pekerja layanan—dan dengan pemahaman itu, perilaku pekerja layanan seharusnya tampak lebih logis, dan tidak terlalu misterius. Pengetahuan tersebut akan membuat Anda lebih percaya diri saat men-deploy dan mengupdate pekerja layanan.