Skillversitas

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

  1. Use for heavy tasks: Large calculations, data processing
  2. Don't access the DOM: Workers cannot access the DOM
  3. Transfer data: Use Transferable Objects for performance
  4. Error handling: Handle errors in the worker
  5. 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.