JavaScript is single-threaded (unless you use Web Workers). I.e. the UI is updated after your long-running onclick handler has finished. Using setTimeout()
as proposed in the answer by @plarner7 is a typical solution. However, this approach is not well-suited for recursive functions because
- it changes the control flow of your recursive function and
- you cannot (easily) access the result of a call that was "spawned" with setTimeout().
The trick is to use setTimeout but instead of shifting logic into the setTimeout call we just call SetTimeout to give the UI a chance to update. The general pattern for recursive functions with UI update is:
- define a
sleep
function that behaves similarly to Java's Thread.sleep():
async function sleep(msec) { return new Promise(resolve => setTimeout(resolve, msec)); }
- insert
await sleep(0)
after UI changes
- declare your recursive function as
async function
- otherwise you will not be allowed to call the async sleep function.
I.e. a solution for the posted question looks like this:
async function sleep(msec) {
return new Promise(resolve => setTimeout(resolve, msec));
}
async function getRes(val) {
if(val === finalVal) return;
// Simulates your time-consuming operation
for(let i=0; i < 1000*1000*1000; i++) { i*i*i*i }
let progress = `${Math.floor(val*100 / finalVal)}%`;
$("#result").text(progress);
// THIS LINE DOES THE TRICK and gives the UI a chance to update
await sleep(0);
await getRes(val+1);
}
$("#resbtn").on('click', async () => getRes(0));