There is no way to let your algorithm perform synchronously without integrating some sort of yielding inside.
You'd have to adapt your algorithm so that you can pause it, and check if enough time has elapsed, or even let the event-loop to actually loop.
Letting the event loop perform other tasks is my personal favorite, since it also allows the main thread to communicate with the Worker, however, if you are really just wanting for it to verbose the current progress, a simple and synchronous time check is just fine.
Note that recursive functions by their very nature aren't really usable in such a case, because the values the function will generate at the 5th nesting level will not reflect the value you would have gotten by calling the main function with 5
as input.
So getting intermediate values using a recursive function is very tedious.
However, a fibonacci calculator can be rewritten inline really easily:
function fibonacci( n ) {
let a = 1, b = 0, temp;
while( n >= 0 ) {
temp = a;
a = a + b;
b = temp;
n--;
}
return b;
}
From here it's super easy to add the time-elapsed check and quite simple to rewrite it in a way we can pause it in the middle:
async function fibonacci( n ) {
let a = 1, b = 0, temp;
while( n >= 0 ) {
temp = a;
a = a + b;
b = temp;
n--;
if( n % batch_size === 0 ) { // we completed one batch
current_value = b; // let the outside scripts know where we are
await nextTask(); // let the event-loop loop.
}
}
return b;
}
To pause a function in the middle the async/await
syntax comes very handy as it allows us to write a linear code, instead of having several intricated recursive callbacks.
The best thing you can use to let the event-loop to loop is, as demonstrated in this answer, to use a MessageChannel as a next-task scheduler.
Now, you can let your preferred scheduling method get in between these pauses and do the messaging to the main port, or listen for updates from the main thread.
But inlining your function also improves the performances so much that you can calculate the full sequence until Infinity in less than a few ms... (fibonacci( 1476 )
does return Infinity
).
So fibonacci is not a great candidate to demonstrate this issue, let's rather calculate π.
I am borrowing a function to calculate PI from this answer, not judging if it's performant or not, it's simply for the sake of demonstrating how to let the Worker thread pause a long running function.
// Main thread code
const log = document.getElementById( "log" );
const url = generateWorkerURL();
const worker = new Worker( url );
worker.onmessage = ({data}) => {
const [ PI, iterations ] = data;
log.textContent = `π = ${ PI }
after ${ iterations } iterations.`
};
function generateWorkerURL() {
const script = document.querySelector( "[type='worker-script']" );
const blob = new Blob( [ script.textContent ], { type: "text/javascript" } );
return URL.createObjectURL( blob );
}
<script type="worker-script">
// The worker script
// Will get loaded dynamically in this snippet
// first some helper functions / monkey-patches
if( !self.requestAnimationFrame ) {
self.requestAnimationFrame = (cb) =>
setTimeout( cb, 16 );
}
function postTask( cb ) {
const channel = postTask.channel;
channel.port2.addEventListener( "message", () => cb(), { once: true } );
channel.port1.postMessage( "" );
}
(postTask.channel = new MessageChannel()).port2.start();
function nextTask() {
return new Promise( (res) => postTask( res ) );
}
// Now the actual code
// The actual processing
// borrowed from https://stackoverflow.com/a/50282537/3702797
// [addition]: made async so it can wait easily for next event loop
async function calculatePI( iterations = 10000 ) {
let pi = 0;
let iterator = sequence();
let i = 0;
// [addition]: start a new interval task
// which will report to main the current values
// using an rAF loop as it's the best to render on screen
requestAnimationFrame( function reportToMain() {
postMessage( [ pi, i ] );
requestAnimationFrame( reportToMain );
} );
// [addition]: define a batch_size
const batch_size = 10000;
for( ; i < iterations; i++ ){
pi += 4 / iterator.next().value;
pi -= 4 / iterator.next().value;
// [addition]: In case we completed one batch,
// we'll wait the next event loop iteration
// to let the interval callback fire.
if( i % batch_size === 0 ) {
await nextTask();
}
}
function* sequence() {
let i = 1;
while( true ){
yield i;
i += 2;
}
}
}
// Start the *big* job...
calculatePI( Infinity );
</script>
<pre id="log"></pre>