Running long async scripts prevents paint of the page. So you need to convert your tasks into "batches" and let the browser process other tasks after running each batch.
Look at this example from https://javascript.info/event-loop :
let i = 0;
function count() {
// do a piece of the heavy job (*)
do {
i++;
progress.innerHTML = i;
} while (i % 1e3 != 0);
if (i < 1e7) {
setTimeout(count);
}
}
count();
Now the heavy task is done in batches of 1000
iterations instead of one batch of 1e7 iterations.
In the code we are saying that:
- Increase
i
if it's not a multiplication of 1000. But the code progress.innerHTML = i
doesn't update the interface before the loop ends.
- As soon as
i
is a multiplication of 1000, the loop breaks, another batch
is "postponed" using setTimeout
and the UI is updated.
You can also move your code into a web worker which runs on a background thread and doesn't interfere with the user interface.
UPDATE: The setTimeout
method shown above works but it's not a perfect solution because:
The main script of JS is run on a single thread and async actions aren't run on another thread, they're just postponed to be run "later". So they "always" block the main script when they're running, even if we do the mentioned hack (the paint is blocked when we're in the do-while loop).
If we want to "really" separate that heavy task from our main script, we should use Web Workers which are really run on another OS-level thread.
So your best choice is to use a Web Worker and communicate the results using postMessage
system.
Note: Web Workers do not have access to the DOM