What Are Web Workers and How to Leverage Them for Optimized Frontend Performance

What Are Web Workers and How to Leverage Them for Optimized Frontend Performance

A Guide to Enhancing Web Application Performance with Background Threads

Hello guys, Vinyl here again! 👋

Welcome back to my blog. I know it's been a while, but I’m excited to share some of my latest findings and learnings while working on one projects I’ve been on with this year — a template playground used to draft, test, and experiment with smart legal contracts and documents. Today, we’ll dive into Web Workers: what they are, how they work, and how you can use them to supercharge your frontend projects.

Okay so let’s imagine you’re at a bar to get some beer right, and the bartender (your main thread) has to take orders, prepare the order, and clean the counter all at once. If they’re busy brewing a complicated order (heavy computation), everyone else in line has to wait—frustrating, right? Now imagine the bartender has an assistant (a Web Worker) who takes care of cleaning and organizing the pint glasses in the background while the bartender focuses on taking and making orders. This teamwork ensures smoother operations.

That’s just a short overview. I know you might thinking an API from the description lol, No, they are not! Let’s dive right in.

What Are Web Workers?

Web Workers in web development are exactly like that assistant. They handle heavy-lifting tasks in the background, freeing the main thread to keep your app responsive and smooth. In this article, we’ll dive into Web Workers, explore their key functions, explain how to navigate them, and use three real-world scenarios to demonstrate their power in frontend development. I’ll also give tips for using Web Workers in other frameworks like Vue because the primary use case here would be React.

The Three Types of Web Workers

Before diving into how to use Web Workers, let’s understand the three main types:

  1. Dedicated Workers: These are specific to a single script and are the most commonly used workers. They’re perfect for tasks like background computations or handling API calls for one app instance.

    Example: Compressing data for a specific user session.

  2. Shared Workers: These can be shared between multiple scripts or browser tabs, making them ideal for tasks that need cross-tab communication, such as syncing data across tabs.

    Example: Keeping user session data consistent across multiple browser tabs.

  3. Service Workers: Unlike Dedicated and Shared Workers, these intercept network requests and act as a proxy between your app and the network. They’re commonly used for caching and offline support.

    Example: Serving cached templates when a user is offline.

You can read more about these types in MDN’s Web Workers Documentation.

Diagram comparing web processes with and without a worker. The left side shows a linear flow, while the right side incorporates a worker for parallel processing, optimizing the workflow.

To know which worker to use, consider the scope of your task:

  • Use Dedicated Workers for isolated, single-script tasks.

  • Use Shared Workers for multi-tab communication.

  • Use Service Workers for network-related tasks like caching or offline capabilities.

The key advantage of Web Workers is their ability to offload these tasks from the main thread, ensuring a smooth user experience. Communication between the main thread and the worker occurs via a messaging system using the postMessage and onmessage APIs.

Key Web Worker Functions

  1. onmessage: Handles messages sent from the main thread to the worker.
self.onmessage = (event) => {
  console.log('Message received from main thread:', event.data);
};
  1. postMessage: Sends messages from the worker back to the main thread.
self.postMessage('Task completed');
  1. terminate: Stops the worker from running.
worker.terminate();
  1. Error Handling: Catches errors in the worker.
self.onerror = (error) => {
  console.error('Worker error:', error.message);
};

Other useful functions include importScripts for loading external scripts, self.close for shutting down the worker, and setTimeout/setInterval for timed operations. Refer to the documentation for further details if needed.

Example Use Cases in a Web Playground Project

Here are three practical scenarios where Web Workers can significantly enhance the sample Template Playground project:

Case 1: API Calls for Templates Data

Fetching templates data from APIs can result in huge datasets that need to be parsed before use. This can block the UI thread if done directly.

1. Create the Worker File: Create dataParser.worker.js.

// dataParser.worker.js
self.onmessage = (event) => {
  const { rawData } = event.data;
  const parsedData = rawData.map((template) => ({
    name: template.name,
    tag: template.tag,
  }));

  self.postMessage(parsedData);
};

2. Use the Worker in React:

import React, { useState } from 'react';

export default function templateDataParser({ rawData }) {
  const [parsedData, setParsedData] = useState([]);

  const parseData = () => {
    const worker = new Worker(new URL('./dataParser.worker.js', import.meta.url));
    worker.postMessage({ rawData });

    worker.onmessage = (event) => {
      setParsedData(event.data);
      worker.terminate();
    };
  };

  return (
    <div>
      <button onClick={parseData}>Template Parsed Data</button>
      <pre>{JSON.stringify(parsedData, null, 2)}</pre>
    </div>
  );
}

Case 2: URL Compression and Decompression

To allow users to share their templates via compact URLs, Web Workers can handle the compression and decompression efficiently.

1. Create the Worker File: Create urlCompressor.worker.js.

// urlCompressor.worker.js
import LZString from 'lz-string';

self.onmessage = (event) => {
  const { action, data } = event.data;
  let result;

  if (action === 'compress') {
    result = LZString.compressToEncodedURIComponent(data);
  } else if (action === 'decompress') {
    result = LZString.decompressFromEncodedURIComponent(data);
  }

  self.postMessage(result);
};

2. Use the Worker in React:

import React, { useState } from 'react';

export default function URLCompressor({ template }) {
  const [compressedURL, setCompressedURL] = useState('');

  const compressTemplate = () => {
    const worker = new Worker(new URL('./urlCompressor.worker.js', import.meta.url));
    worker.postMessage({ action: 'compress', data: template });

    worker.onmessage = (event) => {
      setCompressedURL(event.data);
      worker.terminate();
    };
  };

  return (
    <div>
      <button onClick={compressTemplate}>Compress Template</button>
      <pre>{compressedURL}</pre>
    </div>
  );
}

Case 3: Handling Loading Animations for Templates

While loading multiple templates, Web Workers can process metadata or configurations asynchronously.

1. Create the Worker File: Create templateLoader.worker.js.

// templateLoader.worker.js
self.onmessage = (event) => {
  const { templates } = event.data;
  const loadedTemplates = templates.map((template) => {
    return { ...template, loadedAt: new Date() };
  });

  self.postMessage(loadedTemplates);
};

2. Use the Worker in React:

import React, { useState } from 'react';

export default function TemplateLoader({ templates }) {
  const [loadedTemplates, setLoadedTemplates] = useState([]);

  const loadTemplates = () => {
    const worker = new Worker(new URL('./templateLoader.worker.js', import.meta.url));
    worker.postMessage({ templates });

    worker.onmessage = (event) => {
      setLoadedTemplates(event.data);
      worker.terminate();
    };
  };

  return (
    <div>
      <button onClick={loadTemplates}>Load Templates</button>
      <pre>{JSON.stringify(loadedTemplates, null, 2)}</pre>
    </div>
  );
}

These are three scenarios where Web Workers can improve your work. Feel free to try them in your own project and experiment.

Tips for Using Web Workers in Other Frameworks

  • Vue: Use the worker-loader plugin and call the worker within Vue components.

  • Angular: Leverage Angular’s built-in Web Worker support with the ng generate web-worker command.

  • Svelte: Use the vite-plugin-svelte loader to import and use workers seamlessly.

Conclusion

Viola, You’ve definitely come to the end now! 🎉 Web Workers are like your app’s secret assistants right, quietly handling the heavy lifting while your main thread focuses on providing a great user experience. By using Web Workers in scenarios like URL compression, API calls, and data preprocessing and more, you can significantly improve your app’s responsiveness and make the experience smoother for your users.

So, don’t wait—start experimenting with Web Workers today and unlock the full potential of your web applications! See you next time! 👋

References