How to Break Up an Expensive Computation Task in JavaScript
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