Siklus proses pekerja layanan

Jake Archibald
Jake Archibald

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

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

Niat

Tujuan dari siklus proses adalah untuk:

  • Jadikan offline-first.
  • 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) secara keseluruhan.
  • Pastikan hanya ada satu versi situs yang dijalankan pada saat yang sama.

Yang terakhir adalah hal yang penting. Tanpa pekerja layanan, pengguna dapat memuat satu tab ke situs Anda, kemudian membuka tab lain nanti. Hal ini dapat mengakibatkan dua versi situs Anda dijalankan secara bersamaan. Terkadang hal tersebut tidak menjadi masalah, tetapi jika Anda sedang berurusan dengan penyimpanan, Anda dapat dengan mudah mendapati adanya dua tab yang memiliki pendapat berbeda tentang cara pengelolaan penyimpanan bersama di keduanya. Hal ini dapat mengakibatkan {i>error<i}, atau lebih buruk lagi, kehilangan data.

Pekerja layanan pertama

Singkatnya:

  • Peristiwa install adalah peristiwa pertama yang diambil pekerja layanan, dan hanya terjadi sekali.
  • Promise yang diteruskan ke installEvent.waitUntil() menunjukkan durasi serta keberhasilan atau kegagalan penginstalan.
  • Pekerja layanan tidak akan menerima peristiwa seperti fetch dan push sampai 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 default ini dan mengambil alih 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>

Fungsi ini mendaftarkan pekerja layanan, dan menambahkan gambar setelah 3 detik.

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

Layanan ini menyimpan cache gambar kucing, dan menayangkannya setiap kali ada permintaan untuk /dog.svg. Namun, jika menjalankan contoh di atas, Anda akan melihat gambar saat pertama kali memuat halaman. Tekan muat ulang, 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, pekerja layanan tersebut akan memiliki cakupan default //example.com/foo/.

Kami 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 mengeksekusi

Pekerja layanan pertama Anda akan didownload saat Anda memanggil .register(). Jika skrip Anda gagal mendownload, mengurai, atau menampilkan error dalam eksekusi awalnya, promise register akan menolak, dan pekerja layanan akan dibuang.

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

Error ditampilkan di tab DevTools pekerja layanan

Menginstal

Peristiwa pertama yang diambil pekerja layanan adalah install. Ini dipicu segera setelah pekerja 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-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() memberi tahu browser kapan penginstalan Anda selesai, dan apakah penginstalan berhasil.

Jika promise Anda ditolak, ini menandakan bahwa penginstalan gagal, dan browser akan membuang pekerja layanan. Asisten Google tidak akan pernah mengontrol klien. Ini berarti kita dapat mengandalkan cat.svg yang ada dalam cache pada 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, bukan berarti halaman yang bernama .register() akan dikontrol.

Saat pertama kali Anda memuat demo, meskipun dog.svg diminta lama setelah pekerja layanan diaktifkan, pekerja layanan tidak akan menangani permintaan, dan Anda masih melihat gambar anjingnya. Defaultnya adalah konsistensi, jika halaman Anda dimuat tanpa pekerja layanan, subresource-nya juga tidak akan ada. Jika Anda memuat demo untuk kedua kalinya (dengan kata lain, memuat ulang halaman), hal itu akan dikontrol. Halaman dan gambar akan melewati peristiwa fetch, dan Anda akan melihat kucing.

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 bilang "harus", karena 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 dengan cara yang berbeda dari yang dimuat melalui jaringan, clients.claim() dapat menyusahkan, karena pekerja layanan Anda pada akhirnya akan mengontrol beberapa klien yang dimuat tanpa pekerja layanan tersebut.

Mengupdate pekerja layanan

Singkatnya:

  • Update 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, Anda harus menghindari mengubah URL pekerja.
  • Sebagian besar browser, termasuk Chrome 68 dan yang lebih baru, secara default akan mengabaikan header penyimpanan dalam cache saat memeriksa update skrip pekerja layanan terdaftar. Mesin ini masih mematuhi header penyimpanan dalam 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 yang sudah dimiliki browser. (Kita memperluasnya untuk menyertakan juga skrip/modul yang diimpor.)
  • Pekerja layanan yang diupdate diluncurkan bersama dengan yang sudah ada, dan mendapatkan peristiwa install-nya sendiri.
  • Jika pekerja baru Anda memiliki kode status bukan OK (misalnya, 404), gagal mengurai, menampilkan error selama eksekusi, atau menolak selama penginstalan, worker baru akan dihapus, tetapi yang saat ini digunakan akan tetap aktif.
  • Setelah berhasil diinstal, pekerja yang diupdate akan melakukan wait hingga pekerja layanan yang ada mengontrol nol klien. (Perhatikan bahwa klien tumpang-tindih selama pembaruan.)
  • self.skipWaiting() mencegah waktu tunggu, yang berarti pekerja layanan akan diaktifkan segera setelah selesai diinstal.

