JavaScript Call-back Functions, Promises and Async/Await

Published Oct 14, 2017Last updated Apr 12, 2018
JavaScript Call-back Functions, Promises and Async/Await

As JavaScript has evolved and changed over the last 5 years, I have found various ways to perform asyncronous operations i.e. when making a request for data to a database that does not (or may) reside on the same machine as the application; or while making a call to an external API used by an application.

With a wide variety of JavaScript libraries to choose from, Developers find themselves in a position where they have to choose the best way to handle asynchronous communication - creating queries and handling responses from servers and/or databases. While working with various applications over a number of years, I have had to work with a couple of different implementations of this type of operation, and I have endeavoured to make a summary of some of my own findings here, perhaps to get some perspective.
Cal

Call-backs

The most well-known form of call-back function you will probably be familiar with probably looks something like this:

function myJSCallback() {
  alert(this.message);
};

var receiver = { message: 'hi there!' };
var callback = funcPtrType(myJSCallback, receiver); 

// alerts with 'hi there' when the callback is invoked

This is known as a js-ctype in JavaScript circles. Although this (in MDNs documentation) has been marked as deprecated, most of the other forms of communications have their roots here, and that is why it is best to start by explaining why it is/was so handy/important.

Js-ctypes actually originated with the Operating System APIs and the interactions between the operating system and applications. For communication between, the browser and a Windows application, big vendors developed a series of technologies alongside the main Operating System's API - you would probably be familiar with e.g. ActiveX controls and or the .Net Framework. We can use JavaScript to access methods and endpoints exposed by these technologies.

With APIs exposed on the web, messages are sent from computer to computer using the REST Framework over HTTP. This is where things get interesting, because requests sent over HTTP are rather vulnerable after a fashion; so programmers have to protect them and to ensure that requests reach their destination with all of their data intact.

So how it would work with the js-ctype query above; is that when this query is received by the receiving application, it will go throught the data; and once completing its work, will respond by running the call-back function. Which means that the function above will alert 'Hi there' once the call-back code is run. This is effective to a certain extent. BUT the problems started when people started asking: was my message sent in tact; was the operation completed; etc. These problems were tackled and Promises and Async/Await was the result. And of course, some of these situations, being Web specific, mean that we also have to get domain specific with our references.

Promises

According to the MDN documents, a Promise is an Object that represents the failure or completion of an Asynchronous operation:

js-promise.svg

As you will see in the drawing included (courtesy MDN) and in the code below;

Our code makes a PROMISE to our users in a message sent to provide them with an expected outcome. The request is yet to be fulfilled or unfulfilled (rejected). If rejected we send them a fail message, if successful, operations may be completed (e.g. data returned). But the return message contains another promise. The second promise represents the completion not just of our first method call, but also of the successCallback or failureCallback passed in, which can be other asynchronous functions returning a promise.

A promise comes with some guarantees:

  • Callbacks will never be called before the completion of the current run of the JavaScript event loop.
  • Callbacks added with .then even after the success or failure of the asynchronous operation, will be called, as above.
  • Multiple callbacks may be added by calling .then several times, to be executed independently in insertion order.
// Promise approach

function getJSON(){

    return new Promise( function(resolve) {
        axios.get('https://tutorialzine.com/misc/files/example.json')
            .then( function(json) {

                // The data from the request is available in a .then block
                // We return the result using resolve.
                resolve(json);
            });
    });

}

The most immediate benefit of using promises can be seen in this example of more complex function chaining:

doSomething(function(result) {
  doSomethingElse(result, function(newResult) {
    doThirdThing(newResult, function(finalResult) {
      console.log('Got the final result: ' + finalResult);
    }, failureCallback);
  }, failureCallback);
}, failureCallback);

Async/Await

With Async/Await we are looking at the same operations and functionality. We want to get some JSON file from a server. So we write a function that uses a predetermined library, e.g. angular or axios library to perform an HTTP GET or POST request to an endpoint e.g. an API. We have to wait for the server to respond, so naturally this HTTP request will be asynchronous. Here is a code example:

// Async/Await approach

// The async keyword will automatically create a new Promise and return it.
async function getJSONAsync(){

    // The await keyword saves us from having to write a .then() block.
    let json = await axios.get('https://tutorialzine.com/misc/files/example.json');

    // The result of the GET request is available in the json variable.
    // We return it just like in a regular synchronous function.
    return json;
}

The Async Keyword in our declaration creates the promise and Await, when placed in front of a Promise call, forces the rest of the code to wait until that Promise finishes and returns a result. Await works only with Promises, it does not work with callbacks. Await can only be used inside async functions.

Here is a more complex example, but in using it, it is a bit easier to explain what happens under the hood. There is also one or two code enhancements thrown in:

async function getABC() {
  let A = await getValueA(); // getValueA takes 2 second to finish
  let B = await getValueB(); // getValueB takes 4 second to finish
  let C = await getValueC(); // getValueC takes 3 second to finish

  return A*B*C;
}

async function getABC() {
  // Promise.all() allows us to send all requests at the same time. 
  let results = await Promise.all([ getValueA, getValueB, getValueC ]); 

  return results.reduce((total,value) => total * value);
}

To send all requests at the same time a Promise.all() is required. This will make sure we still have all the results before continuing, but the asynchronous calls will be firing in parallel, not one after another.

Well; that is pretty much a long explainer for a simple concept to into which much complexity may be introduced. But if you are using a RESTful implementation, I can not imagine you getting by without at least a basic understanding of these concepts.

Hope this helps!

See the following links for further reading (some have some really interesting promise problem puzzles worth having a look at):

Promises
Using Promises
Async/Await

Discover and read more posts from Theresa Mostert
get started