JavaScript Async - The Callback & The Controller
Written by Ian Ellliot   
Monday, 31 December 2018
Article Index
JavaScript Async - The Callback & The Controller
The Problem With Async
Using The Controller

Callbacks are so familiar we forget their true position in the world. Not quite events, but very similar.

This is an extract from the recently published JavaScript Async: Events Callbacks, Promises & Async/Await.

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

Events are the first place you are likely to meet asynchronous functions, but once you have events and an event dispatching system you can't stop there. Any long running function has to be implemented as an asynchronous non-blocking function if the UI is to remain responsive. This causes all sorts of difficulties that go beyond events and event handling.

Most of the rest of this book is about implementing and managing asynchronous functions and we will tackle the same fundamental problem from a number of points of view. In this chapter we take the most basic viewpoint and look at the idea of the callback.

Events versus Asynchronous Functions

There really is no need to go any further than events when you consider asynchronous functions. However, for historical reasons we need to look at asynchronous functions that arise in a non-event form in a slightly different way.

The problem arises because of the need to manage the UI thread. If you have an event handler that needs to do some extended work it has to give up the UI thread now and again to keep the UI functioning. This simple fact colors all of JavaScript programming. It is what makes JavaScript programming difficult. The same is true of any language that has to cope with a single-threaded UI.

The problem is made worse by the fact that long running functions are often long running because they ask the system to do something. In this case another thread or an interrupt is involved to make the process run in the background. For example when you ask for a file to be loaded the UI thread doesn't actually do the file load. Instead the task is handed off to the operating system. The UI thread could just wait for the file to be ready, but this would block the UI and so generally this isn't what happens.

This gives rise to the idea that the behavior of a function call can be be blocking or non-blocking.

What does this mean?

All functions return, eventually, and a blocking function returns when its work is done. A non-blocking function, on the other hand, returns when its work is set in motion.

That is, a blocking function returns with its result, but a non-blocking function simply returns with an expectation that its work will be done sometime in the future and its result will be available.

How can you get the result of a non-blocking function?

This is why general asynchronous programming is difficult. The use of a non-blocking function distorts the flow of control. Normally with a blocking function, when the function returns you can just get on with the algorithm. If the function was downloading a file you can now get on with processing that file. If the function was non-blocking, then you can't get on because the result, the file, isn't available. In this case you have no choice but to give the UI thread back and wait for the result to be available.

The standard solution is to provide a callback function to the non-blocking function.

A callback is a function that the non-blocking process calls when it has really finished its task.

As functions are first class objects in JavaScript, using callbacks is relatively easy – you don't have to invent extra ideas such as delegates (C#), anonymous classes (Java) and lambdas (almost all languages) - to pass a function as a parameter. In fact, you almost certainly have been using callbacks for a long time, perhaps without realizing their connection to events.

A callback is like an event handler which fires when the non-blocking function completes its task.

In fact there is little reason to invent the callback. It would be simpler to define task completion events and register event handlers for the event.

For example, suppose we have getFile function which returns a file after using AJAX to download it. The callback version of the function would be used something like:

getFile(url,callback)

and would return at once and call the callback function sometime later when the file had been downloaded.

This can be converted into an event by introducing an object that an event can be attached to:

var ajax={}; 
ajax.getfile=function(url){ getFile(url,
     function(){
var event = new Event("FileLoaded",
{
'bubbles: true,
'cancelable': true
});
ajax.dispatchEvent(event);
});
};

When the getfile function is used it still returns without blocking, but now it raises the FileLoaded event when the file is ready to be processed. To use the new function you would attach an event handler rather than specify a callback:

ajax.addEventListener("FileLoaded",eventHandler);
getfile(url);

Event handler or callback – its a choice.

coverasync



Last Updated ( Monday, 31 December 2018 )