Web Workers
Web Workers allow JavaScript to run on background threads without blocking the UI.
Web Worker Basics
Creating a Worker
<p id="result">Result: 0</p>
<button onclick="startWorker()">Start</button>
<button onclick="stopWorker()">Stop</button>
<script>
let w;
function startWorker() {
if (typeof(Worker) !== "undefined") {
if (typeof(w) == "undefined") {
w = new Worker("demo.js");
}
w.onmessage = function(event) {
document.getElementById("result").innerHTML = event.data;
};
} else {
document.getElementById("result").innerHTML = "Web Worker is not supported.";
}
}
function stopWorker() {
if (typeof(w) != "undefined") {
w.terminate();
w = undefined;
}
}
</script>
Worker File (demo.js)
let i = 0;
function timedCount() {
i = i + 1;
postMessage(i);
setTimeout(timedCount, 500);
}
timedCount();
Case Study: Campus Data Calculation
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Campus Data Calculation</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Segoe UI', sans-serif;
background: #f0f2f5;
padding: 2rem;
}
.container {
max-width: 800px;
margin: 0 auto;
}
h1 {
color: #1e3a5f;
margin-bottom: 2rem;
text-align: center;
}
.card {
background: white;
padding: 1.5rem;
border-radius: 8px;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
margin-bottom: 1rem;
}
.card h2 {
color: #1e3a5f;
margin-bottom: 1rem;
font-size: 1.2rem;
}
.btn {
padding: 0.8rem 1.5rem;
background: #3498db;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 1rem;
margin-right: 0.5rem;
}
.btn:hover {
background: #2980b9;
}
.btn-danger {
background: #e74c3c;
}
.btn-danger:hover {
background: #c0392b;
}
.progress-container {
margin-top: 1rem;
display: none;
}
.progress-bar {
width: 100%;
height: 20px;
background: #e9ecef;
border-radius: 10px;
overflow: hidden;
}
.progress-fill {
height: 100%;
background: #3498db;
width: 0%;
transition: width 0.3s;
}
.progress-text {
margin-top: 0.5rem;
color: #666;
font-size: 0.9rem;
}
.result {
margin-top: 1rem;
padding: 1rem;
background: #f8f9fa;
border-radius: 4px;
display: none;
}
.result.active {
display: block;
}
.result h3 {
color: #1e3a5f;
margin-bottom: 0.5rem;
}
.stats {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 1rem;
margin-top: 1rem;
}
.stat-item {
background: white;
padding: 1rem;
border-radius: 4px;
text-align: center;
}
.stat-item .value {
font-size: 1.5rem;
font-weight: bold;
color: #3498db;
}
.stat-item .label {
color: #666;
font-size: 0.85rem;
}
</style>
</head>
<body>
<div class="container">
<h1>Campus Data Calculation</h1>
<div class="card">
<h2>Process Student Data</h2>
<p>Calculate the total average grade of 10,000 student records using a Web Worker.</p>
<div style="margin-top: 1rem;">
<button class="btn" onclick="startCalculation()">Start Calculation</button>
<button class="btn btn-danger" onclick="stopCalculation()">Stop</button>
</div>
<div class="progress-container" id="progressContainer">
<div class="progress-bar">
<div class="progress-fill" id="progressFill"></div>
</div>
<div class="progress-text" id="progressText">Processing...</div>
</div>
<div class="result" id="result">
<h3>Calculation Result</h3>
<div class="stats">
<div class="stat-item">
<div class="value" id="totalStudents">0</div>
<div class="label">Total Student</div>
</div>
<div class="stat-item">
<div class="value" id="averageGrade">0</div>
<div class="label">Average Grade</div>
</div>
<div class="stat-item">
<div class="value" id="highestGrade">0</div>
<div class="label">Highest Grade</div>
</div>
<div class="stat-item">
<div class="value" id="processingTime">0</div>
<div class="label">Time (ms)</div>
</div>
</div>
</div>
</div>
<div class="card">
<h2>Generasi Data Random</h2>
<p>Generate student data using a Web Worker without blocking the UI.</p>
<div style="margin-top: 1rem;">
<button class="btn" onclick="startGenerate()">Generate Data</button>
</div>
<div class="progress-container" id="generateProgress">
<div class="progress-bar">
<div class="progress-fill" id="generateFill"></div>
</div>
<div class="progress-text" id="generateText">Generating...</div>
</div>
<div class="result" id="generateResult">
<h3>Data Generated</h3>
<p id="generatedCount">0 data telah di-generate</p>
</div>
</div>
</div>
<script>
let calcWorker = null;
let genWorker = null;
// Inline Worker for calculation
function startCalculation() {
const workerCode = `
self.onmessage = function(e) {
const totalStudents = 10000;
let sum = 0;
let highest = 0;
for (let i = 0; i < totalStudents; i++) {
// Simulate calculation
const grade = Math.floor(Math.random() * 100) + 1;
sum += grade;
if (grade > highest) highest = grade;
// Send progress every 1000 records
if (i % 1000 === 0) {
self.postMessage({
type: 'progress',
progress: (i / totalStudents) * 100
});
}
}
self.postMessage({
type: 'complete',
total: totalStudents,
average: (sum / totalStudents).toFixed(2),
highest: highest,
time: Date.now() - e.data.startTime
});
};
`;
const blob = new Blob([workerCode], { type: 'application/javascript' });
const workerUrl = URL.createObjectURL(blob);
calcWorker = new Worker(workerUrl);
document.getElementById('progressContainer').style.display = 'block';
document.getElementById('result').classList.remove('active');
const startTime = Date.now();
calcWorker.onmessage = function(e) {
if (e.data.type === 'progress') {
document.getElementById('progressFill').style.width = e.data.progress + '%';
document.getElementById('progressText').textContent =
`Processing... ${e.data.progress.toFixed(0)}%`;
} else if (e.data.type === 'complete') {
document.getElementById('progressFill').style.width = '100%';
document.getElementById('progressText').textContent = 'Done!';
document.getElementById('totalStudents').textContent = e.data.total;
document.getElementById('averageGrade').textContent = e.data.average;
document.getElementById('highestGrade').textContent = e.data.highest;
document.getElementById('processingTime').textContent = e.data.time;
document.getElementById('result').classList.add('active');
}
};
calcWorker.postMessage({ startTime });
}
function stopCalculation() {
if (calcWorker) {
calcWorker.terminate();
calcWorker = null;
}
}
// Generate Data Worker
function startGenerate() {
const workerCode = `
self.onmessage = function(e) {
const total = 5000;
const data = [];
for (let i = 0; i < total; i++) {
data.push({
id: i + 1,
name: 'Student ' + (i + 1),
studentId: '123' + String(i + 1).padStart(7, '0'),
grade: Math.floor(Math.random() * 100) + 1
});
if (i % 500 === 0) {
self.postMessage({
type: 'progress',
progress: (i / total) * 100,
count: i
});
}
}
self.postMessage({
type: 'complete',
data: data,
count: total
});
};
`;
const blob = new Blob([workerCode], { type: 'application/javascript' });
const workerUrl = URL.createObjectURL(blob);
genWorker = new Worker(workerUrl);
document.getElementById('generateProgress').style.display = 'block';
document.getElementById('generateResult').classList.remove('active');
genWorker.onmessage = function(e) {
if (e.data.type === 'progress') {
document.getElementById('generateFill').style.width = e.data.progress + '%';
document.getElementById('generateText').textContent =
`Generating... ${e.data.progress.toFixed(0)}%`;
} else if (e.data.type === 'complete') {
document.getElementById('generateFill').style.width = '100%';
document.getElementById('generateText').textContent = 'Done!';
document.getElementById('generatedCount').textContent =
`${e.data.count} data telah di-generate`;
document.getElementById('generateResult').classList.add('active');
}
};
genWorker.postMessage({});
}
</script>
</body>
</html>
Everyday Case Study
Case: A developer needs to process a large amount of data without freezing the UI.
<button onclick="process()">Process Data</button>
<p id="status">Siap</p>
<script>
function process() {
const worker = new Worker(URL.createObjectURL(new Blob([`
self.onmessage = function(e) {
let result = 0;
for (let i = 0; i < e.data; i++) {
result += Math.sqrt(i);
}
self.postMessage(result);
};
`], { type: 'application/javascript' })));
worker.postMessage(10000000);
document.getElementById('status').textContent = 'Processing...';
worker.onmessage = function(e) {
document.getElementById('status').textContent = 'Result: ' + e.data;
worker.terminate();
};
}
</script>
Best Practices
- Use for heavy tasks: Large calculations, data processing
- Don't access the DOM: Workers cannot access the DOM
- Transfer data: Use Transferable Objects for performance
- Error handling: Handle errors in the worker
- Terminate: Stop the worker after it's done
Summary
Web Workers allow JavaScript to run on background threads. They're perfect for heavy tasks that must not block the UI.