The is more to say about the whole subject of generators and yield, but this is enough for us to write a version of the Pi program that makes use of the system to save its state. Notice that we can now use the natural for loop and we don't have to make any extra effort to break the program up or save state – we simply have to call yield at suitable intervals:
function* genComputePi() {
var k;
var pi = 0;
for (k = 1; k <= 10000000; k++) {
pi += 4 * Math.pow(-1, k + 1) / (2 * k - 1);
if (Math.trunc(k / 1000) * 1000 === k) yield pi;
}
return pi;
}
The if statement checks to see if k is divisible by 1000 which results in yield being called every 1000 iterations.
Using this version of the function is slightly more difficult because we have to use the generator to create an iterator that we can call next on:
function computePiAsync() {
var computePi = genComputePi(); var pi; function resume() { pi = computePi.next(); result.innerHTML = pi.value; if (!pi.done) setTimeout(resume, 0); return; } setTimeout(resume, 0); return; }
Notice that apart from having to create computePi and make use of next to resume the function this is very like the way that we made use of setTimeout before – except we no longer have to save and use the state information – the system does this for us.
If you are using yield you might as well use postMessage instead of setTimeout as it is likely to be supported by any browser that supports yield:
function computePiAsync() { var computePi = genComputePi(); var pi; window.addEventListener("message",resume, false); function resume() { pi = computePi.next(); result.innerHTML = pi.value; if (!pi.done) window.postMessage("fireEvent", "*"); return; } window.postMessage("fireEvent", "*"); return; }
Notice that in all of these cases closure is being used to allow the inner functions access to the variables of the outer functions.
Summary
If you need to make your own long running functions non-blocking then you can either use another thread or split the computation into short sections.
Threads are more powerful, but not all browsers support them and they can be difficult to implement.
The basic idea in creating a non-blocking function without threads is to make them run in small chunks of time, releasing the UI thread often enough to keep the UI responsive.
The main problem is is to keep the state of the computation so that it can be resumed after it has released the UI thread.
In most cases you need to write the function using a state object to record the important information so that the function resumes from where it left off.
To schedule the next run of the function you can use setTimeout or postMessage to place the function on the event queue.
An alternative way to keep the state of the function is to write it as a generator and use the yield instruction to pause the function and next to resume it.
If you use yield the system will look after the state for you and you can even pause for loops and generally write the function without having to worry about restoring state.