ECMAScript 6 Features (ES6) – Asynchronous Function

Note: This is part 3 of three-part series An Overview of ECMAScript 6 (ES6) Syntax & Features series. This learning post is still in active development and updated regularly.

In the previous learning-note posts in this series, an overview of common ES6 Syntax & Features, and new features in introduced in functions, objects & classes with some basic examples were discussed.

Modern JS is also known as ES6 and its features are heavily used in JS frameworks like React, Angular etc. In this learning-note series, following key ES6 features are discussed.

Article Series
Part 1: Basic Syntax
Part 2: Functions, Objects & Classes
Part 3: Asynchronous Features (this Post)

In previous post Understanding JS Callback Functions, Synchronous and Asynchronous functions were discussed very briefly. The purpose of this series to overview ES6 features & syntax introduced in JS asynchronous functions with simple use case examples.

The definitions used in the following section is directly lifted from the MDN web documents here. They should be considered as quote.

Asynchronous Features

JS is very event driven language, it does not wait to run a block of code before moving on to the next function execution. For example, if we wanted to wait execution of certain function (eg. user input) before moving on to the next function, callback functions come very handy. In such situation we could use setTimeout callback function to delay execution.

//initialize function
setInterval(function() {
  console.log('Hello! JS');
}, 2000);

//OUTPUT
Hello! JS

In the example above, the Hello, JS is logged after 2 seconds delay.

Synchronous vs Asynchronous

The term asynchronous can be confusing at the beginning. To understand it better we should look at the both synchronous and asynchronous terms together.

Synchronous

The term synchronous refers to real time communication where each party receives (and if necessary process and replies to) messages instantly.

//synchronus execution
console.log(`Hello`);
console.log(`World !`);
console.log(`Hello, JS`);
//OUTPUT
Hello
World !
Hello, JS

In the example above, if we execute an function synchronously, we wait each function to complete before moving to next function.

Asynchronous

It refers to a communication environment where each party receives and processes messages when convenient or possible rather than immediately”.

When a function is executed asynchronously, we can move to next function before the first function is executed.

// asynchronous execution
console.log(`Hello`);
setTimeout(() => { console.log(`World !`) }, 100);
console.log(`Hello, JS`);

//OUTPUT
Hello
Hello, JS
World !

In the example above, the setTimeout function delayed execution of console.log(`World !`) by 1 second. The execution of Hello, JS (line 11) didn’t pause to complete during the delay but operation continued and logged Hello, JS and after 1 second,  World ! console print (line 17).

Callback Functions – An Overview

Before ES6, callback function was predominantly used to execute a function asynchronously.

To quote from the MDN DocumentA callback function is a function passed into another function as an argument, which is then invoked inside the outer function to complete some kind of routine or action.

Simple Example
//initialize function
function learnAfter() {
    console.log('Learn React after JS.');
}
//initialize function with callback argument
function learnBefore(callback) {
    setTimeout(function() {
        console.log('Learn JS before React.');

        callback();
    }, 300);
}
// retrieve without callback argument
learnBefore(); //OUTPUT =>Learn JS before React. 

//retrieve with callback argument
learnBefore(learnAfter);
//OUTPUT
Learn JS before React.
Learn React after JS.

In the example above, a named function learnAfter() is initialized (lines: 3-4) and will be passed as a callback argument to learnBefore() function (lines: 6-12).

The learnBefore() function (lines: 6-12) is defined with one callback argument which runs even if no argument is passed (line 14) as a regular function.  The learnAfter() callback function is not run unless it is called by its containing learnBefore() function (lines: 17-20).

Callback Chains

In a nested callback, more than one functions can be created and used as callback elsewhere. If there are chains of callback or nested callbacks it quickly become unmanageable, prone to errors and difficult to read often referred as “callback hell“. This phenomenon is described by  Ire Aderinokun in this blog post with following code example.

//nested callback function
doSomething((response) => {
   doSomethingElse(response,(secondResponse) => {
        doAThirdThing(secondResponse);
   });
})

In the chain or nested callback functions, some part of code depends on results of other previously defined functions (callbacks) elsewhere. In a large sets with a long chains of callback, it can become very confusing.

Use of callback function is ideal only in small, short asynchronous functions. JS promises were introduced to solve this problem allowing to use promises with asynchronous functions in much clearer and ideal way.

Promises

To quote from the MDN document: ” The Promise object represents the eventual completion (or failure) of an asynchronous operation, and its resulting value.”

Unlike callback, discussed earlier, promises comes with some guarantees.  JS promises are used to handle results of an asynchronous operation (completion or failure). This process mimics synchronous operation by returning a value (promise) allowing to operate asynchronous functions without waiting to complete the run for final return value.

A promise (value) one of the following three states:

  • Pending: initial state, neither fulfilled nor rejected.
  • Fulfilled: the operation completed successfully.
  • Rejected: the operation failed; rejected with a reason (error).

