Utilizziamo i cookie per migliorare la tua esperienza sul sito
CodeWorlds

Web Workers dla ciężkich obliczeń

Web Workers to technologia umożliwiająca wykonywanie JavaScript w osobnych wątkach, niezależnie od głównego wątku UI (User Interface). Pozwala to na przeprowadzanie ciężkich obliczeń bez blokowania interfejsu użytkownika, co jest kluczowe dla utrzymania responsywności aplikacji webowych.

Dlaczego Web Workers są potrzebne?

Problem jednowątkowej natury JavaScript

1// Problem: Blokujące obliczenia w głównym wątku
2function heavyCalculation(n) {
3  console.log('Rozpoczynanie ciężkich obliczeń...');
4  
5  // Symulacja intensywnych obliczeń
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('Obliczenia zakończone');
14  return result;
15}
16
17// To zablokuje UI na kilka sekund!
18document.getElementById('calculate-button').addEventListener('click', () => {
19  const result = heavyCalculation(5000);
20  document.getElementById('result').textContent = result;
21});
22
23// Podczas obliczeń użytkownik nie może:
24// - Kliknąć innych przycisków
25// - Przewijać strony
26// - Używać animacji
27// - Wpisywać tekst w formularze

Porównanie wydajności

1// Benchmark: Main Thread vs Web Worker
2const performanceTest = {
3  // Test bez Web Worker - blokuje 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 z Web Worker - nie blokuje 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};

Podstawowe implementacje Web Workers

1. Tworzenie prostego Web Worker

1// main.js - główny wątk
2class WebWorkerManager {
3  constructor() {
4    this.worker = null;
5    this.setupWorker();
6  }
7  
8  setupWorker() {
9    // Sprawdzenie wsparcia dla Web Workers
10    if (typeof Worker !== 'undefined') {
11      this.worker = new Worker('worker.js');
12      
13      // Obsługa wiadomości z worker
14      this.worker.onmessage = (event) => {
15        this.handleWorkerMessage(event.data);
16      };
17      
18      // Obsługa błędów
19      this.worker.onerror = (error) => {
20        console.error('Worker error:', error);
21      };
22      
23      console.log('Web Worker został utworzony');
24    } else {
25      console.warn('Web Workers nie są wspierane w tej przeglądarce');
26    }
27  }
28  
29  // Wysyłanie zadania do 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  // Obsługa odpowiedzi z 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  // Zakończenie pracy worker
56  terminateWorker() {
57    if (this.worker) {
58      this.worker.terminate();
59      this.worker = null;
60      console.log('Web Worker został zakończony');
61    }
62  }
63}
64
65// Użycie
66const workerManager = new WebWorkerManager();
67
68// Wysłanie zadania obliczeń
69document.getElementById('start-calculation').addEventListener('click', () => {
70  workerManager.sendTask('CALCULATE_PRIMES', { max: 100000 });
71});

2. Implementacja worker.js

1// worker.js - kod wykonywany w osobnym wątku
2class WorkerCalculations {
3  constructor() {
4    this.setupMessageHandler();
5  }
6  
7  setupMessageHandler() {
8    // Główny handler wiadomości z głównego wątku
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  // Obliczanie liczb pierwszych z raportowaniem postępu
40  calculatePrimes(max) {
41    const primes = [];
42    const updateInterval = Math.floor(max / 100); // Update co 1%
43    
44    for (let i = 2; i <= max; i++) {
45      if (this.isPrime(i)) {
46        primes.push(i);
47      }
48      
49      // Raportowanie postępu
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  // Przetwarzanie obrazu (filtrowanie, transformacje)
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  // Sortowanie dużych tablic
104  sortArray(array) {
105    const startTime = performance.now();
106    
107    // Implementacja merge sort dla lepszej wydajności
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  // Obliczanie Pi metodą Monte Carlo
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 do 1
154      const y = Math.random() * 2 - 1; // -1 do 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  // Metody pomocnicze do komunikacji
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// Inicjalizacja worker
205new WorkerCalculations();

Zaawansowane wzorce Web Workers

1. Worker Pool dla równoległych obliczeń

1// WorkerPool.js - Zarządzanie wieloma worker'ami
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  // Wykonanie zadania z wykorzystaniem 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      // Przekazanie aktualizacji postępu (opcjonalne)
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    // Sprawdzenie czy są zadania w kolejce
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  // Równoległe przetwarzanie dużych zbiorów danych
109  async processInParallel(dataArray, chunkProcessor) {
110    const chunkSize = Math.ceil(dataArray.length / this.poolSize);
111    const chunks = [];
112    
113    // Podział danych na chunks
114    for (let i = 0; i < dataArray.length; i += chunkSize) {
115      chunks.push(dataArray.slice(i, i + chunkSize));
116    }
117    
118    // Równoległe przetwarzanie chunks
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    // Połączenie wyników
130    return results.flat();
131  }
132  
133  generateTaskId() {
134    return `task_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
135  }
136  
137  // Zamknięcie wszystkich worker'ów
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  // Statystyki pool
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// Użycie Worker Pool
160const workerPool = new WorkerPool('calculation-worker.js', 8);
161
162// Przykład równoległego przetwarzania
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      // Funkcja przetwarzania będzie wykonana w worker
169      return chunk.map(x => Math.sqrt(x * x + 1));
170    });
171    
172    console.log('Przetwarzanie zakończone:', results.length);
173    console.log('Stats:', workerPool.getStats());
174  } catch (error) {
175    console.error('Błąd przetwarzania:', error);
176  }
177}

2. Shared Array Buffer dla wydajnej komunikacji

1// SharedArrayBufferWorker.js - Współdzielona pamięć między worker'ami
2class SharedArrayBufferManager {
3  constructor() {
4    this.checkSupport();
5  }
6  
7  checkSupport() {
8    if (typeof SharedArrayBuffer === 'undefined') {
9      console.warn('SharedArrayBuffer nie jest wspierany');
10      this.fallbackToTransferableObjects();
11      return false;
12    }
13    return true;
14  }
15  
16  // Tworzenie współdzielonej pamięci do obliczeń macierzy
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      // Metody pomocnicze
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  // Równoległe mnożenie macierzy
43  async parallelMatrixMultiply(matrixA, matrixB, workerCount = 4) {
44    const rowsA = matrixA.rows;
45    const colsA = matrixA.cols;
46    const colsB = matrixB.cols;
47    
48    // Tworzenie współdzielonej macierzy wynikowej
49    const resultMatrix = this.createSharedMatrixBuffer(rowsA, colsB);
50    
51    // Podział pracy między worker'y
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('Używanie Transferable Objects jako alternatywy');
96    // Implementacja z Transferable Objects
97  }
98}
99
100// matrix-worker.js - Worker do obliczeń macierzy
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    // Mnożenie macierzy dla przydzielonych wierszy
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};

3. Service Worker do Background Processing

1// ServiceWorkerManager.js - Zarządzanie długotrwałymi zadaniami w tle
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        // Oczekiwanie na aktywację service worker
15        await navigator.serviceWorker.ready;
16        
17        this.serviceWorker = registration.active || registration.waiting || registration.installing;
18        this.setupMessageChannel();
19        
20        console.log('Service Worker zarejestrowany i gotowy');
21      } catch (error) {
22        console.error('Błąd rejestracji Service Worker:', error);
23      }
24    }
25  }
26  
27  setupMessageChannel() {
28    this.messageChannel = new MessageChannel();
29    
30    // Obsługa wiadomości od Service Worker
31    this.messageChannel.port1.onmessage = (event) => {
32      this.handleServiceWorkerMessage(event.data);
33    };
34    
35    // Wysłanie portu do Service Worker
36    navigator.serviceWorker.controller?.postMessage(
37      { type: 'INIT_PORT' },
38      [this.messageChannel.port2]
39    );
40  }
41  
42  // Zlecenie długotrwałego zadania do 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  // Przykład: Okresowe synchronizowanie danych
57  scheduleDataSync(interval = 300000) { // 5 minut
58    this.scheduleBackgroundTask({
59      type: 'DATA_SYNC',
60      interval: interval,
61      endpoint: '/api/sync',
62      recurring: true
63    });
64  }
65  
66  // Przykład: Przetwarzanie plików w tle
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(`Zadanie ${task.id} zakończone:`, result);
94    
95    // Aktualizacja UI lub stanu aplikacji
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      // Zapisanie w IndexedDB lub 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    // Symulacja przetwarzania pliku
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    // Implementacja zapisu w IndexedDB
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();

Performance monitoring i debugging

1. Worker Performance Metrics

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  // Monitorowanie wydajności worker'a
15  monitorWorkerPerformance(worker, taskId) {
16    const startTime = performance.now();
17    let memoryBefore = null;
18    
19    // Pomiar pamięci (jeśli dostępny)
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      // Zapisanie metryki czasu wykonania
39      this.metrics.taskExecutionTimes.push({
40        taskId: taskId,
41        executionTime: executionTime,
42        timestamp: Date.now()
43      });
44      
45      // Pomiar pamięci po wykonaniu
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  // Analiza wydajności
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 minuta
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 sekund
126      recommendations.push('Rozważ podział długotrwałych zadań na mniejsze części');
127    }
128    
129    if (memoryTrends?.potentialMemoryLeak) {
130      recommendations.push('Wykryto potencjalny memory leak - sprawdź zarządzanie pamięcią');
131    }
132    
133    if (this.metrics.taskExecutionTimes.length > 1000) {
134      recommendations.push('Duża liczba zadań - rozważ implementację Worker Pool');
135    }
136    
137    return recommendations;
138  }
139}
140
141// Użycie monitoring
142const performanceMonitor = new WorkerPerformanceMonitor();
143
144// Monitorowanie konkretnego worker'a
145const worker = new Worker('calculation-worker.js');
146const monitoredWorker = performanceMonitor.monitorWorkerPerformance(worker, 'task_001');
147
148// Generowanie raportu po pewnym czasie
149setTimeout(() => {
150  const report = performanceMonitor.generatePerformanceReport();
151  console.log('Performance Report:', report);
152}, 30000);

Best practices i optymalizacje

1. Optymalna komunikacja z worker'ami

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  // Batching wiadomości dla lepszej wydajności
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 po timeout jeśli batch nie jest pełny
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    // Grupowanie wiadomości po worker'ach
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    // Wysyłanie zbatchowanych wiadomości
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  // Kompresja dużych danych przed wysłaniem
50  async sendCompressedData(worker, data) {
51    const serializedData = JSON.stringify(data);
52    
53    if (serializedData.length > this.compressionThreshold) {
54      try {
55        // Używanie Compression Streams API (jeśli dostępne)
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 do niezkompresowanych danych
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    // Implementacja kompresji (przykład z 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 - zwrócenie oryginalnych danych
106    return new TextEncoder().encode(data);
107  }
108  
109  // Transferable Objects dla efektywnego transferu
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}

2. Error handling i resilience

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  // Tworzenie worker'a z 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 z 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  // Wykonanie zadania z 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 po sukcesie
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 jeśli potrzeba
74          if (error.type === 'WORKER_DIED') {
75            await this.recreateWorker(workerId);
76          }
77        }
78      }
79    }
80    
81    // Zapisanie zadania jako 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      // Zapisanie zadania jako aktywne
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    // Odrzucenie wszystkich aktywnych zadań
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 dla nowego worker'a
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  // Okresowe sprawdzanie zdrowia worker'ów
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      // Sprawdzenie czy worker jest responsywny
219      if (timeSinceLastActivity > this.options.healthCheckInterval * 2) {
220        console.warn(`Worker ${workerId} appears unresponsive`);
221        this.recreateWorker(workerId);
222      }
223      
224      // Sprawdzenie 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    // Wybór worker'a z najmniejszą liczbą aktywnych zadań
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  // Czyszczenie zasobów
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  // Statystyki systemu
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}

Praktyczne przykłady użycia

1. Aplikacja do renderowania 3D

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    // Dodanie obiektów 3D do sceny
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    // Podział ekranu na tiles dla równoległego renderowania
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    // Oczekiwanie na wszystkie tiles
64    const renderedTiles = await Promise.all(renderPromises);
65    
66    // Składanie tiles na 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  // Animacja sceny
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        // Przekształcenie współrzędnych ekranu na 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 }; // Tło
164    
165    // Sprawdzanie kolizji z obiektami
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    // Uproszczone oświetlenie
213    const baseColor = obj.type === 'sphere' 
214      ? { r: 255, g: 100, b: 100 }
215      : { r: 100, g: 255, b: 100 };
216    
217    // Symulacja oświetlenia
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();

Podsumowanie

Web Workers umożliwiają:

  1. Wykonywanie ciężkich obliczeń bez blokowania UI
  2. Równoległe przetwarzanie dużych zbiorów danych
  3. Lepszą responsywność aplikacji webowych
  4. Skalowalność poprzez Worker Pool
  5. Background processing z Service Workers

Kluczowe zastosowania:

  • Obliczenia matematyczne i naukowe
  • Przetwarzanie obrazów i video
  • Renderowanie 3D i ray tracing
  • Sortowanie i filtrowanie dużych zbiorów danych
  • Kompresja i dekompresja plików
  • Kryptografia i hashing
  • Machine learning inference

Web Workers to potężne narzędzie do tworzenia wydajnych, responsywnych aplikacji webowych, które mogą konkurować z aplikacjami natywnymi pod względem płynności działania.

Vai a CodeWorlds