JavaScript Async - DoEvents & Microtasks
Written by Ian Elliot   
Monday, 19 August 2019
Article Index
JavaScript Async - DoEvents & Microtasks
Reentrancy

 

Reentrancy

A big problem of approaches like doEvents and hence to async and wait is that they provide no obvious protection against the reentrancy problem. Reentrancy is what it sounds like – it is when a thread enters a section of code that another thread is already executing. In a single-threaded environment this can only happen if you have an instruction such as await or doEvents which pauses the code and provides the opportunity to start it once again. In this case the same thread starts to execute the code it hasn’t finished.

In most cases code is not written to be reentrant and the result is usually some unexpected behavior if not a system crash.

The question is, what happens in JavaScript if a function is reentered?

The answer is surprisingly simple – functions are largely reentrant in JavaScript.

For example, consider the program in the previous section that computes Pi using doEvents to keep the UI responsive. Suppose the user clicks the button more than once. To make it easier to see what is going on change the doEvent function to read:

function doEvents() {
  return pause(5000);
}

This makes it easier to see what is going on by inserting a longer pause. You might think that the result would be a mess as the for loop that is suspended by the await is restarted by the user clicking the button a second time. In fact the for loop isn’t restarted. Every function evocation in JavaScript starts the execution of the function off from the start, but each resume after the await restores all of the variables back to their original values. As this can only happen if the UI thread has been freed by the new invocation being paused by an await, the two invocations take turns running.

This largely makes JavaScript reentrancy safe, but not completely so.

Consider this version of computePi:

state = {};
state.k = 0;
state.pi = 0;
async function computePi() {
  state.pi = 0;
  for (state.k = 1; state.k <= 10000000; state.k++) { 
   state.pi += 4 * Math.pow(-1, state.k + 1) / 
(2 * state.k - 1); if (Math.trunc(state.k / 1000) * 1000 === state.k) { result.innerHTML = state.pi; count.innerHTML = state.k; await doEvents();
} } }

The only change is that now the result is being stored in a global state object. If you try the same experiment what you will find is that now clicking the button a second time resets the computation back to zero.

What is happening is that the global state object is being shared between all of the invocations of the computePi function. When a new invocations starts it zeros the properties of the state object and when the old invocation resumes it uses the same, newly zeroed, state object. Sharing global objects isn’t a good idea unless you have some special behavior in mind.

If we change state to be a local variable:

async function computePi() { 
 var state = {};
 state.k = 0;
 state.pi = 0;

Then we return to the previous case where the object that state references is restored when the thread resumes after the await with the effect each time the user clicks the button another independent computation starts running.

Worker Threads

There is nothing unique about the UI thread when it comes to async and await. You can define async functions in a Worker thread and await them in the usual way. Notice that in this case the event queue that is involved doesn’t have any events from the UI and this is a simplification.

You can use the doEvents approach to periodically free up the Worker thread so that it can respond to any messages sent to it from another thread.

For example we can implement the thread control pattern from Chapter 5 using doEvents:

async function computePiAsync() {
    var k;
    for (k = 1; k <= 1000000; k++) {
        pi += 4 * Math.pow(-1, k + 1) / (2 * k - 1);
        if (Math.trunc(k / 1000) * 1000 === k)
            await doEvents();
    }    
}
function doEvents() {
   return pause(0);
}
async function pause(t) {
   var promise = new Promise(
		   function (resolve, reject) {
	             setTimeout(
		       function () { 
		         resolve();
	             }, t); 
                   });
    return promise;
}

Notice that this is essentially the program given earlier, but now we use await doEvents in computePiAsync to free the Worker thread. The message event handler is unchanged:

var pi=0;
this.addEventListener("message", function (event) {
    switch (event.data) {
        case "start":
            computePiAsync();
            break;
        case "update":
            postMessage(pi);
            break;
        case "stop":
            close();
            break;
    }
});

 

To make use of the worker we need:

var worker = new Worker("pi.js");
worker.addEventListener("message", 
		         function (event) {             
			              result.innerHTML = event.data;
		         }
);
worker.postMessage("start"); 
setInterval(function () {
	          worker.postMessage("update");
		    }, 100);

Notice that without the doEvents the update message would only be handled at the end of the computation.

You need to include some code to stop the function being started again before it has finished and all of the considerations with reentrant code apply to Worker threads.

 

Summary

  • Async and await appear to make asynchronous code look like synchronous code.

  • Marking a function as async makes it return a Promise at once and its returns are converted into calls to resolve with the returned value.

  • You can await the resolution of any Promise including one returned by an async function. Awaiting a Promise is like performing a return that can be resumed.

  • When an async function performs an await the thread returns to the calling function. It is only when the thread is freed that the await can resume.

  • When the Promise resolves, await unpacks the value returned. If the Promise is rejected the await throws an exception. This allows you to await an asynchronous function as if it was a synchronous function.

  • Using await you can place asynchronous functions within loops and conditionals as if they were synchronous functions.

  • If you know how async and await work you can implement other ways of dealing with results. For example you can run asynchronous functions in parallel or await their combined state.

  • You can use async and await to implement a doEvents function which releases the UI thread to service the task queue. However, because this is based on using a Promise, you have to be careful to allow the UI thread to service the task queue so that the UI is updated.

  • JavaScript functions that do not use global objects are largely reentrant.

  • You can use async and await within Worker threads.

 

Now Available as a Book:

 JavaScript Async

cover

You can buy it from: Amazon

Contents

  1. Modern JavaScript (Book Only)
  2. Events,Standard & Custom
  3. The Callback
      extract - The Callback & The Controller
  4. Custom Async - setTimeout, sendMessage & yield
      extract - Custom Async
      extract - Avoiding State With Yield 
  5. Worker Threads
      extract - Basic Worker ***NEW
      extract - Advanced Worker Threads 
  6. Consuming Promises 
  7. Producing Promises
      extract - The Revealing Constructor Pattern
     
    extract - Returning Promises
     
    extract - Composing Promises
  8. The Dispatch Queue
      extract - Microtasks
  9. Async & Await
      extract -  Basic Async & Await
      extract -  DoEvents & Microtasks
  10. Fetch, Cache & Service Worker
      extract - Fetch  
      extract - Cache
     
    extract -  Service Workers

Also by Ian Elliot 
Just JavaScript: An Idiomatic Approach
Just jQuery: The Core UI 
Just jQuery: Events, Async & AJAX  

To be informed about new articles on I Programmer, sign up for our weekly newsletter, subscribe to the RSS feed and follow us on Twitter, Facebook or Linkedin.

espbook

 

Comments




or email your comment to: comments@i-programmer.info

<ASIN:1871962560>

<ASIN:1871962579>

 <ASIN:1871962528>

<ASIN:1871962501>

 



Last Updated ( Monday, 19 August 2019 )