The pending promise state could either be fulfilled or rejected and explained in the following MDN diagram how handlers queued up the <a title="The then() method returns a Promise. It takes up to two arguments: callback functions for the success and failure cases of the Promise." href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/then">Promise.prototype.then()</a> and <a title="The catch() method returns a Promise and deals with rejected cases only. It behaves the same as calling Promise.prototype.then(undefined, onRejected) (in fact, calling obj.catch(onRejected) internally calls obj.then(undefined, onRejected)). This means, that you have to provide onRejected function even if you want to fallback to undefined result value - for example obj.catch(() => {})." href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/catch">Promise.prototype.catch()</a> methods are called to chain promises.

Figure. A flow chart showing promise state and chaining with .then and .catch chain. (source: Promise | MDN)
Basic Syntax
//Basic Syntax
new Promise( /* executor */ function(resolve, reject) { ... } );

//basic example
const myFirstPromise = new Promise((resolve, reject) => {
  // do something asynchronous which eventually calls either:
  //   resolve(someValue); // fulfilled
  // or
  //   reject("failure reason"); // rejected
});

A basic function is passed with resolve or reject function as arguments (line 2). The resolve and reject functions, when called, resolve or reject the promise, respectively. A promise object is created with new keyword and its constructor (lines: 2, 5) .

Creating a Promise

In the example below myFirstPromise object is created with new Promise constructor (line 2) with a function as its argument (called “executor function“). The executor function takes two functions (resolve and reject) as parameters. The resolve function is called when an asynchronous operation is completed successfully with return result as value, where as reject function is called when operation fails and returns an error object.

//creating a promise function
const myFirstPromise = new Promise((resolve, reject) => {
    setTimeout(function(){
    resolve("It was a Success!"); // success message
  }, 250);
});
myFirstPromise.then((successMessage) => {
   console.log("Yay! " + successMessage);
});

//OUTPUT
Yay! It was a Success!

In the example above, the asynchronous operation was successful and resolve() was called (line 4) which was chained with .then() method to logout the successMessage callback function.

Additional Information: Creating a Promise | MDN Web Docs

Basic Promise Example

The following code snippets in creating a promise learnJs was adopted from Chris Nwamba’s post.

// create a promise
const learn = true;
const learnJs = new Promise(function(resolve, reject) {
   if (learn) {
    const topicDetails = {
      topic: 'JS ES6 Syntax',
      section: 'Functions & Objects',
    };

    resolve(topicDetails)
    //console.log(topicDetails);
  } else {
    reject(new Error('Learning prerequisite fist'))
    //console.log(Error);
  }
});

In the example above, if learn is true, the learnJs promise will either resolve with topicsDetails (lines: 5-8) or else it will reject with an error message (line 13).

Using Promises

The return value of promise objects can be used by chaining with .then() and .catch() methods as shown below. The .then() method receives a function with resolve value as an argument where as .catch method returns the value of reject (an error).

//basic promise chaining Syntax
funcName
  .then(function(done) {
    // the content from the resolve() is here
  })
  .catch(function(error) {
    // the info from the reject() is here
  });

In the example below, learnJs promise is chained in a MypRogress function with .then() an .catch() methods.

//promise chain with .then & .catch
const myProgress = function() {
  learnJs
    .then(function(done) {
      console.log('I am making good progress')
      console.log(done)
    })
    .catch(function(error) {
        console.log(error.message)
    })
}
//call promise function
myProgress();
//OUTPUT
I am making good progress
{
   topic: "JS ES6 Syntax",
   section: "Functions & Objects"
}

In the example above, because learnJs promise is true, the myProgress() function is called with output shown in lines 39-43.

Additional Information: Chaining | MDN Web Docs

Chaining Promises

Promises allow to chain two or more asynchronous operations back to back based on the results of previous promises.

To illustrate this process, lets create a new learnReact promise that we would like to chain with learnJs promise.

// create learnReact promise
const learnReact = function(topicDetails) {
  return new Promise(function(resolve, reject) {
    const message = `After JS, I will soon start ReactJs`;

    resolve(message)
  });
}

The above code snippets can be refactored into much shorter code too, as shown below.

//create learnReact promise
const learnReact = function(topicDetails) {
  const message = `After JS, I will soon start ReactJs`;
  return Promise.resolve(message)
}

Chaining the new learnReact promise to earlier learnJs promise as follows:

//chaining with learnJs promise
const myProgress = function() {
  learnJs
    .then(learnReact)
    .then(function(done) {
      console.log(done);
    })
    .catch(function(error) {
      console.log(error.message)
    })
}
//function call
myProgress();//OUTPUT => After JS, I will soon start ReactJs

The .then() method receives a function with resolve value as an argument where as .catch method returns the value of reject (an error). It is also to chain after .catch (failure) too.

Putting all together with Promise Chain
// create a learnJs promise
const learn = true;
const learnJs = new Promise(function(resolve, reject) {
   if (learn) {
    const topicDetails = {
      topic: 'JS ES6 Syntax',
      section: 'Functions & Objects',
    };

    resolve(topicDetails)
    //console.log(topicDetails);
  } else {
    reject(new Error('Learning prerequisite fist'))
    //console.log(Error);
  }
});

