Web Workers API: A Way to Improve Web Performance
Hi Everyone,
It’s been a while since the last time I published an article last year. Today, we’re gonna talk about web workers. Have you used web workers on your web apps on the production level? You might be confused about why I need to implement it.
Intro
Let’s use a study case to make it easier to understand the web worker. I had bad experiences while developing web apps such as bad performance causing several things like nested iteration or complex logic that will affect your web performance. But, ya it was part of my journey. So, time by time I find a way to improve web performance and one of a bunch solutions that I have is using web workers.
But, in this tutorial, I just use a simple example like an iteration until 1K or maybe more, maybe you can image some feature like reading data file Excel from the client side that has data of more than 10K rows. If you only using the main thread, it will a longer until the processing data is completed. So, yeah that is a simple example. I don’t want to show how to read data on file Excel using web workers, but just a simple example.
Also, in this tutorial, I’m using Angular as a front-end framework.
Setup
First of all, please prepare your project or you can clone the project from this repository https://github.com/afifalfiano/angular-tutorial-blogs.git
Next, when using angular we can use the command to generate a web worker. Run this command.
ng generate web-worker app
Actually, you can generate web worker locations in any place. By default, the format command like this ng generate web-worker <location>
Let’s try to use the worker with the default response. Once you generate a web worker, it will automatically generate a script on app.component.ts to debug the web worker like this.
import { Component } from '@angular/core';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss']
})
export class AppComponent {
title = 'angular-tutorial-blogs';
}
if (typeof Worker !== 'undefined') {
// Create a new
const worker = new Worker(new URL('./app.worker', import.meta.url));
worker.onmessage = ({ data }) => {
console.log(`page got message: ${data}`);
};
worker.postMessage('hello');
} else {
// Web Workers are not supported in this environment.
// You should add a fallback so that your program still executes correctly.
}
Then, I have to change the code app.component.ts to fix an error.
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss']
})
export class AppComponent implements OnInit {
title = 'angular-tutorial-blogs';
ngOnInit(): void {
this.initWebWorker();
}
initWebWorker(): void {
if (typeof Worker !== 'undefined') {
// Create a new
const worker = new Worker(new URL('./app.worker', import.meta.url));
worker.onmessage = ({ data }) => {
console.log(`page got message: ${data}`);
};
worker.postMessage('hello');
} else {
// Web Workers are not supported in this environment.
// You should add a fallback so that your program still executes correctly.
}
}
}
Then, run the project.
npm run start
Yeah, you will get a response from the web worker.
Study Case
After the web workers run well, let’s modify the code and make some logic to compare the logic that runs on the main thread and the worker thread.
I have created a simple UI, only button, and have even loaded data iteration. Once the user clicks this button it will generate and render data.
Main Thread
loadData(): void {
console.time('time execution');
const maxNumber = 10_000;
for (let i = 0; i < maxNumber; i++) {
this.data.push(i.toString());
}
console.timeLog('time execution');
}
So, this is the result if we run the logic on the main thread
it takes 1.722900390625 ms. Not too long ya, but can you imagine for 1000K, it will be heavy on the client side, right?
Worker Thread
I updated the worker config like this.
/// <reference lib="webworker" />
addEventListener('message', ({data}) => {
const {method, response} = data;
switch (method) {
case 'greeting':
const intro = `worker response to ${data.response}`;
postMessage({
method,
result: intro
});
break;
case 'logic':
const {maxNumber} = response;
const collect = [];
for (let i = 0; i < maxNumber; i++) {
collect.push(i.toString());
}
postMessage({
method,
result: collect
});
break;
default:
break;
}
});
Then, I also update app.component.ts to post messages on web worker.
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss']
})
export class AppComponent implements OnInit {
title = 'angular-tutorial-blogs';
data: string[] = [];
worker = new Worker(new URL('./app.worker', import.meta.url));
ngOnInit(): void {
this.initWebWorker();
}
initWebWorker(): void {
if (typeof Worker !== 'undefined') {
// Create a new
const body = {
method: 'greeting',
response: 'hello'
};
this.worker.postMessage(body);
this.getResponseWorker();
} else {
// Web Workers are not supported in this environment.
// You should add a fallback so that your program still executes correctly.
}
}
getResponseWorker(): void {
this.worker.onmessage = ({ data }) => {
const {method, result} = data;
switch (method) {
case 'greeting':
console.log(`page got message: ${result}`);
break;
case 'logic':
this.data = [...result];
break;
default:
break;
}
};
}
loadData(): void {
console.time('time execution');
const maxNumber = 10_000;
// for (let i = 0; i < maxNumber; i++) {
// this.data.push(i.toString());
// }
const body = {
method: 'logic',
response: {
maxNumber
}
};
this.worker.postMessage(body)
console.timeLog('time execution');
}
}
Cause I only have one config Worker.js, So I separate them with the config method. When the postMessage is logic it will run logic for 10K iteration.
Here is an explanation of the event worker.
- postMessage : The event worker to sends data on a worker thread.
- onMessage: The event worker to listen real-time data after trigger using postMessage.
- onError: The event worker to handle error data.
For more details, you can go and visit this website https://medium.com/r?url=https%3A%2F%2Fdeveloper.mozilla.org%2Fen-US%2Fdocs%2FWeb%2FAPI%2FWeb_Workers_API%2FUsing_web_workers
So, the result will be like this.
Awesome!. it takes only 0.22~ ms.
Also, you can check on the monitoring performance to check when the worker thread runs on dev tools.
Yeah, I think that’s all for a simple introduction about web workers on angular. Maybe you don’t need it right now, and that’s fine. Nevertheless, I believe in the future this feature will help us a lot.
If you want to see all the codes, you can check in this repository https://github.com/afifalfiano/angular-tutorial-blogs/tree/web-worker
Thank you for reading this tutorial, I hope it is useful and can improve your performance on web apps. Sharing is caring.
References
#Angular #WebWorker #Performance #WebDev #Frontend