Siklus proses pekerja layanan

Jake Archibald
Jake Archibald

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.

Intent

Intent dari siklus proses adalah untuk:

  • Memungkinkan offline-terlebih dahulu.
  • 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 untuk situs Anda yang dijalankan pada satu waktu.

Poin terakhir merupakan hal yang sangat 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 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 dapat mengakibatkan error, atau lebih buruk lagi, kehilangan data.

Pekerja layanan pertama

Singkatnya:

  • Peristiwa install adalah peristiwa pertama yang diambil pekerja layanan, dan ini hanya terjadi sekali.
  • Promise yang diteruskan ke installEvent.waitUntil() 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 efek pekerja layanan.
  • clients.claim() dapat mengganti default ini, dan mengambil kontrol atas 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>

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

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

Cakupan dan kontrol

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

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

Mendownload, mengurai, dan menjalankan

Pekerja layanan pertama Anda akan didownload saat Anda memanggil .register(). Jika skrip Anda gagal mendownload, menguraikan, atau menampilkan 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 pembaruan secara mendetail nanti.

Peristiwa install adalah kesempatan Anda untuk menyimpan cache semua 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 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, ia tidak menangani permintaan tersebut, dan Anda tetap melihat gambar. 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, 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-nya. Anda akan melihat kucing untuk pertama kalinya. 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() dapat menjadi masalah, karena pekerja layanan Anda akan mengontrol beberapa klien yang dimuat tanpa pekerja layanan.

Memperbarui pekerja layanan

Singkatnya:

  • Pembaruan dipicu jika salah satu hal berikut terjadi:
    • Navigasi ke halaman dalam cakupan.
    • Peristiwa fungsional seperti push dan sync, kecuali jika ada pemeriksaan update dalam 24 jam sebelumnya.
    • Memanggil .register() hanya jika URL pekerja layanan telah berubah. Namun, sebaiknya hindari mengubah URL pekerja.
  • Sebagian besar browser, termasuk Chrome 68 dan yang lebih baru, secara default mengabaikan pemeriksaan header saat 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 menetapkan 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 diperbarui 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'));
  }
});

Lihat demo di atas. Anda seharusnya tetap melihat gambar kucing. Berikut alasannya…

Instal

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 akan membuat cache versi tertentu, semacam aset yang akan dibundel oleh aplikasi native bersama file yang dapat dieksekusi. Anda mungkin juga memiliki cache yang bukan versi tertentu, seperti avatars.

Menunggu

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 telah diperbarui, Anda seharusnya tetap melihat gambar kucing, karena pekerja layanan V2 belum diaktifkan. Anda dapat melihat pekerja layanan baru menunggu di tab "Application" pada DevTools:

DevTools menampilkan pekerja layanan baru yang sedang menunggu

Meskipun Anda hanya memiliki satu tab yang dibuka ke demo, memuat ulang halaman tidak cukup untuk memungkinkan versi baru mengambil alih. Hal ini berdasarkan cara kerja navigasi browser. 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. Kemudian, saat membuka demo lagi, Anda akan melihat gambar kuda.

Pola ini serupa dengan cara update Chrome. Update pada hasil download Chrome dilatar belakang, tetapi tidak diterapkan hingga Chrome dimulai ulang. Sementara itu, 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 layanan lama masih digunakan, seperti memigrasikan 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 menghapus cache static-v1 lama.

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

Melewati fase menunggu

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

Hal 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). Hal ini tidak menyebabkan pekerja Anda melewati penginstalan, hanya 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 skipWaiting() interaksi pengguna berikut.

Berikut adalah demo yang menggunakan skipWaiting(). Anda seharusnya akan melihat gambar sapi tanpa harus mengarahkan navigasi ke lain. 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

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 URL unik ke setiap versi pekerja layanan. Jangan lakukan ini! Ini biasanya adalah kebiasaan buruk untuk pekerja layanan, cukup perbarui skrip di lokasi saat ini.

Hal ini dapat menyebabkan masalah seperti ini:

  1. index.html mendaftarkan sw-v1.js sebagai pekerja layanan.
  2. sw-v1.js menyimpan ke cache dan menayangkan index.html sehingga berfungsi secara offline terlebih dahulu.
  3. Anda mengupdate index.html sehingga mendaftarkan sw-v2.js baru dan mengkilap.

Jika Anda melakukan hal di atas, pengguna tidak akan 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 mengupdatenya. Iiihh.

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

Daur hidup pekerja layanan dibuat dengan mempertimbangkan pengguna, tetapi selama pengembangan ini agak menjengkelkan. Untungnya, ada beberapa alat untuk membantu:

Memperbarui saat memuat ulang

Ini adalah favorit saya.

DevTools menampilkan &#39;update on reload&#39;

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

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

Ini berarti Anda akan mendapatkan update pada setiap navigasi (termasuk pemuatan ulang) tanpa harus memuat ulang atau menutup tab.

Melewati proses menunggu

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

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

Shift-muat ulang

Jika Anda memaksa muat ulang halaman (muat ulang geser), tindakan 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. 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.
});

Siklus proses terus berlanjut

Seperti yang dapat 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 membuat Anda lebih percaya diri saat men-deploy dan mengupdate pekerja layanan.