//create learnReact promise
const learnReact = function(topicDetails) {
  const message = `After JS, I will soon start ReactJs`;
  return Promise.resolve(message)
}

//chaining with learnJs promise
const myProgress = function() {
  learnJs
    .then(learnReact)
    .then(function(done) {
      console.log(done);
    })
    .catch(function(error) {
      console.log(error.message)
    })
}
//function call
myProgress(); //OUTPUT => After JS, I will soon start ReactJs

If const learn is set to false (line 2), then the myProgress() promise call (line 36) returns reject object with error from (line 13).

// if const learn = false (line 2)
myProgress(); //OUTPUT => Learning prerequisite fist

Additional Information: Chaining | MDN Web Docs

Async/Await

To quote from the MDN Docs, “the  async function declaration defines an asynchronous function, which returns an AsyncFunction object“.

Basic Syntax
//basic syntax
async function name(param1, param2, ... paramN) {
   //statements
}

An <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function#Syntax" target="_blank" rel="noopener">async</a> function is defined using async keyword. The syntax and structure of async functions is similar to standard synchronous functions. The async syntax is used to write simplified Promise syntax.

An async function returns a Promise – if a value is returned the promise is resolved with the returned value, or rejected with reason (throws an error).

Basic Example
//async function syntax
async function learnES6() {
  return 'Learning ES6 features';
}
learnES6();//function call
//OUTPUT
Promise { <state>: "fulfilled", <value>: "Learning ES6 features" }

This above code snippet could also written without async keyword before function (line 9) and <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/resolve" target="_blank" rel="noopener">Promise.resolve()</a> in return statement (line 10) as shown below.

//initialize function
function learnES6() { 
  return Promise.resolve('Learning ES6 features'); 
} 
learnES6(); //function call
//OUTPUT
Promise { <state>: "fulfilled", <value>: "Learning ES6 features" }

In the examples above, both methods return Promise object that is resolved and same value (lines: 7, 14).

The following basic example illustrates when a async function returns a promise with reject value (an error) with or without async keyword.

//without async keyword
function funcName() { 
  return Promise.reject('A rejected promise') 
} 
//function call
funcName(); //OUTPUT => Promise { <state>: "rejected" }

// with asyn keyword
async function funcName () { 
  throw 'A rejected promise'; 
} 
//function call
funcName();//OUTPUT => Promise { <state>: "rejected" }
Await

To quote from MDN Docs, “the await operator is used to wait for a Promise. It can only be used inside an async function“.

Basic Syntax
//basic example
const value = await promise;

The await keyword is used only with async function and placed before promise call. Await makes async function to pause function execution until promise is resolved and result is returned or rejected (with error).  Error handling in async/await is done with try and catch statement blocks.

Refactoring myProgress promise chain with async/await

In the example below the myProgress promise described earlier using .then and .catch method is refactored using try and catch statement blocks.

//define myProgress with Async / Wait
async function myProgress() {
  try {
    const topicDetails = await learnJs;
    const message     = await learnReact(topicDetails);
    console.log(message);
  } catch(error) {
    console.log(error.message);
  }
}

// call async functions
(async () => {
  await myProgress();
})();

The above code block with try...catch statement is much simpler and easy to read.

Putting All Together with Async/await
// create a learnJs promise
const learn = true;
const learnJs = new Promise(function(resolve, reject) {
   if (learn) {
    const topicDetails = {
      topic: 'JS ES6 Syntax',
      section: 'Functions & Objects',
    };

    resolve(topicDetails)
    //console.log(topicDetails);
  } else {
    reject(new Error('Learning prerequisite fist'))
    //console.log(Error);
  }
});

//create learnReact promise
const learnReact = function(topicDetails) {
  const message = `After JS, I will soon start ReactJs`;
  return Promise.resolve(message)
}
// Async / Wait
async function myProgress() {
  try {

    const topicDetails = await learnJs;
    const message     = await learnReact(topicDetails);
    console.log(message);

  } catch(error) {
    console.log(error.message);
  }
}

// call async functions
(async () => {
  await myProgress();
})();

//OUTPUT
After JS, I will soon start ReactJs
//Promise {<resolved>: undefined}

In the example above, the async/wait returned a value (line 42) the promise is resolved (line 43)

The async/await topic is discussed in detail with use case examples in other posts Deep Diving Into ES6 Async/await Features.

Wrapping Up

In this ES6 learning-note post series basic features of promise, chaining promises and use of Aync/Await features in JS promises are discussed. More detail discussion on Async/Await feature with some use cases will be discussed in another post.

Next Post: Deep Diving Into ES6 Async/await Features

Useful Resources and Links

While preparing this post, I have referred the following references extensively. Please to refer original posts for more detailed information.

Acknowledgement: This learning-note post including example snippets used in this post was inspired by Chris Nwamba‘s article Callback, Promise, and async | Scotch.io.