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 sleepfunction 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));