Web Worker adalah thread JavaScript kedua yang jalan di samping main thread. Kerja berat dipindah ke sana, jadi main thread tetap bebas merespons klik dan menggambar layar. Bayangin dapur restoran yang kokinya juga melayani tamu di depan. Ada pesanan 50 kg bawang yang harus dipotong, jadi dia minta tolong asisten di dapur belakang. Asisten itu worker: dia gak bisa keluar ke ruang tamu, cuma menerima pesanan lewat kertas dan mengirim hasilnya lewat kertas juga.
🧪 Tebakan cepat: menghitung statistik 100.000 transaksi butuh 1.500 ms. Kerja itu dipindah ke Web Worker. Hasilnya muncul kapan: jauh lebih cepat (sekitar 750 ms), sekitar 1.500 ms, atau langsung? Jawabannya ada di bagian "Main thread lawan worker".
Main thread lawan worker
Dari pelajaran sebelumnya: main thread cuma satu, dan selama dia menjalankan satu task, klik harus antre. Worker punya thread sendiri, jadi kerja di sana gak menahan main thread.
Angka simulasi dari Lab di halaman ini: hitung statistik 1.500 ms, lalu tampilkan hasilnya 10 ms. Kamu klik tombol Like di ms ke-500.
| Dihitung di main thread | Dihitung di Web Worker | |
|---|---|---|
| Main thread sibuk | 1.510 ms tanpa jeda | 40 ms kirim data, lalu 10 ms tampilkan hasil |
| Klik Like di ms ke-500 | nunggu 1.010 ms | langsung diproses |
| Hasil tampil | ms ke-1.510 | ms ke-1.591 |
Di worker, hasilnya tetap butuh sekitar 1.500 ms, malah sedikit lebih lama karena ada ongkos kirim data bolak-balik. Itu jawaban tebakannya. Worker gak bikin JavaScript lebih cepat. Untungnya: selama 1.500 ms itu halamannya tetap responsif, jadi Like langsung bertambah.
Membuat worker
Kode worker ditulis di file terpisah. Main thread dan worker saling bicara lewat pesan.
// main.js
const worker = new Worker("statistik.js");
tombolHitung.addEventListener("click", () => {
worker.postMessage(transaksi); // kirim 100.000 transaksi
});
worker.onmessage = (event) => {
tampilkan(event.data); // hasil dari worker
};
// statistik.js (jalan di worker)
self.onmessage = (event) => {
const transaksi = event.data;
const total = transaksi.reduce((jumlah, t) => jumlah + t.nominal, 0);
self.postMessage({ total, rataRata: total / transaksi.length });
};
new Worker("statistik.js")menjalankan file itu di thread baru.worker.postMessage(data)mengirim data ke worker. Di dalam worker, data itu datang sebagaievent.datadiself.onmessage.selfadalah objek global di dalam worker, penggantiwindow.self.postMessage(hasil)mengirim hasilnya balik, dan main thread menerimanya diworker.onmessage.- Kalau worker-nya sudah gak dipakai, matikan dengan
worker.terminate(). - Di proyek yang pakai bundler seperti Vite, tulis
new Worker(new URL("./statistik.js", import.meta.url), { type: "module" })supaya file worker-nya ikut di-bundle.
Yang gak bisa dilakukan worker
Worker gak punya akses ke DOM: di dalamnya gak ada document dan gak ada window. Menulis document.querySelector("#hasil") di worker langsung error: ReferenceError: document is not defined. Jadi worker cuma menghitung, lalu mengirim hasilnya. Yang mengubah tampilan tetap main thread, di worker.onmessage.
Yang tetap bisa: fetch, setTimeout, IndexedDB, WebSocket, dan semua hitungan biasa. Cocok untuk parsing data besar (JSON, CSV), olah gambar, enkripsi atau hashing, dan sorting data yang banyak.
Data dikopi atau dipindah
postMessage mengkopi datanya dengan cara yang disebut structured clone. Object, array, Map, Date, dan ArrayBuffer bisa dikirim. Function dan elemen DOM gak bisa: postMessage langsung melempar DataCloneError. Karena dikopi, mengubah object aslinya setelah dikirim gak mengubah salinan di worker. Mengkopi juga ada ongkosnya: di Lab, 100.000 object butuh 40 ms di main thread.
Data biner di ArrayBuffer bisa dipindah (transfer), bukan dikopi. Hampir gratis, tapi setelah itu pengirimnya gak punya datanya lagi.
const nominal = new Float64Array(100000); // 800 KB angka
worker.postMessage(nominal.buffer, [nominal.buffer]);
console.log(nominal.buffer.byteLength); // 0, sudah pindah ke worker
- Argumen kedua,
[nominal.buffer], adalah daftar yang dipindah. Tanpa itu, buffer-nya dikopi. - Setelah dipindah,
byteLengthdi main thread jadi0. Worker yang sekarang memilikinya.
Ada juga SharedArrayBuffer untuk memori yang dipakai bersama oleh dua thread, tapi dia butuh header cross-origin isolation di server dan jarang dibutuhkan.
⚠️ Jebakan yang sering ketemu
- Memindah kerja kecil. Mengirim data ada ongkosnya. Filter 50 item lebih cepat dikerjakan langsung di main thread.
- Mengubah DOM dari worker.
documentgak ada di worker. Kirim hasilnya, lalu ubah DOM diworker.onmessage. - Mengirim function atau elemen DOM. Keduanya gak bisa di-clone, jadi muncul
DataCloneError. Kirim datanya saja. - Lupa
terminate(). Worker yang sudah gak dipakai tetap hidup dan tetap memakan memori.
TL;DR: Web Worker menjalankan JavaScript di thread terpisah, jadi kerja berat gak menahan main thread dan halaman tetap responsif. Kerjanya gak jadi lebih cepat (1.500 ms tetap sekitar 1.500 ms), tapi klik gak perlu antre. Buat dengan new Worker("file.js"), kirim dengan postMessage, terima di onmessage, dan matikan dengan terminate(). Worker gak bisa mengakses DOM (document dan window gak ada), jadi dia mengirim hasilnya dan main thread yang mengubah tampilan. Data dikopi lewat structured clone (function dan elemen DOM gak bisa), dan ArrayBuffer bisa dipindah lewat daftar transfer sehingga byteLength di pengirim jadi 0.