
Web Workers
Planted:
Status: seed
Running computationally expensive functions on a web page can make the page slow or stop responding (blocked). This is because the main thread running the UI (User Interface) also executes all the JavaScript. Being on 1 thread means the engine can only do 1 thing at a time. If the engine is executing an expensive function, it can't also respond to the user attempting to scroll a page.
This can be solved by moving the execution of expensive functions to a different thread. The Web Workers API is a Web API that allows JavaScript to run in a background thread separate from the main execution thread. The worker constructor creates a web worker and returns an object that can send and receive messages.
/index.js
const myWorker = new Worker('myWorker.js'); myWorker.postMessage('...'); myWorker.onmessage = function (event) {};
/myWorker.js
onmessage = function (event) {}; postMessage('...');
Problem
Imagine we created a 10 second timer. Every second, the count is outputted to the console.
localhost:3000
const startButton = document.querySelector('button#timer'); function secondsFromStart(startMS) { return Math.floor((Date.now() - startMS) / 1000); } function onTimerEnd(intervalId) { clearInterval(intervalId); startButton.disabled = false; } function onInterval(startMS, intervalId) { const seconds = secondsFromStart(startMS); console.log(seconds); if (seconds > 9) { onTimerEnd(intervalId); } } function startTimer() { const startMS = Date.now(); const id = setInterval(() => { onInterval(startMS, id); }, 1000); } startButton.onclick = function () { startButton.disabled = true; console.clear(); startTimer(); };
Expensive Function
Now imagine we were required to execute an expensive function while the timer is running.
To mock an expensive function, a while
loop that runs for 3 seconds will be used.
/index.js
function expensiveFunc() { const laterMS = Date.now() + 3000; while (Date.now() < laterMS) {} }
If you start the timer, then start the expensive function, you will notice the output is missing some numbers.
When the counter has started, every second, the engine outputs a number to the console.
When the expensive function is invoked, it starts executing the while
loop.
When it comes time to output the next number, the engine can't do it.
It needs to 1st complete the while
loop before it can continue with the counting.
localhost:3000
const startButton = document.querySelector('button#timer'); const expensiveButton = document.querySelector('button#expensive'); function secondsFromStart(startMS) { return Math.floor((Date.now() - startMS) / 1000); } function onTimerEnd(intervalId) { clearInterval(intervalId); startButton.disabled = false; } function onInterval(startMS, intervalId) { const seconds = secondsFromStart(startMS); console.log(seconds); if (seconds > 9) { onTimerEnd(intervalId); } } function startTimer() { const startMS = Date.now(); const id = setInterval(() => { onInterval(startMS, id); }, 1000); } startButton.onclick = function () { startButton.disabled = true; console.clear(); startTimer(); }; function expensiveFunc() { console.log('expensiveFunc invoked'); const laterMS = Date.now() + 3000; while (Date.now() < laterMS) {} console.log('expensiveFunc complete'); } expensiveButton.onclick = function () { expensiveButton.disabled = true; expensiveFunc(); expensiveButton.disabled = false; };
Solution
A solution to this problem is to move the expensive function to a different thread using a worker:
- 1. Create a worker containing the function,
- 2. Send a message to the worker when the function need to be invoked,
- 3. Have the worker execute the function and
- 4. When complete, have the worker send back the results of the function.
Now the count is no longer blocked when the expensive function is executed.
React
A Web Worker can also be used within a React Component via the useRef
hook.
Where to Next?


