Kamu klik tombol "Generate Report", lalu halaman membeku selama beberapa detik. Kursor berubah jadi spinner, tombol tidak bisa diklik, scroll ikut macet. Kalau kamu pernah bikin aplikasi web yang mengurutkan data besar, mengompresi gambar, atau menghitung hash di sisi klien, kamu pasti kenal momen itu.
Masalahnya bukan kode kamu yang lambat. JavaScript di browser berjalan di satu thread yang sama persis dengan render dan interaksi UI. Selama loop raksasa itu belum selesai, browser tidak punya kesempatan menggambar ulang layar atau menanggapi klik. Solusinya sederhana: pindahkan kerja berat itu ke thread terpisah lewat Web Workers.
Kenapa JavaScript Macet Padahal Cuma Looping
Browser menjalankan JavaScript lewat satu event loop. Setiap klik, scroll, dan frame render mengantre sebagai task yang diproses satu per satu. Kalau satu task butuh 5 detik, semua task di belakangnya menunggu. Ini berbeda dengan setTimeout atau Promise yang hanya menunda eksekusi tapi tetap di thread yang sama. Kalau konsep async masih kerasa samar, saya sudah bahas tuntas di Menguasai Async/Await di JavaScript. Dasar-dasar event dan DOM-nya juga ada di Memahami Dasar JavaScript.
Web Workers: Thread Kedua untuk JavaScript
Web Worker adalah file JavaScript yang berjalan di thread terpisah, paralel dengan main thread. Komputasi berat di dalam worker tidak menyentuh event loop utama, jadi UI tetap mulus. Komunikasi antara main thread dan worker dilakukan lewat pesan: main thread mengirim data dengan postMessage(), worker membalas dengan postMessage() juga, dan masing-masing menerima lewat onmessage.
Web Worker tidak bisa menyentuh DOM. Tidak ada document, window, atau localStorage di dalam worker. Kalau kamu perlu membaca nilai dari input, lakukan di main thread, lalu kirim hasilnya ke worker lewat postMessage.
Membuat Worker Pertama
Worker hanyalah file JavaScript terpisah. Kita bikin contoh nyata: pencari bilangan prima sampai batas tertentu. Tanpa worker, loop ini bisa membekukan tab selama beberapa detik untuk angka besar. Dengan worker, pengguna tetap bisa mengetik dan scroll sambil menunggu hasilnya.
<!DOCTYPE html>
<html lang="id">
<head>
<meta charset="UTF-8">
<title>Web Worker Demo</title>
</head>
<body>
<h1>Pencari Bilangan Prima</h1>
<input type="number" id="limit" value="300000">
<button id="start">Cari bilangan prima</button>
<p id="status">Idle</p>
<ul id="result"></ul>
<script src="main.js"></script>
</body>
</html>
Berikutnya file worker.js yang berisi logika beratnya. Perhatikan bahwa di dalam worker, onmessage dan postMessage tersedia sebagai fungsi global — tidak perlu worker.postMessage lagi.
function isPrime(n) {
if (n < 2) return false;
for (let i = 2; i * i <= n; i++) {
if (n % i === 0) return false;
}
return true;
}
onmessage = (event) => {
const limit = event.data;
const primes = [];
for (let i = 2; i <= limit; i++) {
if (isPrime(i)) primes.push(i);
}
postMessage(primes);
};
Terakhir main.js yang menjembatani UI dan worker. Kita instansiasi worker, kirim batas angka saat tombol diklik, dan tampilkan hasilnya saat worker selesai.
const worker = new Worker("worker.js");
const startBtn = document.getElementById("start");
const statusEl = document.getElementById("status");
const resultEl = document.getElementById("result");
startBtn.addEventListener("click", () => {
const limit = Number(document.getElementById("limit").value);
statusEl.textContent = "Menghitung...";
worker.postMessage(limit);
});
worker.onmessage = (event) => {
const primes = event.data;
statusEl.textContent = `Selesai: ${primes.length} bilangan prima ditemukan`;
resultEl.innerHTML = "";
primes.slice(0, 20).forEach((p) => {
const li = document.createElement("li");
li.textContent = p;
resultEl.appendChild(li);
});
};
Jalankan lewat server HTTP lokal apa pun, misalnya python -m http.server, lalu buka halamannya. Masukkan angka besar seperti 300000 dan klik tombolnya. Bandingkan kalau kamu memanggil logika itu langsung di main thread — tab akan membeku sampai selesai. Dengan worker, kamu tetap bisa scroll dan mengetik di input.
Data yang dikirim lewat postMessage di-clone menggunakan structured clone algorithm, bukan di-share. Artinya, objek biasa, array, Map, dan Date aman dikirim. Untuk data biner yang besar seperti ArrayBuffer, kamu bisa pakai postMessage(buffer, [buffer]) agar buffer-nya dipindah (transferable), bukan dicopy — jauh lebih cepat untuk ukuran puluhan megabyte.
Menangani Error dan Memberhentikan Worker
Kalau ada exception di dalam worker, main thread tidak akan mengetahuinya lewat onmessage. Kamu perlu menangkap onerror. Worker juga tidak berhenti sendiri setelah selesai — ia menunggu task berikutnya. Kalau sudah tidak dibutuhkan, panggil terminate() supaya thread-nya benar-benar dilepas.
worker.onerror = (event) => {
statusEl.textContent = `Error: ${event.message}`;
};
// Panggil saat worker sudah tidak dipakai lagi
worker.terminate();
Shared Worker dan Module Worker
Worker yang kita buat tadi adalah dedicated worker: satu instance, satu pemilik tab. Kalau kamu ingin beberapa tab atau beberapa window berbagi satu worker yang sama, gunakan SharedWorker. Adapun module worker (new Worker(url, { type: "module" })) membolehkan kamu memakai import di dalam worker, jadi bisa berbagi kode util dengan mudah.
Workers di Proyek Bundler
Kalau kamu pakai Vite atau Webpack, cara instansiasi worker dengan path string seperti di atas bisa bermasalah karena file-nya ikut dibundle. Vite menyediakan pola new Worker(new URL("./worker.js", import.meta.url), { type: "module" }) yang otomatis menangani bundling worker. Setup dasar Vite-nya sudah saya tulis di Belajar Vite.
Jangan pakai worker untuk semua hal. Membuat worker butuh biaya: ada overhead memori dan latency saat pertukaran pesan. Gunakan hanya untuk komputasi yang benar-benar berat — parsing JSON besar, encoding video, kalkulasi ribuan baris — bukan untuk sekadar fetch atau update state ringan.
Langkah berikutnya: ambil satu titik di proyek kamu yang bikin UI terasa lambat, pindahkan ke worker, lalu ukur bedanya lewat tab Performance di DevTools. Kalau aplikasimu masih memakai pola blokir di main thread, kamu akan langsung merasakan perubahannya.