Katakanlah 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 tetap akan melihat gambar kucing. Alasannya...

Menginstal

Perlu diperhatikan bahwa saya telah mengubah nama cache dari static-v1 menjadi static-v2. Artinya, saya dapat menyiapkan cache baru tanpa menimpa hal-hal yang ada di cache saat ini, yang masih digunakan oleh pekerja layanan lama.

Pola ini membuat cache khusus versi, serupa dengan aset yang akan dipaketkan oleh aplikasi native bersama file yang dapat dieksekusi. Anda mungkin juga memiliki cache yang tidak bersifat spesifik per versi, seperti 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 satu versi pekerja layanan yang berjalan pada satu waktu.

Jika menjalankan demo yang diupdate, Anda masih akan 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 yang terbuka untuk demo, memuat ulang halaman saja tidak cukup untuk membiarkan versi baru mengambil alih demo. Hal ini disebabkan oleh cara kerja navigasi browser. Saat Anda menavigasi, halaman saat ini tidak akan hilang hingga header respons diterima, dan 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 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. 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

Layanan ini akan aktif setelah pekerja layanan yang lama hilang, dan pekerja layanan baru Anda 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 mempertahankan daftar cache yang diharapkan ada di sana, dan dalam peristiwa activate saya menghapus yang lain, yang menghapus cache static-v1 lama.

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

Lewati fase menunggu

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

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

Hal ini tidak terlalu penting saat Anda memanggil skipWaiting(), selama hal tersebut berlangsung selama atau sebelum menunggu. Cukup umum untuk memanggilnya di peristiwa install:

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

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

Namun, Anda mungkin ingin memanggilnya sebagai hasil dari postMessage() ke pekerja layanan. Seperti, Anda ingin skipWaiting() mengikuti interaksi pengguna.

Berikut adalah demo yang menggunakan skipWaiting(). Anda akan melihat gambar sapi tanpa harus mengarahkan kursor. 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 peristiwa fungsional dan navigasi, 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() dengan interval (misalnya setiap jam).

Hindari mengubah URL skrip pekerja layanan Anda

Jika Anda telah membaca postingan saya tentang praktik terbaik penyimpanan cache, Anda dapat mempertimbangkan untuk memberikan URL unik kepada 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 itu dapat membawa Anda pada masalah seperti ini:

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

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

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

Memudahkan pengembangan

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

Update saat memuat ulang

Ini adalah favorit saya.

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

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

  1. Mengambil kembali pekerja layanan.
  2. Instal sebagai versi baru meskipun byte identik, yang berarti peristiwa install akan berjalan dan cache Anda akan diperbarui.
  3. Melewati fase menunggu sehingga pekerja layanan baru diaktifkan.
  4. Buka halaman.

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

Lewati proses menunggu

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

Jika Anda memiliki pekerja yang sedang menunggu, Anda dapat memilih "lewati waktu menunggu" di DevTools untuk segera mempromosikannya menjadi "active".

Geser pemuatan ulang

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

Menangani update

Pekerja layanan didesain sebagai bagian dari web yang dapat diperluas. Idenya adalah bahwa kami, sebagai developer browser, mengakui bahwa kami tidak lebih baik dalam pengembangan web dibandingkan developer web. Dengan demikian, kita seharusnya tidak menyediakan API tingkat tinggi yang sempit untuk memecahkan masalah tertentu menggunakan pola yang kami sukai, dan sebagai gantinya memberi Anda akses ke isi browser dan memungkinkan Anda melakukannya sesuai keinginan, 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 terus berjalan

Seperti yang Anda lihat, ada baiknya untuk memahami siklus proses pekerja layanan—dan dengan pemahaman tersebut, 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.