How to Break Up an Expensive Computation Task in JavaScript

Vladimir Topolev
JavaScript in Plain English
3 min readSep 7, 2021

--

JavaScript is a single-threaded language, which means if we start completing the long-running task, it blocks another part of the application to be completed. On the front-end side, it means that UI will be frozen what definitely is an irritating part for users. On the server-side, it means that the server stops handling new upcoming requests from users and it’s getting unresponsive as well. We as developers always try to mitigate it and should keep it in mind.

One way to avoid it is to move the completion of a long-running task in separate web workers (on the client-side) or spawn separate child process with their own event-loop (on the server-side), but there’s another way to complete it — using asynchronous behavior of JavaScript.

Break up long-running process on client-side

Here Promise and requestAnimationFrame come to play together.

Let’s imagine, that we have an animated rectangle using CSS rules:

We also added a script with an infinite loop. I believe, that you know when we start to complete this script, the page will become unresponsive and we never see in console Here word that is supposed to be printed by console.log

function completeInfiniteTask(){
while(true){}
}
completeInfiniteTask();
console.log('Here');

Well, let’s develop nextFrame function, which would have the possibility to interrupt this infinite loop and make a way to complete another part of the application. It looks like that:

function nextFrame() {
return new Promise((res) => {
requestAnimationFrame(() => res())
});
}

In this case, we explicitly ask a browser to draw the next animation frame and Promise is going to be resolved as soon as the browser draws this frame.

Of course, we need to rewrite our long-running task and turn it out in async function and define when exactly we would like temporarily to interrupt. For example, we may interrupt this loop each 5th step:

async function completeInfiniteTask(){
let count = 0;
while(true){
if (++count % 5 === 0){
await nextFrame()
}
}
}
completeInfiniteTask();
console.log('Here');

In this time, JS has enough time to complete simultaneously long-running-task and draws the next frame of the animated rectangle, just because JS is not only a single-threaded language, but it also supports asynchrony.

Here console.log printed Here almost immediately and there’s no lag in rectangle animation, despite that in the background JS still continues to process infinite loop, since each 5th step makes a way for other parts of the application to be processed.

Break up long-running process on server-side

The approach actually is the same but requestAnimationFrame isn’t available on the server-side and it’s included only in a browser API. But in this case, we may use setImmediate instead:

function nextFrame() {
return new Promise((res) => {
setImmediate(() => res())
});
}

Where may it be used?

Let’s imagine, that we’re developing an application where a user uploads a CSV file and the application needs to parse each row, during this process we should show a progress bar. It’s a good candidate to use nextFrame function, where after parsing N rows we may break up the parsing process and show the actual state of the progress bar.

This method also has such frameworks as TensorFlow , which allows developers to break up the long-running process of model learning.

More content at plainenglish.io

--

--

Addicted Fullstack JS engineer. Love ReactJS and everything related to animation