Web Workers are a technology that enables running JavaScript in separate threads, independent of the main UI (User Interface) thread. This allows performing heavy computations without blocking the user interface, which is crucial for maintaining the responsiveness of web applications.
1// Problem: Blocking computations on the main thread
2function heavyCalculation(n) {
3 console.log('Starting heavy computations...');
4
5 // Simulation of intensive computations
6 let result = 0;
7 for (let i = 0; i < n; i++) {
8 for (let j = 0; j < 1000000; j++) {
9 result += Math.sqrt(i * j);
10 }
11 }
12
13 console.log('Computations completed');
14 return result;
15}
16
17// This will block the UI for several seconds!
18document.getElementById('calculate-button').addEventListener('click', () => {
19 const result = heavyCalculation(5000);
20 document.getElementById('result').textContent = result;
21});
22
23// During computations the user cannot:
24// - Click other buttons
25// - Scroll the page
26// - Use animations
27// - Type text in forms1// Benchmark: Main Thread vs Web Worker
2const performanceTest = {
3 // Test without Web Worker - blocks UI
4 async testMainThread() {
5 console.time('Main Thread Calculation');
6
7 const start = performance.now();
8 const result = this.calculatePrimes(100000);
9 const end = performance.now();
10
11 console.timeEnd('Main Thread Calculation');
12
13 return {
14 result: result.length,
15 time: end - start,
16 blocked: true
17 };
18 },
19
20 // Test with Web Worker - does not block UI
21 async testWebWorker() {
22 console.time('Web Worker Calculation');
23
24 const start = performance.now();
25 const result = await this.calculatePrimesWorker(100000);
26 const end = performance.now();
27
28 console.timeEnd('Web Worker Calculation');
29
30 return {
31 result: result.length,
32 time: end - start,
33 blocked: false
34 };
35 },
36
37 calculatePrimes(max) {
38 const primes = [];
39 for (let i = 2; i <= max; i++) {
40 if (this.isPrime(i)) {
41 primes.push(i);
42 }
43 }
44 return primes;
45 },
46
47 isPrime(num) {
48 for (let i = 2; i <= Math.sqrt(num); i++) {
49 if (num % i === 0) return false;
50 }
51 return true;
52 }
53};1// main.js - main thread
2class WebWorkerManager {
3 constructor() {
4 this.worker = null;
5 this.setupWorker();
6 }
7
8 setupWorker() {
9 // Check Web Workers support
10 if (typeof Worker !== 'undefined') {
11 this.worker = new Worker('worker.js');
12
13 // Handle messages from the worker
14 this.worker.onmessage = (event) => {
15 this.handleWorkerMessage(event.data);
16 };
17
18 // Handle errors
19 this.worker.onerror = (error) => {
20 console.error('Worker error:', error);
21 };
22
23 console.log('Web Worker has been created');
24 } else {
25 console.warn('Web Workers are not supported in this browser');
26 }
27 }
28
29 // Send a task to the worker
30 sendTask(taskType, data) {
31 if (this.worker) {
32 this.worker.postMessage({
33 type: taskType,
34 data: data,
35 timestamp: Date.now()
36 });
37 }
38 }
39
40 // Handle responses from the worker
41 handleWorkerMessage(message) {
42 switch (message.type) {
43 case 'CALCULATION_COMPLETE':
44 this.displayResult(message.result);
45 break;
46 case 'PROGRESS_UPDATE':
47 this.updateProgress(message.progress);
48 break;
49 case 'ERROR':
50 this.handleError(message.error);
51 break;
52 }
53 }
54
55 // Terminate the worker
56 terminateWorker() {
57 if (this.worker) {
58 this.worker.terminate();
59 this.worker = null;
60 console.log('Web Worker has been terminated');
61 }
62 }
63}
64
65// Usage
66const workerManager = new WebWorkerManager();
67
68// Send a computation task
69document.getElementById('start-calculation').addEventListener('click', () => {
70 workerManager.sendTask('CALCULATE_PRIMES', { max: 100000 });
71});1// worker.js - code executed in a separate thread
2class WorkerCalculations {
3 constructor() {
4 this.setupMessageHandler();
5 }
6
7 setupMessageHandler() {
8 // Main message handler from the main thread
9 self.onmessage = (event) => {
10 const { type, data, timestamp } = event.data;
11
12 try {
13 this.handleTask(type, data, timestamp);
14 } catch (error) {
15 this.sendError(error.message);
16 }
17 };
18 }
19
20 handleTask(type, data, timestamp) {
21 switch (type) {
22 case 'CALCULATE_PRIMES':
23 this.calculatePrimes(data.max);
24 break;
25 case 'PROCESS_IMAGE':
26 this.processImage(data.imageData);
27 break;
28 case 'SORT_LARGE_ARRAY':
29 this.sortArray(data.array);
30 break;
31 case 'MONTE_CARLO_PI':
32 this.calculatePiMonteCarlo(data.iterations);
33 break;
34 default:
35 this.sendError(`Unknown task type: ${type}`);
36 }
37 }
38
39 // Calculate prime numbers with progress reporting
40 calculatePrimes(max) {
41 const primes = [];
42 const updateInterval = Math.floor(max / 100); // Update every 1%
43
44 for (let i = 2; i <= max; i++) {
45 if (this.isPrime(i)) {
46 primes.push(i);
47 }
48
49 // Report progress
50 if (i % updateInterval === 0) {
51 const progress = (i / max) * 100;
52 this.sendProgress(progress);
53 }
54 }
55
56 this.sendResult('CALCULATION_COMPLETE', {
57 primes: primes,
58 count: primes.length,
59 executionTime: performance.now()
60 });
61 }
62
63 isPrime(num) {
64 if (num < 2) return false;
65 for (let i = 2; i <= Math.sqrt(num); i++) {
66 if (num % i === 0) return false;
67 }
68 return true;
69 }
70
71 // Image processing (filtering, transformations)
72 processImage(imageData) {
73 const { data, width, height, filterType } = imageData;
74
75 switch (filterType) {
76 case 'grayscale':
77 this.applyGrayscaleFilter(data);
78 break;
79 case 'blur':
80 this.applyBlurFilter(data, width, height);
81 break;
82 case 'edge':
83 this.applyEdgeDetection(data, width, height);
84 break;
85 }
86
87 this.sendResult('IMAGE_PROCESSED', {
88 processedData: data,
89 filterApplied: filterType
90 });
91 }
92
93 applyGrayscaleFilter(data) {
94 for (let i = 0; i < data.length; i += 4) {
95 const gray = data[i] * 0.299 + data[i + 1] * 0.587 + data[i + 2] * 0.114;
96 data[i] = gray; // Red
97 data[i + 1] = gray; // Green
98 data[i + 2] = gray; // Blue
99 // Alpha (i + 3) remains unchanged
100 }
101 }
102
103 // Sorting large arrays
104 sortArray(array) {
105 const startTime = performance.now();
106
107 // Merge sort implementation for better performance
108 const sortedArray = this.mergeSort(array);
109
110 const endTime = performance.now();
111
112 this.sendResult('SORT_COMPLETE', {
113 sortedArray: sortedArray,
114 originalLength: array.length,
115 executionTime: endTime - startTime
116 });
117 }
118
119 mergeSort(arr) {
120 if (arr.length <= 1) return arr;
121
122 const mid = Math.floor(arr.length / 2);
123 const left = this.mergeSort(arr.slice(0, mid));
124 const right = this.mergeSort(arr.slice(mid));
125
126 return this.merge(left, right);
127 }
128
129 merge(left, right) {
130 const result = [];
131 let leftIndex = 0;
132 let rightIndex = 0;
133
134 while (leftIndex < left.length && rightIndex < right.length) {
135 if (left[leftIndex] <= right[rightIndex]) {
136 result.push(left[leftIndex]);
137 leftIndex++;
138 } else {
139 result.push(right[rightIndex]);
140 rightIndex++;
141 }
142 }
143
144 return result.concat(left.slice(leftIndex), right.slice(rightIndex));
145 }
146
147 // Calculate Pi using Monte Carlo method
148 calculatePiMonteCarlo(iterations) {
149 let pointsInCircle = 0;
150 const updateInterval = Math.floor(iterations / 100);
151
152 for (let i = 0; i < iterations; i++) {
153 const x = Math.random() * 2 - 1; // -1 to 1
154 const y = Math.random() * 2 - 1; // -1 to 1
155
156 if (x * x + y * y <= 1) {
157 pointsInCircle++;
158 }
159
160 if (i % updateInterval === 0) {
161 const currentPi = (pointsInCircle / i) * 4;
162 const progress = (i / iterations) * 100;
163
164 this.sendProgress(progress, { currentPi, iteration: i });
165 }
166 }
167
168 const finalPi = (pointsInCircle / iterations) * 4;
169
170 this.sendResult('PI_CALCULATION_COMPLETE', {
171 pi: finalPi,
172 iterations: iterations,
173 accuracy: Math.abs(Math.PI - finalPi)
174 });
175 }
176
177 // Helper methods for communication
178 sendResult(type, result) {
179 self.postMessage({
180 type: type,
181 result: result,
182 timestamp: Date.now()
183 });
184 }
185
186 sendProgress(progress, additionalData = {}) {
187 self.postMessage({
188 type: 'PROGRESS_UPDATE',
189 progress: progress,
190 ...additionalData,
191 timestamp: Date.now()
192 });
193 }
194
195 sendError(errorMessage) {
196 self.postMessage({
197 type: 'ERROR',
198 error: errorMessage,
199 timestamp: Date.now()
200 });
201 }
202}
203
204// Initialize worker
205new WorkerCalculations();1// WorkerPool.js - Managing multiple workers
2class WorkerPool {
3 constructor(workerScript, poolSize = navigator.hardwareConcurrency || 4) {
4 this.workerScript = workerScript;
5 this.poolSize = poolSize;
6 this.workers = [];
7 this.taskQueue = [];
8 this.activeTasksCount = 0;
9
10 this.initializePool();
11 }
12
13 initializePool() {
14 for (let i = 0; i < this.poolSize; i++) {
15 const worker = {
16 instance: new Worker(this.workerScript),
17 busy: false,
18 id: i
19 };
20
21 worker.instance.onmessage = (event) => {
22 this.handleWorkerMessage(worker, event.data);
23 };
24
25 worker.instance.onerror = (error) => {
26 this.handleWorkerError(worker, error);
27 };
28
29 this.workers.push(worker);
30 }
31
32 console.log(`Worker pool initialized with ${this.poolSize} workers`);
33 }
34
35 // Execute a task using the pool
36 execute(taskData) {
37 return new Promise((resolve, reject) => {
38 const task = {
39 data: taskData,
40 resolve: resolve,
41 reject: reject,
42 id: this.generateTaskId()
43 };
44
45 const availableWorker = this.getAvailableWorker();
46
47 if (availableWorker) {
48 this.assignTaskToWorker(availableWorker, task);
49 } else {
50 this.taskQueue.push(task);
51 }
52 });
53 }
54
55 getAvailableWorker() {
56 return this.workers.find(worker => !worker.busy);
57 }
58
59 assignTaskToWorker(worker, task) {
60 worker.busy = true;
61 worker.currentTask = task;
62 this.activeTasksCount++;
63
64 worker.instance.postMessage({
65 taskId: task.id,
66 ...task.data
67 });
68 }
69
70 handleWorkerMessage(worker, message) {
71 const task = worker.currentTask;
72
73 if (message.type === 'TASK_COMPLETE') {
74 task.resolve(message.result);
75 this.releaseWorker(worker);
76 } else if (message.type === 'TASK_ERROR') {
77 task.reject(new Error(message.error));
78 this.releaseWorker(worker);
79 } else if (message.type === 'PROGRESS_UPDATE') {
80 // Pass progress update (optional)
81 if (task.onProgress) {
82 task.onProgress(message.progress);
83 }
84 }
85 }
86
87 releaseWorker(worker) {
88 worker.busy = false;
89 worker.currentTask = null;
90 this.activeTasksCount--;
91
92 // Check if there are tasks in the queue
93 if (this.taskQueue.length > 0) {
94 const nextTask = this.taskQueue.shift();
95 this.assignTaskToWorker(worker, nextTask);
96 }
97 }
98
99 handleWorkerError(worker, error) {
100 console.error(`Worker ${worker.id} error:`, error);
101
102 if (worker.currentTask) {
103 worker.currentTask.reject(error);
104 this.releaseWorker(worker);
105 }
106 }
107
108 // Parallel processing of large datasets
109 async processInParallel(dataArray, chunkProcessor) {
110 const chunkSize = Math.ceil(dataArray.length / this.poolSize);
111 const chunks = [];
112
113 // Split data into chunks
114 for (let i = 0; i < dataArray.length; i += chunkSize) {
115 chunks.push(dataArray.slice(i, i + chunkSize));
116 }
117
118 // Process chunks in parallel
119 const promises = chunks.map(chunk =>
120 this.execute({
121 type: 'PROCESS_CHUNK',
122 chunk: chunk,
123 processor: chunkProcessor.toString()
124 })
125 );
126
127 const results = await Promise.all(promises);
128
129 // Combine results
130 return results.flat();
131 }
132
133 generateTaskId() {
134 return `task_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
135 }
136
137 // Terminate all workers
138 terminate() {
139 this.workers.forEach(worker => {
140 worker.instance.terminate();
141 });
142
143 this.workers = [];
144 this.taskQueue = [];
145 console.log('Worker pool terminated');
146 }
147
148 // Pool statistics
149 getStats() {
150 return {
151 poolSize: this.poolSize,
152 activeWorkers: this.workers.filter(w => w.busy).length,
153 queueLength: this.taskQueue.length,
154 activeTasks: this.activeTasksCount
155 };
156 }
157}
158
159// Using Worker Pool
160const workerPool = new WorkerPool('calculation-worker.js', 8);
161
162// Example of parallel processing
163async function processLargeDataset() {
164 const largeArray = Array.from({ length: 1000000 }, (_, i) => Math.random() * 1000);
165
166 try {
167 const results = await workerPool.processInParallel(largeArray, (chunk) => {
168 // Processing function will be executed in a worker
169 return chunk.map(x => Math.sqrt(x * x + 1));
170 });
171
172 console.log('Processing completed:', results.length);
173 console.log('Stats:', workerPool.getStats());
174 } catch (error) {
175 console.error('Processing error:', error);
176 }
177}1// SharedArrayBufferWorker.js - Shared memory between workers
2class SharedArrayBufferManager {
3 constructor() {
4 this.checkSupport();
5 }
6
7 checkSupport() {
8 if (typeof SharedArrayBuffer === 'undefined') {
9 console.warn('SharedArrayBuffer is not supported');
10 this.fallbackToTransferableObjects();
11 return false;
12 }
13 return true;
14 }
15
16 // Create shared memory for matrix computations
17 createSharedMatrixBuffer(rows, cols) {
18 const bufferSize = rows * cols * Float64Array.BYTES_PER_ELEMENT;
19 const sharedBuffer = new SharedArrayBuffer(bufferSize);
20
21 return {
22 buffer: sharedBuffer,
23 matrix: new Float64Array(sharedBuffer),
24 rows: rows,
25 cols: cols,
26
27 // Helper methods
28 get: (row, col) => {
29 return this.matrix[row * cols + col];
30 },
31
32 set: (row, col, value) => {
33 this.matrix[row * cols + col] = value;
34 },
35
36 fill: (value) => {
37 this.matrix.fill(value);
38 }
39 };
40 }
41
42 // Parallel matrix multiplication
43 async parallelMatrixMultiply(matrixA, matrixB, workerCount = 4) {
44 const rowsA = matrixA.rows;
45 const colsA = matrixA.cols;
46 const colsB = matrixB.cols;
47
48 // Create shared result matrix
49 const resultMatrix = this.createSharedMatrixBuffer(rowsA, colsB);
50
51 // Distribute work among workers
52 const rowsPerWorker = Math.ceil(rowsA / workerCount);
53 const workers = [];
54
55 for (let i = 0; i < workerCount; i++) {
56 const startRow = i * rowsPerWorker;
57 const endRow = Math.min(startRow + rowsPerWorker, rowsA);
58
59 if (startRow < rowsA) {
60 const worker = new Worker('matrix-worker.js');
61
62 const promise = new Promise((resolve, reject) => {
63 worker.onmessage = (event) => {
64 if (event.data.type === 'MATRIX_COMPLETE') {
65 worker.terminate();
66 resolve(event.data.result);
67 }
68 };
69
70 worker.onerror = reject;
71 });
72
73 worker.postMessage({
74 type: 'MULTIPLY_MATRIX_ROWS',
75 matrixABuffer: matrixA.buffer,
76 matrixBBuffer: matrixB.buffer,
77 resultBuffer: resultMatrix.buffer,
78 dimensions: {
79 rowsA: rowsA,
80 colsA: colsA,
81 colsB: colsB
82 },
83 rowRange: { start: startRow, end: endRow }
84 });
85
86 workers.push(promise);
87 }
88 }
89
90 await Promise.all(workers);
91 return resultMatrix;
92 }
93
94 fallbackToTransferableObjects() {
95 console.log('Using Transferable Objects as an alternative');
96 // Implementation with Transferable Objects
97 }
98}
99
100// matrix-worker.js - Worker for matrix computations
101self.onmessage = function(event) {
102 const { type, matrixABuffer, matrixBBuffer, resultBuffer, dimensions, rowRange } = event.data;
103
104 if (type === 'MULTIPLY_MATRIX_ROWS') {
105 const matrixA = new Float64Array(matrixABuffer);
106 const matrixB = new Float64Array(matrixBBuffer);
107 const result = new Float64Array(resultBuffer);
108
109 const { rowsA, colsA, colsB } = dimensions;
110 const { start, end } = rowRange;
111
112 // Matrix multiplication for assigned rows
113 for (let i = start; i < end; i++) {
114 for (let j = 0; j < colsB; j++) {
115 let sum = 0;
116 for (let k = 0; k < colsA; k++) {
117 sum += matrixA[i * colsA + k] * matrixB[k * colsB + j];
118 }
119 result[i * colsB + j] = sum;
120 }
121 }
122
123 self.postMessage({
124 type: 'MATRIX_COMPLETE',
125 result: `Rows ${start}-${end} completed`
126 });
127 }
128};1// ServiceWorkerManager.js - Managing long-running background tasks
2class ServiceWorkerManager {
3 constructor() {
4 this.serviceWorker = null;
5 this.messageChannel = null;
6 this.setupServiceWorker();
7 }
8
9 async setupServiceWorker() {
10 if ('serviceWorker' in navigator) {
11 try {
12 const registration = await navigator.serviceWorker.register('background-worker.js');
13
14 // Wait for service worker activation
15 await navigator.serviceWorker.ready;
16
17 this.serviceWorker = registration.active || registration.waiting || registration.installing;
18 this.setupMessageChannel();
19
20 console.log('Service Worker registered and ready');
21 } catch (error) {
22 console.error('Service Worker registration error:', error);
23 }
24 }
25 }
26
27 setupMessageChannel() {
28 this.messageChannel = new MessageChannel();
29
30 // Handle messages from the Service Worker
31 this.messageChannel.port1.onmessage = (event) => {
32 this.handleServiceWorkerMessage(event.data);
33 };
34
35 // Send port to the Service Worker
36 navigator.serviceWorker.controller?.postMessage(
37 { type: 'INIT_PORT' },
38 [this.messageChannel.port2]
39 );
40 }
41
42 // Schedule a long-running task for the Service Worker
43 scheduleBackgroundTask(taskConfig) {
44 if (this.messageChannel) {
45 this.messageChannel.port1.postMessage({
46 type: 'SCHEDULE_TASK',
47 task: {
48 id: this.generateTaskId(),
49 ...taskConfig,
50 scheduledAt: Date.now()
51 }
52 });
53 }
54 }
55
56 // Example: Periodic data synchronization
57 scheduleDataSync(interval = 300000) { // 5 minutes
58 this.scheduleBackgroundTask({
59 type: 'DATA_SYNC',
60 interval: interval,
61 endpoint: '/api/sync',
62 recurring: true
63 });
64 }
65
66 // Example: Background file processing
67 scheduleFileProcessing(files) {
68 files.forEach(file => {
69 this.scheduleBackgroundTask({
70 type: 'FILE_PROCESSING',
71 fileName: file.name,
72 fileData: file,
73 processor: 'image-compression'
74 });
75 });
76 }
77
78 handleServiceWorkerMessage(message) {
79 switch (message.type) {
80 case 'TASK_COMPLETED':
81 this.onTaskCompleted(message.task, message.result);
82 break;
83 case 'TASK_FAILED':
84 this.onTaskFailed(message.task, message.error);
85 break;
86 case 'SYNC_COMPLETED':
87 this.onSyncCompleted(message.result);
88 break;
89 }
90 }
91
92 onTaskCompleted(task, result) {
93 console.log(`Task ${task.id} completed:`, result);
94
95 // Update UI or application state
96 this.updateApplicationState(task, result);
97 }
98
99 generateTaskId() {
100 return `bg_task_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
101 }
102}
103
104// background-worker.js - Service Worker
105class BackgroundTaskProcessor {
106 constructor() {
107 this.activeTasks = new Map();
108 this.messagePort = null;
109 this.setupEventListeners();
110 }
111
112 setupEventListeners() {
113 self.addEventListener('message', (event) => {
114 if (event.data.type === 'INIT_PORT') {
115 this.messagePort = event.ports[0];
116 this.setupPortListener();
117 }
118 });
119
120 // Background Sync
121 self.addEventListener('sync', (event) => {
122 if (event.tag === 'background-data-sync') {
123 event.waitUntil(this.performDataSync());
124 }
125 });
126 }
127
128 setupPortListener() {
129 this.messagePort.onmessage = (event) => {
130 this.handleTask(event.data);
131 };
132 }
133
134 async handleTask(message) {
135 if (message.type === 'SCHEDULE_TASK') {
136 const task = message.task;
137
138 try {
139 const result = await this.executeTask(task);
140
141 this.messagePort.postMessage({
142 type: 'TASK_COMPLETED',
143 task: task,
144 result: result
145 });
146 } catch (error) {
147 this.messagePort.postMessage({
148 type: 'TASK_FAILED',
149 task: task,
150 error: error.message
151 });
152 }
153 }
154 }
155
156 async executeTask(task) {
157 switch (task.type) {
158 case 'DATA_SYNC':
159 return await this.performDataSync(task);
160 case 'FILE_PROCESSING':
161 return await this.processFile(task);
162 case 'BATCH_CALCULATION':
163 return await this.performBatchCalculation(task);
164 default:
165 throw new Error(`Unknown task type: ${task.type}`);
166 }
167 }
168
169 async performDataSync(task) {
170 try {
171 const response = await fetch(task.endpoint, {
172 method: 'POST',
173 headers: { 'Content-Type': 'application/json' },
174 body: JSON.stringify({ timestamp: Date.now() })
175 });
176
177 if (!response.ok) {
178 throw new Error(`Sync failed: ${response.status}`);
179 }
180
181 const data = await response.json();
182
183 // Store in IndexedDB or Cache API
184 await this.storeDataLocally(data);
185
186 return { success: true, syncedRecords: data.length };
187 } catch (error) {
188 throw new Error(`Data sync failed: ${error.message}`);
189 }
190 }
191
192 async processFile(task) {
193 // File processing simulation
194 const { fileName, processor } = task;
195
196 switch (processor) {
197 case 'image-compression':
198 return await this.compressImage(task.fileData);
199 case 'video-transcoding':
200 return await this.transcodeVideo(task.fileData);
201 default:
202 throw new Error(`Unknown processor: ${processor}`);
203 }
204 }
205
206 async storeDataLocally(data) {
207 // IndexedDB storage implementation
208 return new Promise((resolve, reject) => {
209 const request = indexedDB.open('AppDatabase', 1);
210
211 request.onsuccess = (event) => {
212 const db = event.target.result;
213 const transaction = db.transaction(['syncedData'], 'readwrite');
214 const store = transaction.objectStore('syncedData');
215
216 store.put({ id: 'latest', data: data, timestamp: Date.now() });
217
218 transaction.oncomplete = () => resolve();
219 transaction.onerror = () => reject(transaction.error);
220 };
221
222 request.onerror = () => reject(request.error);
223 });
224 }
225}
226
227new BackgroundTaskProcessor();1// WorkerPerformanceMonitor.js
2class WorkerPerformanceMonitor {
3 constructor() {
4 this.metrics = {
5 taskExecutionTimes: [],
6 memoryUsage: [],
7 errorRates: {},
8 throughput: []
9 };
10
11 this.startTime = performance.now();
12 }
13
14 // Monitor worker performance
15 monitorWorkerPerformance(worker, taskId) {
16 const startTime = performance.now();
17 let memoryBefore = null;
18
19 // Memory measurement (if available)
20 if (performance.memory) {
21 memoryBefore = performance.memory.usedJSHeapSize;
22 }
23
24 const originalPostMessage = worker.postMessage.bind(worker);
25 const originalOnMessage = worker.onmessage;
26
27 // Intercept postMessage
28 worker.postMessage = (message) => {
29 console.log(`[Worker] Sending task ${taskId}:`, message);
30 originalPostMessage(message);
31 };
32
33 // Intercept onmessage
34 worker.onmessage = (event) => {
35 const endTime = performance.now();
36 const executionTime = endTime - startTime;
37
38 // Record execution time metric
39 this.metrics.taskExecutionTimes.push({
40 taskId: taskId,
41 executionTime: executionTime,
42 timestamp: Date.now()
43 });
44
45 // Memory measurement after execution
46 if (performance.memory && memoryBefore) {
47 const memoryAfter = performance.memory.usedJSHeapSize;
48 const memoryDelta = memoryAfter - memoryBefore;
49
50 this.metrics.memoryUsage.push({
51 taskId: taskId,
52 memoryDelta: memoryDelta,
53 timestamp: Date.now()
54 });
55 }
56
57 console.log(`[Worker] Task ${taskId} completed in ${executionTime.toFixed(2)}ms`);
58
59 if (originalOnMessage) {
60 originalOnMessage(event);
61 }
62 };
63
64 return worker;
65 }
66
67 // Performance analysis
68 generatePerformanceReport() {
69 const report = {
70 totalRuntime: performance.now() - this.startTime,
71 taskCount: this.metrics.taskExecutionTimes.length,
72 averageExecutionTime: this.calculateAverageExecutionTime(),
73 memoryTrends: this.analyzeMemoryTrends(),
74 throughputAnalysis: this.calculateThroughput(),
75 recommendations: this.generateRecommendations()
76 };
77
78 return report;
79 }
80
81 calculateAverageExecutionTime() {
82 if (this.metrics.taskExecutionTimes.length === 0) return 0;
83
84 const total = this.metrics.taskExecutionTimes.reduce(
85 (sum, metric) => sum + metric.executionTime, 0
86 );
87
88 return total / this.metrics.taskExecutionTimes.length;
89 }
90
91 analyzeMemoryTrends() {
92 if (this.metrics.memoryUsage.length === 0) return null;
93
94 const memoryDeltas = this.metrics.memoryUsage.map(m => m.memoryDelta);
95 const averageMemoryDelta = memoryDeltas.reduce((a, b) => a + b, 0) / memoryDeltas.length;
96
97 return {
98 averageMemoryDelta: averageMemoryDelta,
99 maxMemoryDelta: Math.max(...memoryDeltas),
100 minMemoryDelta: Math.min(...memoryDeltas),
101 potentialMemoryLeak: averageMemoryDelta > 1024 * 1024 // > 1MB average
102 };
103 }
104
105 calculateThroughput() {
106 const timeWindow = 60000; // 1 minute
107 const now = Date.now();
108 const recentTasks = this.metrics.taskExecutionTimes.filter(
109 task => now - task.timestamp < timeWindow
110 );
111
112 return {
113 tasksPerMinute: recentTasks.length,
114 averageTaskTime: recentTasks.length > 0
115 ? recentTasks.reduce((sum, task) => sum + task.executionTime, 0) / recentTasks.length
116 : 0
117 };
118 }
119
120 generateRecommendations() {
121 const recommendations = [];
122 const avgTime = this.calculateAverageExecutionTime();
123 const memoryTrends = this.analyzeMemoryTrends();
124
125 if (avgTime > 5000) { // > 5 seconds
126 recommendations.push('Consider splitting long-running tasks into smaller chunks');
127 }
128
129 if (memoryTrends?.potentialMemoryLeak) {
130 recommendations.push('Potential memory leak detected - review memory management');
131 }
132
133 if (this.metrics.taskExecutionTimes.length > 1000) {
134 recommendations.push('Large number of tasks - consider implementing a Worker Pool');
135 }
136
137 return recommendations;
138 }
139}
140
141// Monitoring usage
142const performanceMonitor = new WorkerPerformanceMonitor();
143
144// Monitor a specific worker
145const worker = new Worker('calculation-worker.js');
146const monitoredWorker = performanceMonitor.monitorWorkerPerformance(worker, 'task_001');
147
148// Generate report after some time
149setTimeout(() => {
150 const report = performanceMonitor.generatePerformanceReport();
151 console.log('Performance Report:', report);
152}, 30000);1// OptimizedWorkerCommunication.js
2class OptimizedWorkerCommunication {
3 constructor() {
4 this.messageQueue = [];
5 this.batchSize = 10;
6 this.batchTimeout = 16; // ~60fps
7 this.compressionThreshold = 1024; // 1KB
8 }
9
10 // Message batching for better performance
11 sendBatchedMessage(worker, message) {
12 this.messageQueue.push({ worker, message });
13
14 if (this.messageQueue.length >= this.batchSize) {
15 this.flushMessageQueue();
16 } else {
17 // Flush after timeout if batch is not full
18 setTimeout(() => {
19 if (this.messageQueue.length > 0) {
20 this.flushMessageQueue();
21 }
22 }, this.batchTimeout);
23 }
24 }
25
26 flushMessageQueue() {
27 const workerMessages = new Map();
28
29 // Group messages by workers
30 this.messageQueue.forEach(({ worker, message }) => {
31 if (!workerMessages.has(worker)) {
32 workerMessages.set(worker, []);
33 }
34 workerMessages.get(worker).push(message);
35 });
36
37 // Send batched messages
38 workerMessages.forEach((messages, worker) => {
39 worker.postMessage({
40 type: 'BATCH_MESSAGES',
41 messages: messages,
42 timestamp: Date.now()
43 });
44 });
45
46 this.messageQueue = [];
47 }
48
49 // Compress large data before sending
50 async sendCompressedData(worker, data) {
51 const serializedData = JSON.stringify(data);
52
53 if (serializedData.length > this.compressionThreshold) {
54 try {
55 // Use Compression Streams API (if available)
56 const compressedData = await this.compressData(serializedData);
57
58 worker.postMessage({
59 type: 'COMPRESSED_DATA',
60 data: compressedData,
61 compressed: true,
62 originalSize: serializedData.length
63 });
64 } catch (error) {
65 // Fallback to uncompressed data
66 worker.postMessage({
67 type: 'RAW_DATA',
68 data: data,
69 compressed: false
70 });
71 }
72 } else {
73 worker.postMessage({
74 type: 'RAW_DATA',
75 data: data,
76 compressed: false
77 });
78 }
79 }
80
81 async compressData(data) {
82 // Compression implementation (example with Compression Streams)
83 if ('CompressionStream' in window) {
84 const stream = new CompressionStream('gzip');
85 const writer = stream.writable.getWriter();
86 const reader = stream.readable.getReader();
87
88 writer.write(new TextEncoder().encode(data));
89 writer.close();
90
91 const chunks = [];
92 let done = false;
93
94 while (!done) {
95 const { value, done: readerDone } = await reader.read();
96 done = readerDone;
97 if (value) {
98 chunks.push(value);
99 }
100 }
101
102 return new Uint8Array(chunks.reduce((acc, chunk) => [...acc, ...chunk], []));
103 }
104
105 // Fallback - return original data
106 return new TextEncoder().encode(data);
107 }
108
109 // Transferable Objects for efficient transfer
110 sendTransferableData(worker, arrayBuffer) {
111 worker.postMessage({
112 type: 'TRANSFERABLE_DATA',
113 data: arrayBuffer
114 }, [arrayBuffer]); // Transfer ownership
115
116 console.log('Transferred ArrayBuffer to worker (zero-copy)');
117 }
118}1// ResilientWorkerManager.js
2class ResilientWorkerManager {
3 constructor(workerScript, options = {}) {
4 this.workerScript = workerScript;
5 this.options = {
6 maxRetries: 3,
7 retryDelay: 1000,
8 healthCheckInterval: 30000,
9 taskTimeout: 60000,
10 ...options
11 };
12
13 this.workers = new Map();
14 this.failedTasks = new Map();
15 this.healthCheckTimer = null;
16
17 this.startHealthChecking();
18 }
19
20 // Create a worker with error handling
21 createResilientWorker(workerId) {
22 const worker = new Worker(this.workerScript);
23 const workerInfo = {
24 instance: worker,
25 id: workerId,
26 healthy: true,
27 taskCount: 0,
28 errorCount: 0,
29 lastActivity: Date.now(),
30 activeTasks: new Map()
31 };
32
33 // Error handling
34 worker.onerror = (error) => {
35 this.handleWorkerError(workerInfo, error);
36 };
37
38 // Message handling with timeout
39 worker.onmessage = (event) => {
40 this.handleWorkerMessage(workerInfo, event.data);
41 };
42
43 this.workers.set(workerId, workerInfo);
44 return workerInfo;
45 }
46
47 // Execute a task with retry logic
48 async executeTaskWithRetry(taskData, workerId = null) {
49 let attempts = 0;
50 let lastError = null;
51
52 while (attempts < this.options.maxRetries) {
53 try {
54 const result = await this.executeTask(taskData, workerId);
55
56 // Reset failed task counter on success
57 if (this.failedTasks.has(taskData.id)) {
58 this.failedTasks.delete(taskData.id);
59 }
60
61 return result;
62 } catch (error) {
63 attempts++;
64 lastError = error;
65
66 console.warn(`Task ${taskData.id} failed (attempt ${attempts}/${this.options.maxRetries}):`, error);
67
68 if (attempts < this.options.maxRetries) {
69 // Exponential backoff
70 const delay = this.options.retryDelay * Math.pow(2, attempts - 1);
71 await this.sleep(delay);
72
73 // Recreate worker if needed
74 if (error.type === 'WORKER_DIED') {
75 await this.recreateWorker(workerId);
76 }
77 }
78 }
79 }
80
81 // Record task as failed
82 this.failedTasks.set(taskData.id, {
83 taskData: taskData,
84 attempts: attempts,
85 lastError: lastError,
86 failedAt: Date.now()
87 });
88
89 throw new Error(`Task ${taskData.id} failed after ${attempts} attempts: ${lastError.message}`);
90 }
91
92 executeTask(taskData, workerId) {
93 return new Promise((resolve, reject) => {
94 const worker = workerId ? this.workers.get(workerId) : this.getHealthyWorker();
95
96 if (!worker || !worker.healthy) {
97 reject(new Error('No healthy worker available'));
98 return;
99 }
100
101 const taskId = taskData.id || this.generateTaskId();
102 const timeoutId = setTimeout(() => {
103 worker.activeTasks.delete(taskId);
104 reject(new Error(`Task ${taskId} timed out`));
105 }, this.options.taskTimeout);
106
107 // Record task as active
108 worker.activeTasks.set(taskId, {
109 resolve: resolve,
110 reject: reject,
111 timeoutId: timeoutId,
112 startTime: Date.now()
113 });
114
115 worker.taskCount++;
116 worker.lastActivity = Date.now();
117
118 worker.instance.postMessage({
119 ...taskData,
120 taskId: taskId
121 });
122 });
123 }
124
125 handleWorkerMessage(workerInfo, message) {
126 const { taskId, type, result, error } = message;
127
128 if (workerInfo.activeTasks.has(taskId)) {
129 const task = workerInfo.activeTasks.get(taskId);
130
131 clearTimeout(task.timeoutId);
132 workerInfo.activeTasks.delete(taskId);
133 workerInfo.lastActivity = Date.now();
134
135 if (type === 'TASK_SUCCESS') {
136 task.resolve(result);
137 } else if (type === 'TASK_ERROR') {
138 workerInfo.errorCount++;
139 task.reject(new Error(error));
140 }
141 }
142 }
143
144 handleWorkerError(workerInfo, error) {
145 console.error(`Worker ${workerInfo.id} error:`, error);
146
147 workerInfo.healthy = false;
148 workerInfo.errorCount++;
149
150 // Reject all active tasks
151 workerInfo.activeTasks.forEach(task => {
152 clearTimeout(task.timeoutId);
153 task.reject(new Error('Worker died'));
154 });
155
156 workerInfo.activeTasks.clear();
157
158 // Auto-restart worker
159 setTimeout(() => {
160 this.recreateWorker(workerInfo.id);
161 }, this.options.retryDelay);
162 }
163
164 async recreateWorker(workerId) {
165 const oldWorker = this.workers.get(workerId);
166
167 if (oldWorker) {
168 oldWorker.instance.terminate();
169 this.workers.delete(workerId);
170 }
171
172 console.log(`Recreating worker ${workerId}`);
173 const newWorker = this.createResilientWorker(workerId);
174
175 // Health check for the new worker
176 await this.waitForWorkerReady(newWorker);
177 }
178
179 waitForWorkerReady(workerInfo, timeout = 5000) {
180 return new Promise((resolve, reject) => {
181 const timeoutId = setTimeout(() => {
182 reject(new Error(`Worker ${workerInfo.id} failed to initialize`));
183 }, timeout);
184
185 // Ping test
186 const pingTest = () => {
187 workerInfo.instance.postMessage({
188 type: 'PING',
189 timestamp: Date.now()
190 });
191 };
192
193 const handlePong = (event) => {
194 if (event.data.type === 'PONG') {
195 clearTimeout(timeoutId);
196 workerInfo.instance.removeEventListener('message', handlePong);
197 workerInfo.healthy = true;
198 resolve(workerInfo);
199 }
200 };
201
202 workerInfo.instance.addEventListener('message', handlePong);
203 pingTest();
204 });
205 }
206
207 // Periodic worker health checking
208 startHealthChecking() {
209 this.healthCheckTimer = setInterval(() => {
210 this.performHealthCheck();
211 }, this.options.healthCheckInterval);
212 }
213
214 performHealthCheck() {
215 this.workers.forEach((workerInfo, workerId) => {
216 const timeSinceLastActivity = Date.now() - workerInfo.lastActivity;
217
218 // Check if worker is responsive
219 if (timeSinceLastActivity > this.options.healthCheckInterval * 2) {
220 console.warn(`Worker ${workerId} appears unresponsive`);
221 this.recreateWorker(workerId);
222 }
223
224 // Check error rate
225 const errorRate = workerInfo.errorCount / Math.max(workerInfo.taskCount, 1);
226 if (errorRate > 0.1) { // > 10% error rate
227 console.warn(`Worker ${workerId} has high error rate: ${(errorRate * 100).toFixed(2)}%`);
228 }
229 });
230 }
231
232 getHealthyWorker() {
233 const healthyWorkers = Array.from(this.workers.values()).filter(w => w.healthy);
234
235 if (healthyWorkers.length === 0) {
236 return null;
237 }
238
239 // Select the worker with the fewest active tasks
240 return healthyWorkers.reduce((best, current) =>
241 current.activeTasks.size < best.activeTasks.size ? current : best
242 );
243 }
244
245 sleep(ms) {
246 return new Promise(resolve => setTimeout(resolve, ms));
247 }
248
249 generateTaskId() {
250 return `task_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
251 }
252
253 // Resource cleanup
254 terminate() {
255 if (this.healthCheckTimer) {
256 clearInterval(this.healthCheckTimer);
257 }
258
259 this.workers.forEach(workerInfo => {
260 workerInfo.instance.terminate();
261 });
262
263 this.workers.clear();
264 }
265
266 // System statistics
267 getSystemStats() {
268 const workers = Array.from(this.workers.values());
269
270 return {
271 totalWorkers: workers.length,
272 healthyWorkers: workers.filter(w => w.healthy).length,
273 totalTasks: workers.reduce((sum, w) => sum + w.taskCount, 0),
274 activeTasks: workers.reduce((sum, w) => sum + w.activeTasks.size, 0),
275 totalErrors: workers.reduce((sum, w) => sum + w.errorCount, 0),
276 failedTasksCount: this.failedTasks.size,
277 averageErrorRate: workers.length > 0
278 ? (workers.reduce((sum, w) => sum + (w.errorCount / Math.max(w.taskCount, 1)), 0) / workers.length * 100).toFixed(2) + '%'
279 : '0%'
280 };
281 }
282}1// 3DRenderingApp.js
2class WebWorker3DRenderer {
3 constructor(canvasId) {
4 this.canvas = document.getElementById(canvasId);
5 this.ctx = this.canvas.getContext('2d');
6 this.workerPool = new WorkerPool('3d-renderer-worker.js', 4);
7
8 this.scene = {
9 objects: [],
10 camera: { x: 0, y: 0, z: -10 },
11 lights: [{ x: 5, y: 5, z: -5, intensity: 1 }]
12 };
13
14 this.setupScene();
15 }
16
17 setupScene() {
18 // Add 3D objects to the scene
19 this.scene.objects = [
20 { type: 'cube', position: { x: 0, y: 0, z: 0 }, rotation: { x: 0, y: 0, z: 0 } },
21 { type: 'sphere', position: { x: 3, y: 0, z: 2 }, radius: 1 },
22 { type: 'cylinder', position: { x: -3, y: 0, z: 1 }, height: 2, radius: 0.5 }
23 ];
24 }
25
26 async render() {
27 const startTime = performance.now();
28
29 // Split screen into tiles for parallel rendering
30 const tileSize = 64;
31 const tilesX = Math.ceil(this.canvas.width / tileSize);
32 const tilesY = Math.ceil(this.canvas.height / tileSize);
33
34 const renderPromises = [];
35
36 for (let tileY = 0; tileY < tilesY; tileY++) {
37 for (let tileX = 0; tileX < tilesX; tileX++) {
38 const tileData = {
39 type: 'RENDER_TILE',
40 tile: {
41 x: tileX * tileSize,
42 y: tileY * tileSize,
43 width: Math.min(tileSize, this.canvas.width - tileX * tileSize),
44 height: Math.min(tileSize, this.canvas.height - tileY * tileSize)
45 },
46 scene: this.scene,
47 screenDimensions: {
48 width: this.canvas.width,
49 height: this.canvas.height
50 }
51 };
52
53 renderPromises.push(
54 this.workerPool.execute(tileData).then(result => ({
55 ...result,
56 tileX: tileX,
57 tileY: tileY
58 }))
59 );
60 }
61 }
62
63 // Wait for all tiles
64 const renderedTiles = await Promise.all(renderPromises);
65
66 // Compose tiles on canvas
67 renderedTiles.forEach(tile => {
68 const imageData = new ImageData(
69 new Uint8ClampedArray(tile.pixelData),
70 tile.width,
71 tile.height
72 );
73
74 this.ctx.putImageData(imageData, tile.x, tile.y);
75 });
76
77 const renderTime = performance.now() - startTime;
78 console.log(`Frame rendered in ${renderTime.toFixed(2)}ms`);
79 }
80
81 // Scene animation
82 animate() {
83 this.scene.objects.forEach(obj => {
84 if (obj.rotation) {
85 obj.rotation.y += 0.02;
86 obj.rotation.x += 0.01;
87 }
88 });
89
90 this.render().then(() => {
91 requestAnimationFrame(() => this.animate());
92 });
93 }
94}
95
96// 3d-renderer-worker.js
97class Tile3DRenderer {
98 constructor() {
99 self.onmessage = (event) => {
100 this.handleRenderTask(event.data);
101 };
102 }
103
104 handleRenderTask(data) {
105 if (data.type === 'RENDER_TILE') {
106 const result = this.renderTile(data.tile, data.scene, data.screenDimensions);
107
108 self.postMessage({
109 type: 'TASK_COMPLETE',
110 result: result
111 });
112 }
113 }
114
115 renderTile(tile, scene, screenDimensions) {
116 const { x, y, width, height } = tile;
117 const pixelData = new Uint8ClampedArray(width * height * 4);
118
119 for (let py = 0; py < height; py++) {
120 for (let px = 0; px < width; px++) {
121 const screenX = x + px;
122 const screenY = y + py;
123
124 // Transform screen coordinates to ray
125 const ray = this.screenToRay(screenX, screenY, screenDimensions, scene.camera);
126
127 // Ray tracing
128 const color = this.traceRay(ray, scene);
129
130 const pixelIndex = (py * width + px) * 4;
131 pixelData[pixelIndex] = color.r; // Red
132 pixelData[pixelIndex + 1] = color.g; // Green
133 pixelData[pixelIndex + 2] = color.b; // Blue
134 pixelData[pixelIndex + 3] = 255; // Alpha
135 }
136 }
137
138 return {
139 x: x,
140 y: y,
141 width: width,
142 height: height,
143 pixelData: Array.from(pixelData)
144 };
145 }
146
147 screenToRay(screenX, screenY, screenDimensions, camera) {
148 const normalizedX = (screenX / screenDimensions.width) * 2 - 1;
149 const normalizedY = 1 - (screenY / screenDimensions.height) * 2;
150
151 return {
152 origin: camera,
153 direction: {
154 x: normalizedX,
155 y: normalizedY,
156 z: 1
157 }
158 };
159 }
160
161 traceRay(ray, scene) {
162 let closestDistance = Infinity;
163 let hitColor = { r: 0, g: 0, b: 50 }; // Background
164
165 // Check collision with objects
166 scene.objects.forEach(obj => {
167 const distance = this.rayObjectIntersection(ray, obj);
168
169 if (distance > 0 && distance < closestDistance) {
170 closestDistance = distance;
171 hitColor = this.calculateObjectColor(obj, ray, distance, scene.lights);
172 }
173 });
174
175 return hitColor;
176 }
177
178 rayObjectIntersection(ray, obj) {
179 switch (obj.type) {
180 case 'sphere':
181 return this.raySphereIntersection(ray, obj);
182 case 'cube':
183 return this.rayCubeIntersection(ray, obj);
184 default:
185 return -1;
186 }
187 }
188
189 raySphereIntersection(ray, sphere) {
190 const dx = ray.origin.x - sphere.position.x;
191 const dy = ray.origin.y - sphere.position.y;
192 const dz = ray.origin.z - sphere.position.z;
193
194 const a = ray.direction.x * ray.direction.x +
195 ray.direction.y * ray.direction.y +
196 ray.direction.z * ray.direction.z;
197
198 const b = 2 * (dx * ray.direction.x + dy * ray.direction.y + dz * ray.direction.z);
199 const c = dx * dx + dy * dy + dz * dz - sphere.radius * sphere.radius;
200
201 const discriminant = b * b - 4 * a * c;
202
203 if (discriminant < 0) return -1;
204
205 const t1 = (-b - Math.sqrt(discriminant)) / (2 * a);
206 const t2 = (-b + Math.sqrt(discriminant)) / (2 * a);
207
208 return t1 > 0 ? t1 : (t2 > 0 ? t2 : -1);
209 }
210
211 calculateObjectColor(obj, ray, distance, lights) {
212 // Simplified lighting
213 const baseColor = obj.type === 'sphere'
214 ? { r: 255, g: 100, b: 100 }
215 : { r: 100, g: 255, b: 100 };
216
217 // Lighting simulation
218 const lightIntensity = 0.7 + 0.3 * Math.sin(distance * 0.1);
219
220 return {
221 r: Math.floor(baseColor.r * lightIntensity),
222 g: Math.floor(baseColor.g * lightIntensity),
223 b: Math.floor(baseColor.b * lightIntensity)
224 };
225 }
226}
227
228new Tile3DRenderer();Web Workers enable:
Web Workers are a powerful tool for creating performant, responsive web applications that can rival native applications in terms of smooth operation.