JavaScript Async - Custom Async
Written by Ian Elliot   
Monday, 05 November 2018
Article Index
JavaScript Async - Custom Async
A Piece of Pi
Using a Callback

 

A Piece of Pi

As a simple example, suppose you want to compute pi to a few digits using the series:

pi=4*(1-1/3+1/5-1/7 ... )

This is very easy to implement, we just need to generate the odd integers but to get pi to a reasonable number of digits you have to compute a lot of terms.

The simple-minded synchronous approach is to write something like:

button1.click(computePi);
function computePi() {
  var pi = 0;
  var k;
  for (k = 1; k <= 100000; k++) {
   pi += 4 * Math.pow(-1, k + 1) / (2 * k – 1);
   result.innerHTML=pi;
   count.innerHTML=k;
  }
}

where the DOM elements are provided by:

<div id="result">
0
</div>
<div id="count">
0
</div>
<button id="button1" onclick>Go</button>

The intention is to display the progress of the calculation by changing the text displayed in the two divs each time through the for loop.

If you try it out what you will find is that the UI freezes for some minutes and nothing is displayed in the web page until the loop finishes and the UI thread is freed to tend to the UI.

Restoring State

This is an example of an unacceptable function that monopolizes the UI thread at the expense of the UI.

To keep the UI responsive, and in this case to see the intermediate results, we have to turn the calculation into an asynchronous function using setTimeout. We do this by breaking the calculation in small chunks – say 1000 iterations each. To do this we need a state object that records the state of the calculation so that it can be resumed:

var state = {};
state.k = 0;
state.pi = 0;

The function is now going to perform 1000 iterations and then update the text in the divs.

To enable the UI to stay responsive, the function then terminates, but not before setting itself up in the event queue ready to perform another 1000 iterations after the UI has been updated:

function computePi() {
  if (state.k >= 100000000) return;
  var i;
  for (i = 0; i < 1000; i++) {
   state.k++;
   state.pi += 4 * Math.pow(-1, state.k + 1)/
                                  (2 * state.k - 1);
  }
  result.innerHTML=state.pi;
  count.innerHTML=state.k;
  setTimeout(computePi, 0);
}

Notice the final setTimeout which ensures that the function restarts.

If you run this version of the computation you will find that not only does the UI remain responsive, you get to see the intermediate values as the calculation proceeds.

Notice that the computePi function is now almost non-blocking in that it returns after doing 1000 iterations.

Non-Global State

Final version in book

Using postMessage

We have already discovered in Chapter Two that the problem with using setTimeout is that it is slow. It can take 4 to 5 milliseconds to get the function that computes Pi restarted. The solution in most cases is to use the postMessage method as described in Chapter Two which has a much smaller overhead.

As already explained, the postMessage method of the Window object was introduced to allow JavaScript to pass data between different windows by using events – that is it fires a message event to let the UI thread know that there is a message waiting. However, as it is possible for a window to send itself a message, it can be used as an alternative to setTimeout as a way to insert events into the event queue. It is slightly more complicated to use, but its only real disadvantage is that it isn't as widely supported as setTimeout. It is supported in IE 8 and later and in browsers of the same vintage.

If you want to make use of the postMessage method and the message event for a zero delay addition to the event queue then the previous function can be rewritten as:

function computePiAsync() {
   var state = {};
   state.k = 0;
   state.pi = 0;
   window.addEventListener("message",computePi, false);
   function computePi() {
    if (state.k >= 100000000) return;
    var i;
    for (i = 0; i < 1000; i++) {
     state.k++;
     state.pi += 4 * Math.pow(-1, state.k + 1) /
                                    (2 * state.k - 1);
    }
    result.innerHTML = state.pi;
    count.innerHTML = state.k;
    window.postMessage("fireEvent", "*");
  }
  window.postMessage("fireEvent", "*");
}

You can use the same technique to turn nearly any long running computation into an asynchronous procedure.

All you have to do is break the computation down into small parts and preserve the state of the computation at the end of each chunk so that it can be restarted. Write the function so that it takes the state object and continues the computation.

This is always possible, even if the task isn't to sum a mathematical series.

For example, if you want to perform a complex database operation, simply save the point in the transaction that you have reached.

<ASIN:1871962560>

<ASIN:1871962579>



Last Updated ( Saturday, 10 November 2018 )