July 31, 2025
Pernahkah kamu mengalami website yang tiba-tiba hang ketika melakukan proses berat seperti manipulasi gambar atau perhitungan kompleks? Nah, masalah ini sebenarnya bisa diatasi dengan fitur keren bernama Web Workers. Mari kita bahas tuntas bagaimana cara kerja teknologi ini dan implementasinya untuk menangani heavy task tanpa membuat user interface (UI) jadi tidak responsif.
Web Workers adalah teknologi JavaScript yang memungkinkan kamu menjalankan script di background thread terpisah dari main thread. Bayangkan main thread sebagai jalan utama yang sibuk dengan aktivitas UI, sementara Web Workers adalah jalur alternatif khusus untuk pekerjaan berat.
Keuntungan utama menggunakan Web Workers:
Web Workers bekerja dengan konsep isolasi thread. Script yang berjalan di worker tidak bisa langsung mengakses DOM atau variabel dari main thread. Komunikasi antara main thread dan worker dilakukan melalui sistem message passing menggunakan PostMessage API.
Berikut gambaran sederhana alur kerjanya:
postMessage()Mari kita buat contoh nyata untuk memahami konsep ini. Kita akan membuat aplikasi sederhana untuk mengubah gambar menjadi grayscale menggunakan Web Workers.
// imageWorker.js
self.onmessage = function(event) {
const { imageData, filterType } = event.data;
let processedData;
switch(filterType) {
case 'grayscale':
processedData = applyGrayscaleFilter(imageData);
break;
case 'sepia':
processedData = applySepiaFilter(imageData);
break;
default:
processedData = imageData;
}
// Kirim hasil kembali ke main thread
self.postMessage({
success: true,
processedImageData: processedData,
message: 'Image processing completed'
});
};
function applyGrayscaleFilter(imageData) {
const data = imageData.data;
for (let i = 0; i < data.length; i += 4) {
const red = data[i];
const green = data[i + 1];
const blue = data[i + 2];
// Formula grayscale menggunakan weighted average
const gray = Math.round(0.299 * red + 0.587 * green + 0.114 * blue);
data[i] = gray; // Red
data[i + 1] = gray; // Green
data[i + 2] = gray; // Blue
// Alpha (i + 3) tetap sama
}
return imageData;
}
function applySepiaFilter(imageData) {
const data = imageData.data;
for (let i = 0; i < data.length; i += 4) {
const red = data[i];
const green = data[i + 1];
const blue = data[i + 2];
// Formula sepia tone
const newRed = Math.min(255, (red * 0.393) + (green * 0.769) + (blue * 0.189));
const newGreen = Math.min(255, (red * 0.349) + (green * 0.686) + (blue * 0.168));
const newBlue = Math.min(255, (red * 0.272) + (green * 0.534) + (blue * 0.131));
data[i] = newRed;
data[i + 1] = newGreen;
data[i + 2] = newBlue;
}
return imageData;
}
// main.js
class ImageProcessor {
constructor() {
this.worker = new Worker('imageWorker.js');
this.canvas = document.getElementById('imageCanvas');
this.ctx = this.canvas.getContext('2d');
this.setupWorkerListener();
this.setupUI();
}
setupWorkerListener() {
this.worker.onmessage = (event) => {
const { success, processedImageData, message } = event.data;
if (success) {
// Update canvas dengan hasil processing
this.ctx.putImageData(processedImageData, 0, 0);
this.showStatus(message, 'success');
} else {
this.showStatus('Processing failed', 'error');
}
this.hideLoader();
};
this.worker.onerror = (error) => {
console.error('Worker error:', error);
this.showStatus('Worker error occurred', 'error');
this.hideLoader();
};
}
setupUI() {
const fileInput = document.getElementById('imageInput');
const grayscaleBtn = document.getElementById('grayscaleBtn');
const sepiaBtn = document.getElementById('sepiaBtn');
fileInput.addEventListener('change', (e) => this.loadImage(e));
grayscaleBtn.addEventListener('click', () => this.processImage('grayscale'));
sepiaBtn.addEventListener('click', () => this.processImage('sepia'));
}
loadImage(event) {
const file = event.target.files[0];
if (!file) return;
const reader = new FileReader();
reader.onload = (e) => {
const img = new Image();
img.onload = () => {
this.canvas.width = img.width;
this.canvas.height = img.height;
this.ctx.drawImage(img, 0, 0);
};
img.src = e.target.result;
};
reader.readAsDataURL(file);
}
processImage(filterType) {
const imageData = this.ctx.getImageData(0, 0, this.canvas.width, this.canvas.height);
this.showLoader();
this.showStatus('Processing image...', 'info');
// Kirim data ke worker untuk diproses
this.worker.postMessage({
imageData: imageData,
filterType: filterType
});
}
showLoader() {
document.getElementById('loader').style.display = 'block';
}
hideLoader() {
document.getElementById('loader').style.display = 'none';
}
showStatus(message, type) {
const statusEl = document.getElementById('status');
statusEl.textContent = message;
statusEl.className = `status ${type}`;
}
}
// Initialize aplikasi ketika DOM ready
document.addEventListener('DOMContentLoaded', () => {
new ImageProcessor();
});
<!DOCTYPE html>
<html lang="id">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Web Workers Image Processor Demo</title>
<style>
.container {
max-width: 800px;
margin: 0 auto;
padding: 20px;
font-family: Arial, sans-serif;
}
.controls {
margin: 20px 0;
display: flex;
gap: 10px;
align-items: center;
}
.btn {
padding: 10px 20px;
border: none;
border-radius: 5px;
cursor: pointer;
font-weight: bold;
}
.btn-primary { background: #007bff; color: white; }
.btn-secondary { background: #6c757d; color: white; }
.status {
padding: 10px;
margin: 10px 0;
border-radius: 5px;
}
.status.success { background: #d4edda; color: #155724; }
.status.error { background: #f8d7da; color: #721c24; }
.status.info { background: #d1ecf1; color: #0c5460; }
#loader {
display: none;
text-align: center;
padding: 20px;
}
#imageCanvas {
max-width: 100%;
border: 2px solid #ddd;
border-radius: 8px;
}
</style>
</head>
<body>
<div class="container">
<h1>Web Workers Image Processor</h1>
<div class="controls">
<input type="file" id="imageInput" accept="image/*">
<button id="grayscaleBtn" class="btn btn-primary">Apply Grayscale</button>
<button id="sepiaBtn" class="btn btn-secondary">Apply Sepia</button>
</div>
<div id="status" class="status"></div>
<div id="loader">
<p>Processing image...</p>
<div>🔄 Loading...</div>
</div>
<canvas id="imageCanvas"></canvas>
</div>
<script src="main.js"></script>
</body>
</html>
PostMessage API adalah jembatan komunikasi antara main thread dan Web Workers. Berikut penjelasan detail cara kerjanya:
// Kirim data sederhana
worker.postMessage('Hello Worker!');
// Kirim object kompleks
worker.postMessage({
action: 'calculate',
numbers: [1, 2, 3, 4, 5],
operation: 'sum'
});
// Kirim dengan Transferable Objects untuk performa lebih baik
const buffer = new ArrayBuffer(1024);
worker.postMessage(buffer, [buffer]); // buffer di-transfer, bukan di-copy
// Di dalam worker file
self.onmessage = function(event) {
const receivedData = event.data;
// Process data
const result = processData(receivedData);
// Kirim hasil kembali
self.postMessage({
status: 'completed',
result: result
});
};
worker.onmessage = function(event) {
const { status, result } = event.data;
if (status === 'completed') {
// Update UI dengan hasil
updateInterface(result);
}
};
👉👉 demo image web processing link codepen
// Di main thread
worker.onerror = function(error) {
console.error('Worker error:', error.message);
// Handle error gracefully
};
// Di worker
try {
// Heavy processing code
const result = heavyComputation(data);
self.postMessage({ success: true, result });
} catch (error) {
self.postMessage({
success: false,
error: error.message
});
}
// Di worker - kirim progress update
function processLargeData(data) {
const total = data.length;
for (let i = 0; i < total; i++) {
// Process item
processItem(data[i]);
// Report progress setiap 100 item
if (i % 100 === 0) {
self.postMessage({
type: 'progress',
completed: i,
total: total,
percentage: Math.round((i / total) * 100)
});
}
}
}
// Terminate worker ketika sudah tidak diperlukan
worker.terminate();
// Di worker, cleanup resources
self.close(); // Tutup worker dari dalam
Web Workers cocok digunakan untuk:
Jangan gunakan Web Workers untuk:
Web Workers adalah solusi powerful untuk menangani heavy task di JavaScript tanpa mengorbankan responsivitas UI. Dengan memahami konsep thread isolation dan PostMessage API, kamu bisa membuat aplikasi web yang lebih smooth dan user-friendly.
Implementasi yang tepat dari Web Workers bisa dramatically meningkatkan user experience, terutama untuk aplikasi yang melakukan processing intensif. Jangan lupa untuk selalu handle error dengan baik dan manage memory usage agar aplikasi tetap optimal.
Sekarang saatnya kamu mencoba implementasi Web Workers di project kamu sendiri. Happy coding! 🚀