Memindahkan pekerjaan berat ke thread latar belakang kini lebih mudah dengan modul JavaScript di worker web.
JavaScript bersifat single-thread, yang berarti JavaScript hanya dapat menjalankan satu operasi dalam satu waktu. Cara ini intuitif dan berfungsi dengan baik untuk banyak kasus di web, tetapi dapat menjadi masalah saat kita harus melakukan tugas berat seperti pemrosesan data, penguraian, komputasi, atau analisis. Karena semakin banyak aplikasi kompleks yang dikirimkan di web, terdapat peningkatan kebutuhan akan pemrosesan multi-thread.
Di platform web, primitif utama untuk threading dan paralelisme adalah Web Workers API. Pekerja adalah abstraksi ringan selain thread sistem operasi yang mengekspos pesan yang meneruskan API 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 dengan lancar saat melakukan operasi yang mahal pada satu atau beberapa thread latar belakang.
Berikut adalah contoh umum penggunaan pekerja, ketika skrip pekerja memproses pesan dari thread utama dan merespons dengan mengirim 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 tersebut sudah lama berada di depan modul JavaScript. Karena tidak ada sistem modul ketika pekerja dirancang, API untuk memuat kode ke dalam pekerja dan menulis skrip tetap serupa dengan pendekatan pemuatan skrip sinkron yang umum digunakan pada tahun 2009.
Sejarah: pekerja klasik
Konstruktor Pekerja menggunakan URL skrip
klasik, yang
relatif terhadap URL dokumen. Metode ini segera menampilkan referensi ke instance pekerja baru,
yang mengekspos antarmuka pesan serta metode terminate()
yang segera menghentikan dan
menghancurkan pekerja.
const worker = new Worker('worker.js');
Fungsi importScripts()
tersedia dalam pekerja web untuk memuat kode tambahan, tetapi akan menjeda eksekusi pekerja untuk mengambil dan mengevaluasi setiap skrip. Skrip 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';
}
Karena alasan ini, pekerja web secara historis telah menerapkan efek yang sangat besar pada arsitektur
aplikasi. Developer harus membuat alat dan solusi cerdas untuk memungkinkan penggunaan pekerja web tanpa meninggalkan praktik pengembangan modern. Contohnya, 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 impor dan ekspor dependensi.
Masukkan pekerja modul
Mode baru untuk pekerja web dengan manfaat ergonomi dan performa modul
JavaScript akan 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 dijalankan satu kali dalam konteks tertentu (thread utama, pekerja, dll.), dan semua impor mendatang merujuk ke instance modul yang sudah dijalankan. Pemuatan dan eksekusi modul JavaScript juga dioptimalkan oleh browser. Dependensi modul dapat dimuat sebelum modul dieksekusi, yang memungkinkan seluruh hierarki modul dimuat secara paralel. Pemuatan modul juga meng-cache kode yang diuraikan, yang berarti modul yang digunakan di thread utama dan dalam pekerja hanya perlu diurai sekali.
Pindah 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 hebat, metode importScripts()
lama tidak tersedia dalam pekerja
modul. Dengan mengalihkan pekerja untuk menggunakan modul JavaScript, semua kode akan dimuat dalam mode
ketat. Perubahan penting lainnya adalah nilai this
dalam cakupan level teratas modul JavaScript adalah undefined
, sedangkan pada pekerja klasik, nilainya adalah cakupan global pekerja. Untungnya, selalu ada
global self
yang menyediakan referensi ke cakupan global. Ini tersedia di 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 dependensi mereka. Dengan pekerja modul, skrip dimuat dan dieksekusi sebagai modul JavaScript
standar, yang berarti skrip dapat dipramuat dan bahkan diurai 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 pramuat juga dapat digunakan oleh thread utama dan pekerja modul. Hal ini berguna untuk modul yang diimpor dalam kedua konteks, atau jika tidak mungkin untuk mengetahui sebelumnya apakah modul akan digunakan di thread utama atau dalam pekerja.
Sebelumnya, opsi yang tersedia untuk pramuat skrip pekerja web terbatas dan tidak
harus dapat diandalkan. Pekerja klasik memiliki jenis resource "pekerja" sendiri untuk melakukan pramuat, tetapi tidak ada
browser yang menerapkan <link rel="preload" as="worker">
. Akibatnya, teknik utama yang tersedia untuk melakukan pramuat pekerja web adalah menggunakan <link rel="prefetch">
, yang sepenuhnya bergantung pada cache HTTP. Ketika digunakan bersama dengan header caching yang benar, hal ini memungkinkan pembuatan instance pekerja agar 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 telah
diupdate 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 mendukung modul JavaScript, konstruktor SharedWorker()
hanya mengharapkan
URL dan argumen name
opsional. Ini akan terus berfungsi untuk penggunaan pekerja bersama klasik; namun, pembuatan pekerja bersama 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 dengan pekerja modul,
tetapi perubahan ini belum diterapkan di browser. Setelah itu terjadi, pekerja layanan dapat dibuat instance-nya menggunakan modul JavaScript menggunakan kode berikut:
navigator.serviceWorker.register('/sw.js', {
type: 'module'
});
Setelah spesifikasi diperbarui, browser mulai menerapkan perilaku baru. Proses ini memerlukan waktu karena ada beberapa detail tambahan yang terkait dengan membawa modul JavaScript ke pekerja layanan. 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, dan standardisasi browser
- Penambahan spesifikasi pekerja modul asli
- Modul JavaScript untuk pekerja bersama
- Modul JavaScript untuk pekerja layanan: Status implementasi Chrome