Sekarang, memindahkan tugas berat ke thread latar belakang lebih mudah dilakukan dengan modul JavaScript di pekerja web.
JavaScript adalah bahasa pemrograman single-threaded, yang berarti hanya dapat melakukan satu operasi dalam satu waktu. Hal ini intuitif dan berfungsi dengan baik untuk banyak kasus di web, tetapi dapat menjadi masalah saat kita perlu melakukan tugas berat seperti pemrosesan, penguraian, penghitungan, atau analisis data. Seiring dengan semakin banyaknya aplikasi kompleks yang ditayangkan di web, kebutuhan akan pemrosesan multi-thread meningkat.
Di platform web, primitif utama untuk threading dan paralelisme adalah Web Workers API. Worker adalah abstraksi ringan di atas thread sistem operasi yang mengekspos API penerusan pesan untuk komunikasi antar-thread. Hal ini dapat sangat berguna saat melakukan komputasi yang mahal atau mengoperasikan 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, di mana skrip pekerja memproses pesan dari thread utama 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 berarti pekerja memiliki dukungan browser yang sangat baik dan dioptimalkan dengan baik, hal ini juga berarti pekerja sudah ada jauh sebelum modul JavaScript. Karena tidak ada sistem modul saat pekerja didesain, API untuk memuat kode ke dalam pekerja dan menyusun skrip tetap mirip dengan pendekatan pemuatan skrip sinkron yang umum pada tahun 2009.
Histori: pekerja klasik
Konstruktor Worker menggunakan URL klasik
script, yang
relatif terhadap URL dokumen. Objek ini akan langsung menampilkan referensi ke instance pekerja baru,
yang juga mengekspos antarmuka pesan serta metode terminate()
yang langsung menghentikan dan
menghancurkan pekerja.
const worker = new Worker('worker.js');
Fungsi importScripts()
tersedia dalam pekerja web untuk memuat kode tambahan, tetapi fungsi ini menjeda eksekusi pekerja untuk mengambil dan mengevaluasi setiap skrip. Tag ini juga menjalankan skrip
dalam cakupan global seperti tag <script>
klasik, yang berarti variabel dalam satu skrip dapat
ditimpa oleh variabel dalam skrip 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';
}
Oleh karena itu, web worker secara historis memberikan efek yang sangat besar pada arsitektur aplikasi. Developer harus membuat alat dan solusi sementara yang cerdas agar dapat menggunakan pekerja web tanpa mengorbankan praktik pengembangan modern. Sebagai contoh, bundler seperti webpack menyematkan implementasi pemuat modul kecil ke dalam kode yang dihasilkan yang menggunakan importScripts()
untuk pemuatan kode, tetapi membungkus modul dalam fungsi untuk menghindari konflik variabel dan menyimulasikan impor dan ekspor dependensi.
Memasukkan pekerja modul
Mode baru untuk pekerja web dengan manfaat ergonomi dan performa modul
JavaScript diluncurkan di Chrome 80, yang disebut pekerja modul. Konstruktor
Worker
kini menerima opsi {type:"module"}
baru, yang mengubah pemuatan dan
eksekusi skrip agar cocok dengan <script type="module">
.
const worker = new Worker('worker.js', {
type: 'module'
});
Karena pekerja modul adalah modul JavaScript standar, mereka dapat menggunakan pernyataan impor dan ekspor. Seperti semua modul JavaScript, dependensi hanya dieksekusi satu kali dalam konteks tertentu (thread utama, worker, dll.), dan semua impor mendatang mereferensikan instance modul yang sudah dieksekusi. Pemuatan dan eksekusi modul JavaScript juga dioptimalkan oleh browser. Dependensi modul dapat dimuat sebelum modul dieksekusi, sehingga seluruh hierarki modul dapat dimuat secara paralel. Pemuatan modul juga menyimpan kode yang diuraikan dalam cache, yang berarti modul yang digunakan di thread utama dan di pekerja hanya perlu diuraikan satu kali.
Beralih ke modul JavaScript juga memungkinkan penggunaan impor
dinamis untuk kode pemuatan lambat tanpa memblokir eksekusi
pekerja. 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 pekerja
modul. Mengalihkan pekerja untuk menggunakan modul JavaScript berarti semua kode dimuat dalam mode
ketat. Perubahan penting lainnya adalah nilai this
dalam cakupan tingkat teratas modul JavaScript adalah undefined
, sedangkan dalam pekerja klasik, nilainya adalah cakupan global pekerja. Untungnya, selalu ada self
global yang memberikan referensi ke cakupan global. API ini tersedia di semua jenis pekerja, termasuk pekerja layanan, serta di DOM.
Memuat pekerja terlebih dahulu dengan modulepreload
Salah satu peningkatan performa substansial yang disertakan dengan pekerja modul adalah kemampuan untuk memuat pekerja dan dependensinya terlebih dahulu. Dengan pekerja modul, skrip dimuat dan dieksekusi sebagai modul JavaScript standar, yang berarti skrip dapat dimuat sebelumnya dan bahkan diuraikan sebelumnya 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 yang sudah dimuat sebelumnya juga dapat digunakan oleh thread utama dan pekerja modul. Hal ini berguna untuk modul yang diimpor dalam kedua konteks, atau dalam kasus ketika tidak mungkin untuk mengetahui sebelumnya apakah modul akan digunakan di thread utama atau di pekerja.
Sebelumnya, opsi yang tersedia untuk memuat skrip pekerja web lebih awal terbatas dan tidak
selalu dapat diandalkan. Pekerja klasik memiliki jenis resource "pekerja" sendiri untuk pemuatan awal, tetapi tidak ada browser yang menerapkan <link rel="preload" as="worker">
. Akibatnya, teknik utama yang tersedia untuk memuat web worker terlebih dahulu adalah menggunakan <link rel="prefetch">
, yang sepenuhnya mengandalkan cache HTTP. Jika digunakan bersama dengan header caching yang benar, hal ini memungkinkan
instansiasi pekerja tidak perlu menunggu untuk mendownload skrip pekerja. Namun, tidak seperti
modulepreload
teknik ini tidak mendukung pemuatan awal dependensi atau pra-parsing.
Bagaimana dengan pekerja bersama?
Shared worker telah
diperbarui dengan dukungan untuk modul JavaScript mulai Chrome 83. Seperti pekerja khusus,
membuat pekerja bersama dengan opsi {type:"module"}
kini memuat skrip pekerja sebagai
modul, bukan skrip klasik:
const worker = new SharedWorker('/worker.js', {
type: 'module'
});
Sebelum dukungan modul JavaScript, konstruktor SharedWorker()
hanya mengharapkan
URL dan argumen name
opsional. Hal ini akan terus berfungsi untuk penggunaan shared worker klasik; namun, pembuatan shared worker modul memerlukan penggunaan argumen options
baru. Opsi yang tersedia sama dengan opsi untuk pekerja khusus, termasuk opsi name
yang menggantikan argumen name
sebelumnya.
Bagaimana dengan pekerja layanan?
Spesifikasi pekerja layanan telah diperbarui untuk mendukung penerimaan modul JavaScript sebagai titik entri, menggunakan opsi {type:"module"}
yang sama seperti pekerja modul, tetapi perubahan ini belum diterapkan di browser. Setelah itu terjadi, pekerja layanan dapat dibuat instance-nya menggunakan modul JavaScript dengan kode berikut:
navigator.serviceWorker.register('/sw.js', {
type: 'module'
});
Setelah spesifikasi diperbarui, browser mulai menerapkan perilaku baru. Hal ini memerlukan waktu karena ada beberapa komplikasi tambahan yang terkait dengan penggunaan modul JavaScript di service worker. Pendaftaran pekerja layanan perlu membandingkan skrip yang diimpor dengan versi yang di-cache sebelumnya saat menentukan apakah akan memicu update, dan hal ini perlu diterapkan untuk modul JavaScript saat digunakan untuk pekerja layanan. Selain itu, pekerja layanan harus dapat melewati cache untuk skrip dalam kasus tertentu saat memeriksa update.
Referensi tambahan dan bacaan lebih lanjut
- Status fitur, konsensus browser, dan standardisasi
- Penambahan spesifikasi pekerja modul asli
- Modul JavaScript untuk pekerja bersama
- Modul JavaScript untuk pekerja layanan: Status penerapan Chrome