Threading web dengan pekerja modul

Memindahkan pekerjaan berat ke thread latar belakang kini lebih mudah dengan modul JavaScript di pekerja web.

JavaScript adalah thread tunggal, yang berarti JavaScript hanya dapat melakukan satu operasi pada satu waktu. Ini adalah intuitif dan berfungsi dengan baik untuk banyak kasus di web, tetapi dapat menjadi masalah ketika kita harus melakukan tugas-tugas yang berat seperti pemrosesan data, penguraian, komputasi, atau analisis. Seiring dengan semakin banyaknya waktu aplikasi yang kompleks disajikan di web, maka ada peningkatan kebutuhan akan diproses.

Di platform web, primitif utama untuk threading dan paralelisme adalah Workers API. Pekerja merupakan abstraksi ringan di atas sistem operasi thread yang mengekspos pesan yang meneruskan API untuk komunikasi antar-thread. Fungsi ini dapat sangat berguna saat melakukan komputasi yang mahal atau beroperasi pada set data besar, sehingga memungkinkan thread utama berjalan lancar saat melakukan operasi yang mahal pada satu atau beberapa thread latar belakang.

Berikut adalah contoh umum penggunaan pekerja, dengan skrip pekerja yang mendengarkan pesan dari layanan utama rangkaian pesan dan merespons dengan mengirimkan kembali pesannya sendiri:

page.js:

const worker = new Worker('worker.js');
worker.addEventListener('message', e => {
  console.log(e.data);
});
worker.postMessage('hello');

worker.js:

addEventListener('message', e => {
  if (e.data === 'hello') {
    postMessage('world');
  }
});

Web Worker API telah tersedia di sebagian besar browser selama lebih dari sepuluh tahun. Meskipun hal tersebut berarti pekerja memiliki dukungan browser yang sangat baik dan dioptimalkan dengan baik, itu juga berarti mereka lama sebelum ada modul JavaScript. Karena tidak ada sistem modul saat pekerja dirancang, API untuk memuat kode ke dalam worker dan menulis skrip tetap serupa dengan skrip sinkron pemuatan yang umum dilakukan pada tahun 2009.

Histori: pekerja klasik

Konstruktor Worker mengambil klasik skrip Anda, yang merupakan relatif terhadap URL dokumen. Metode ini segera menampilkan referensi ke instance pekerja baru, yang mengekspos antarmuka pesan serta metode terminate() yang langsung menghentikan dan menghancurkan pekerja itu.

const worker = new Worker('worker.js');

Fungsi importScripts() tersedia dalam pekerja web untuk memuat kode tambahan, tetapi menjeda eksekusi pekerja untuk mengambil dan mengevaluasi setiap skrip. Cloud SQL juga menjalankan skrip dalam cakupan global seperti tag <script> klasik. Artinya, variabel dalam satu skrip dapat oleh variabel lain.

worker.js:

importScripts('greet.js');
// ^ could block for seconds
addEventListener('message', e => {
  postMessage(sayHello());
});

greet.js:

// global to the whole worker
function sayHello() {
  return 'world';
}

Karena alasan ini, pekerja web secara historis telah memberlakukan efek besar pada arsitektur aplikasi. Pengembang harus membuat alat dan solusi yang cerdas untuk memungkinkan menggunakan pekerja web tanpa meninggalkan praktik pengembangan modern. Sebagai contoh, pemaket seperti webpack menyematkan implementasi loader modul kecil ke dalam kode yang dihasilkan yang menggunakan importScripts() untuk pemuatan kode, tetapi menggabungkan modul dalam fungsi untuk menghindari konflik variabel dan menyimulasikan ekspor dan impor dependensi.

Masukkan worker modul

Mode baru untuk pekerja web dengan manfaat ergonomi dan performa JavaScript modul diluncurkan di Chrome 80, yang disebut worker modul. Tujuan Konstruktor Worker kini menerima opsi {type:"module"} baru, yang mengubah pemuatan skrip dan eksekusi agar cocok dengan <script type="module">.

const worker = new Worker('worker.js', {
  type: 'module'
});

Karena pekerja modul merupakan modul JavaScript standar, mereka dapat menggunakan pernyataan impor dan ekspor. Sebagai dengan semua modul JavaScript, dependensi hanya dijalankan sekali worker, dll.), dan semua impor mendatang merujuk ke instance modul yang sudah dieksekusi. Pemuatan dan eksekusi modul JavaScript juga dioptimalkan oleh browser. Dependensi modul dapat dimuat sebelum modul dieksekusi, yang memungkinkan seluruh hierarki modul dimuat paralel. Pemuatan modul juga menyimpan kode yang diuraikan dalam cache, yang berarti modul yang digunakan di jaringan utama thread dan di worker hanya perlu diurai sekali.

