Make your website faster: web workers

Slow websites are the bane of the internet.

Gav McKenzie
Author
Gav McKenzie
Published
Mar 18, 2020
Topics
How-to, Industry, Engineering

A lot of the time it’sthe connection speed or downloading the 18Mb of JavaScript and adverts you need to browse a basic blog, but sometimes it’sraw processing power getting in the way of awesome user experience.

In this article, we are going to learn about web workers, what they are and how to use them to make your site faster.

The problem

As powerful as JavaScript is, at its core, it’s a single threaded language. This means it can only execute one command at a time. It’spretty bonkers to think that all these powerful and engaging web apps are literally just doing one thing at a time, really really fast.

Sometimes you run into some code that takes a while to run, long enough for a user to really notice it. Because JavaScript can only do one thing at a time, the UI will completely lock up until the code has finished executing.

We came across this problem whilst importing, processing and validating a 160,000 row excel file (don’t ask...).

The file was taking around 2 minutes to process, giving the impression to the user that the site had completely hung. I wanted to update the state of the react UI to show how much progress we had made and what was currently happening. Long loading times are so much more manageable when you feel in control and aware of what is happening. However, setState is an asynchronous method, and it was refusing to execute until the data processing has been completed.

I had tried giving the processor some time to relax and update the UI using setTimeout/setInterval techniques, but to no avail! What is a poor developer to do?

Enter the web worker

Web workers are essentially scripts that run in the background of your app, without affecting the main thread. They communicate with the UI via a messaging system, allowing you to offload intensive tasks and keep the UI fresh and up to date.

Getting started

Web workers must have their own file. With the magic of modern bundling techniques, you may need some kind of web worker loader to make sure your worker doesn’t get bundled into your main app file.

Of course if you are doing this manually you may not run into this problem.

Communication is key

The trick to using web workers is the messaging system. This is how our main app and the worker file will communicate with each other. There’s a small amount of setup at each end.

The main app needs to import and instantiate the web worker. We then need to add event listening for messages from the worker and fire the worker a message to start it running.

// In the main app
const worker = new Worker('/path/to/my/worker.js'); // Import and instantiate
// Listen for messages from the worker
worker.addEventListener('message', function(e) {
console.log('Message from worker received: ', e.data);
}, false);
worker.postMessage('Do some work then'); // Start the worker

On the worker side, we just need to listen and react to messages from the main app.

// In the web worker file
// Listen for messages from the main app
self.addEventListener('message', function(e) {
// Do something with the message data
...
// Post a message back to the main app
self.postMessage(e.data);
}, false);

When you have finished with the worker, it will need to be shut down again so it doesn’t hang around eating memory.

// Shut down the worker
worker.terminate();

Make yourself useful

Messages don’t have to be just text, you can send anything between the worker and the main app. We ended up sending the spreadsheet file reference to the worker, which would periodically fire back messages on its progress to the main app, which could then update the state.

// Main app
// Next.js withWorkers automagically splits this into it’sown file
import UploadWorker from 'lib/uploadWorker/upload.worker';
...
componentDidMount() {
// Start the web worker
this.worker = new (UploadWorker)();
this.worker.addEventListener('message', e => {
this.setState(e.data);
});
}
componentWillUnmount() {
// Close the worker thread
this.worker.terminate();
}
...
handleUpload(file) {
this.worker.postMessage(file);
return null;
}
...

Having this in place changed our app from a hanging mess to a snappy loading state that tells the user exactly what’sgoing on, making them feel safe and in control.

It’snever simple

Of course, as with any technology, there are a bunch of limitations and considerations to keep in mind. Rather than muddy this article with all the different details to remember, we're just going to list a few areas to watch out for.

Transferring data

Data is copied, rather than literally transferred, to the worker. Of course you are a good developer and treat data as immutable I'm sure, but copying a 50Mb file can be quite time consuming, so keep an eye out for lag.

If things are getting laggy, you can use a slightly different format to actually transfer the object, rather than copying it. This uses what is known as Transferrable Objects.

Limited functionality

Web workers have a slightly stripped back amount of power to work with. Whilst most functionality is still available, there are some things you can’t use in the worker context. This might require some creative thinking to get around, using the messaging system.

  • The DOM
  • The window object
  • The document object
  • The parent object

Further reading

If you've run into something strange and it’snot covered here, it’sprobably in one of these links below.

Etch is a web software consultancy based in the UK©2012-2024 Etch Software Ltd