Web Worker in TypeScript

Web Worker in TypeScript

This article explains Web Workers in TypeScript.

You can learn about the concept of Web Workers and various usage tips with examples.

YouTube Video

Worker in TypeScript

In TypeScript, a Worker is a mechanism for performing processing in the background, separate from the main thread, leveraging the JavaScript Web Workers API. This allows heavy computations and asynchronous tasks to be executed without affecting the operation of the user interface.

A Worker operates in parallel with the main thread (UI thread) and can exchange data between threads via messages. Even in TypeScript, you can use Worker while writing type-safe code.

Basic Usage of Worker

  1. Creating a Worker

    Create a Worker instance and execute the specified script. Normally, the script is defined in a separate file.

  2. Message Exchange

    Send and receive messages between the main thread and the Worker thread using postMessage and onmessage.

Example: Basic Worker Implementation

  1. worker.ts: Script for Worker
1// worker.ts
2self.onmessage = (event) => {
3    const data = event.data;
4    const result = data.num1 + data.num2;
5    self.postMessage(result); // Return the result to the main thread
6};
  1. main.ts: Script to use Worker in Main Thread
 1// main.ts
 2const worker = new Worker(
 3    new URL('./worker.ts', import.meta.url),
 4    { type: 'module' }
 5);
 6
 7worker.onmessage = (event) => {
 8    console.log("Result from worker:", event.data); // Receive message from the worker
 9};
10
11worker.postMessage({ num1: 10, num2: 20 }); // Send message to the worker
  • In this example, worker.ts runs in a separate thread, calculates the sum of num1 and num2, and returns it to the main thread. The main thread receives the result and outputs it to the console.
  • When you specify type: 'module', the Worker script is interpreted as an ES module, allowing you to use import and export. This allows you to handle module structures without using the traditional importScripts().

Points to Consider When Using Workers in TypeScript

Adding Type Definitions

In TypeScript, define data types to ensure type-safe interaction during message sending and receiving.

 1// Define data types
 2interface WorkerData {
 3    num1: number;
 4    num2: number;
 5}
 6
 7interface WorkerResult {
 8    result: number;
 9}
10
11// worker.ts
12self.onmessage = (event: MessageEvent<WorkerData>) => {
13    const data = event.data;
14    const result = data.num1 + data.num2;
15    const message: WorkerResult = { result };
16    self.postMessage(message); // Send the result in a type-safe manner
17};
18
19// main.ts
20const worker = new Worker(new URL('./worker.ts', import.meta.url), { type: 'module' });
21
22// Type the event from the worker
23worker.onmessage = (event: MessageEvent<WorkerResult>) => {
24  console.log("Result from worker:", event.data); // event.data is number
25};
26
27// Send typed data to the worker
28const message: WorkerData = { num1: 10, num2: 20 };
29worker.postMessage(message);
  • By specifying the type parameter of MessageEvent, you can clearly define the type of data to receive. This allows data to be exchanged with type safety.

Setting Up Webpack or Vite

When using Worker in TypeScript, bundlers like Webpack or Vite may be required. By using these tools, you can properly bundle the Worker script and make it available from the main thread.

For example, when using Vite, use import.meta.url to correctly import the Worker.

1const worker = new Worker(new URL('./worker.ts', import.meta.url), { type: 'module' });
  • This ensures that the bundled Worker script is loaded correctly, enabling processing leveraging Worker.

Considerations for Messaging and Concurrency

  1. Data Copying

    When sending and receiving messages between the main thread and the Worker thread, data is copied. When dealing with complex data such as objects, efficiency needs to be considered. Frequently exchanging large amounts of data can degrade performance.

  2. Transferable Objects

    Some objects, such as ArrayBuffer, are called Transferable objects. Transferable objects can be transferred to the Worker instead of being copied during messaging. This allows you to avoid the overhead of data copying.

1const worker = new Worker(new URL('./worker.ts', import.meta.url), { type: 'module' });
2
3const buffer = new ArrayBuffer(1024);
4worker.postMessage(buffer, [buffer]); // Transfer ownership to the Worker
5
6console.log(buffer.byteLength); // 0 (ownership moved)
  • By passing [buffer] as the second argument to worker.postMessage(), the buffer is transferred to the Worker instead of being copied.

  • After this, the buffer on the main thread becomes empty (with byteLength 0) and can only be used by the Worker.

Terminating a Worker

The Worker should be terminated after use to minimize memory consumption. You can terminate a Worker by using the terminate method.

1const worker = new Worker(new URL('./worker.ts', import.meta.url), { type: 'module' });
2// ...
3
4worker.terminate(); // Terminate the Worker
  • This code terminates the Worker by using the terminate method.

Exception Handling in Worker

If an error occurs within a Worker, you can handle the error using the onerror event.

1const worker = new Worker(new URL('./worker.ts', import.meta.url), { type: 'module' });
2
3worker.onerror = (error) => {
4    console.error("Error in Worker:", error.message);
5};
  • This code catches and handles errors that occur inside the Worker using the onerror event.

Summary

By using Worker in TypeScript, you can execute heavy tasks in the background while keeping the main thread smooth. By utilizing type definitions, message exchanges can also be done in a type-safe manner. By paying attention to data exchange and thread management, you can achieve performance improvements and efficient concurrency.

You can follow along with the above article using Visual Studio Code on our YouTube channel. Please also check out the YouTube channel.

YouTube Video