Peralihan ke modul JavaScript juga memungkinkan penggunaan modul dinamis impor untuk kode pemuatan lambat tanpa memblokir eksekusi pekerja tersebut. Impor dinamis jauh lebih eksplisit daripada menggunakan importScripts() untuk memuat dependensi, karena ekspor modul yang diimpor ditampilkan, bukan mengandalkan variabel global.

worker.js:

import { sayHello } from './greet.js';
addEventListener('message', e => {
  postMessage(sayHello());
});

greet.js:

import greetings from './data.js';
export function sayHello() {
  return greetings.hello;
}

Untuk memastikan performa yang baik, metode importScripts() lama tidak tersedia dalam modul pekerja. Mengalihkan pekerja untuk menggunakan modul JavaScript berarti semua kode dimuat di ketat mode. Lainnya perubahan penting adalah nilai this di cakupan tingkat atas modul JavaScript undefined, sedangkan pada pekerja klasik, nilainya adalah cakupan global pekerja. Untungnya, ada selalu merupakan self global yang menyediakan referensi ke cakupan global. Tersedia dalam bahasa semua jenis pekerja, termasuk pekerja layanan, serta di DOM.

Pramuat pekerja dengan modulepreload

Salah satu peningkatan performa penting yang menyertai pekerja modul adalah kemampuan untuk melakukan pramuat pekerja dan ketergantungan mereka. Dengan pekerja modul, skrip dimuat dan dieksekusi sebagai standar Modul JavaScript, yang berarti dapat dipramuat dan bahkan diurai menggunakan modulepreload:

<!-- preloads worker.js and its dependencies: -->
<link rel="modulepreload" href="worker.js">

<script>
  addEventListener('load', () => {
    // our worker code is likely already parsed and ready to execute!
    const worker = new Worker('worker.js', { type: 'module' });
  });
</script>

Modul pramuat juga dapat digunakan oleh thread utama dan pekerja modul. Hal ini berguna untuk modul yang diimpor dalam kedua konteks, atau jika tidak mungkin mengetahui sebelumnya apakah modul akan digunakan di thread utama atau di pekerja.

Sebelumnya, opsi yang tersedia untuk pramuat skrip pekerja web terbatas dan tidak dapat diandalkan. Pekerja klasik memiliki "pekerja" mereka sendiri jenis resource untuk pramuat, tetapi tidak browser menerapkan <link rel="preload" as="worker">. Oleh karena itu, teknik utama yang tersedia untuk pramuat pekerja web adalah menggunakan <link rel="prefetch">, yang sepenuhnya mengandalkan pada cache HTTP. Saat digunakan bersama dengan header {i>caching<i} yang benar, metode ini memungkinkan agar pembuatan instance pekerja tidak perlu menunggu untuk mendownload skrip pekerja. Namun, tidak seperti modulepreload teknik ini tidak mendukung pramuat dependensi atau pra-penguraian.

Bagaimana dengan pekerja bersama?

Pekerja bersama memiliki telah diupdate dengan dukungan untuk modul JavaScript mulai Chrome 83. Seperti pekerja yang berdedikasi, membuat pekerja bersama dengan opsi {type:"module"} kini memuat skrip pekerja sebagai bukan skrip klasik:

const worker = new SharedWorker('/worker.js', {
  type: 'module'
});

Sebelum mendukung modul JavaScript, konstruktor SharedWorker() hanya mengharapkan sebuah URL dan argumen name opsional. Fitur ini akan terus berfungsi untuk penggunaan pekerja bersama klasik; namun pembuatan pekerja bersama modul memerlukan penggunaan argumen options baru. Properti tersedia opsi sama dengan pekerja khusus, termasuk opsi name yang menggantikan argumen name sebelumnya.

Bagaimana dengan pekerja layanan?

Spesifikasi pekerja layanan telah diperbarui untuk mendukung persetujuan modul JavaScript sebagai titik entri, dengan opsi {type:"module"} yang sama seperti worker modul, namun perubahan ini belum diterapkan di browser. Setelah itu terjadi, akan ada kemungkinan untuk membuat instance pekerja layanan menggunakan modul JavaScript menggunakan kode berikut:

navigator.serviceWorker.register('/sw.js', {
  type: 'module'
});

Setelah spesifikasi diupdate, browser mulai menerapkan perilaku baru. Proses ini membutuhkan waktu karena ada beberapa detail tambahan yang terkait dengan menghadirkan JavaScript modul ke pekerja layanan. Pendaftaran pekerja layanan perlu membandingkan skrip yang diimpor dengan versi cache sebelumnya menentukan apakah akan memicu update, dan hal ini perlu diterapkan untuk modul JavaScript ketika digunakan untuk pekerja layanan. Selain itu, pekerja layanan harus dapat mengabaikan cache untuk skrip dalam kasus tertentu saat memeriksa pembaruan.

Referensi tambahan dan bacaan lebih lanjut