Memindahkan tugas berat ke thread latar belakang kini lebih mudah dengan modul JavaScript di web worker.
JavaScript bersifat 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 data, penguraian, komputasi, atau analisis. Seiring dengan semakin banyaknya aplikasi kompleks yang dikirimkan di web, kebutuhan akan pemrosesan multi-thread pun 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 beroperasi pada set data besar, sehingga thread utama dapat berjalan lancar saat melakukan operasi yang mahal pada satu atau beberapa thread latar belakang.
Berikut adalah contoh umum penggunaan worker, tempat skrip worker 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 hal ini berarti worker memiliki dukungan browser yang sangat baik dan dioptimalkan dengan baik, hal ini juga berarti worker sudah ada jauh sebelum modul JavaScript. Karena tidak ada sistem modul saat worker dirancang, API untuk memuat kode ke dalam worker dan menyusun skrip tetap mirip dengan pendekatan pemuatan skrip sinkron yang umum pada tahun 2009.
Histori: Worker klasik
Konstruktor Worker menggunakan URL skrip
klasik, yang
relatif terhadap URL dokumen. Fungsi ini segera menampilkan referensi ke worker instance baru,
yang mengekspos antarmuka pesan serta metode terminate() yang segera menghentikan dan
menghancurkan worker.
const worker = new Worker('worker.js');
Fungsi importScripts() tersedia dalam worker web untuk memuat kode tambahan, tetapi
fungsi ini menjeda eksekusi worker untuk mengambil dan mengevaluasi setiap skrip. Tag ini juga mengeksekusi 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 cerdas agar dapat
menggunakan web worker tanpa mengorbankan 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 tabrakan variabel dan menyimulasikan
impor dan ekspor dependensi.
Memasukkan worker modul
Mode baru untuk web worker dengan manfaat ergonomi dan performa modul JavaScript akan diluncurkan di Chrome 80, yang disebut module worker. Konstruktor
Worker kini menerima opsi {type:"module"} baru, yang mengubah pemuatan dan
eksekusi skrip agar sesuai dengan <script type="module">.
const worker = new Worker('worker.js', {
type: 'module'
});
Karena worker modul adalah modul JavaScript standar, worker modul dapat menggunakan pernyataan impor dan ekspor. Seperti semua modul JavaScript, dependensi hanya dieksekusi sekali dalam konteks tertentu (thread utama, 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, sehingga seluruh hierarki modul dapat dimuat secara paralel. Pemuatan modul juga meng-cache kode yang diurai, yang berarti modul yang digunakan di thread utama dan di worker hanya perlu diurai sekali.
Beralih ke modul JavaScript juga memungkinkan penggunaan impor
dinamis untuk kode pemuatan lambat tanpa memblokir eksekusi
worker. 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 worker modul. Mengalihkan worker 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 di worker klasik, nilainya adalah cakupan global worker. Untungnya, selalu ada self global yang memberikan referensi ke cakupan global. API ini tersedia di
semua jenis worker, termasuk pekerja layanan, serta di DOM.
Pramuat worker dengan modulepreload
Satu peningkatan performa yang signifikan yang disertakan dengan worker modul adalah kemampuan untuk melakukan pramuat
worker dan dependensinya. Dengan worker modul, skrip dimuat dan dieksekusi sebagai modul JavaScript standar, yang berarti skrip dapat dipramuat dan bahkan dipra-urai 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 dipramuat juga dapat digunakan oleh thread utama dan worker 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 worker.
Sebelumnya, opsi yang tersedia untuk melakukan pramuat skrip web worker terbatas dan tidak
selalu dapat diandalkan. Worker klasik memiliki jenis resource "worker" sendiri untuk pramuat, tetapi tidak ada browser yang menerapkan <link rel="preload" as="worker">. Akibatnya, teknik utama
yang tersedia untuk melakukan pramuat web worker adalah menggunakan <link rel="prefetch">, yang sepenuhnya mengandalkan
cache HTTP. Jika digunakan bersama dengan header caching yang benar, hal ini memungkinkan
untuk menghindari pembuatan instance worker yang harus menunggu untuk mendownload skrip worker. Namun, tidak seperti
modulepreload, teknik ini tidak mendukung pra-pemuatan dependensi atau pra-penguraian.
Bagaimana dengan shared worker?
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 worker bersama klasik; namun,
membuat worker bersama modul memerlukan penggunaan argumen options baru. Opsi yang tersedia sama dengan opsi untuk worker 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 dengan pekerja modul,
tetapi perubahan ini belum diterapkan di browser. Setelah itu, Anda dapat
membuat instance pekerja layanan menggunakan modul JavaScript menggunakan 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 menghadirkan modul JavaScript ke 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 mengabaikan cache untuk skrip dalam kasus tertentu saat memeriksa update.
Referensi tambahan dan bacaan lebih lanjut
- Status fitur, konsensus browser, dan standardisasi
- Penambahan spesifikasi worker modul asli
- Modul JavaScript untuk shared worker
- Modul JavaScript untuk pekerja layanan: Status penerapan Chrome