Niroj Dahal

Evolution of Asynchronous Javascript

Published 3 years ago 5 min read
image

Although, Javascript is single-threaded , web APIs make us feel like everything is happening asynchronously. Current state of asynchronous programming has not been developed overnight. It has been there for quite a long time and has evolved over time. Below are the ways used to work asynchronously till date

  1. Callbacks
  2. Promises
  3. Async Await

Callbacks

A function is a first-class citizen in Javascript. It can be passed as variable to another function and can be invoked. It is the mechanism of callback. A function is passed to another function as an argument and is invoked from it. It has no return statements. Example:

function funcA(anotherFunc)
{
  // do something
	anotherFunc();
} 

In the above example, anotherFunc is passed as an argument to funcA and is invoked inside it .

Drawbacks:

  1. This way of invoking a function can quickly lead to callback hell (when multiple functions are executed one after another, callbacks are nested)

const fs = require('fs')

const callbackHell = () => {
  return fs.readFile(filePath, (err, res)=> {
    if(res) {
      firstCallback(args, (err, res1) => { 
        if(res1) {
          secondCallback(args, (err, res2) => {
            if(res2) {
              thirdCallback(args,  (err, res3) => {
                  // and so on...
              }
            }
          }
        }
      }
    } 
  })
}

  1. Error handling gets messy.

Promises

Promises are there for quite a long time now. A promise ascertains the completion/failure of task at some time. Promises can be in one of three states

  • Pending : initial state
  • Resolved : denotes that task completed successfully
  • Rejected : denotes that task failed

A promise can be created as

 let myPromise = new Promise((resolve,reject)=>{
   setTimeout(()=>{
      let isSuccess = DoSomeWork();
			if(isSuccess){
			// this will be dealt in then block
			 resolve("Promise completed successfully");
			}
			else{
			//this will be dealt in catch block
			reject("Task failed");
			}
		},3000)
 })

We can work with the above promise like

 myPromise().then(res=> {
  // if promise is resolved, control flows to this block
 console.log(res);
 }).catch(ex=>{
 //if promise is rejected , control flows to this block
 console.log(ex);
 })

In the above example,

if promise is resolved then control flows to then block and Promise completed successfully is logged if promise is rejected, control flows to catch block and Task failed is logged

Promises can be chained one after another removing the drawbacks of callback hell.

myPromise()
 .then(res=> DoSomething(res))
 .then(res=>DoAnotherThing(res));

Async Await

As promises, it deals with completion/failure of task at later time but in much nicer and cleaner way. If you are familiar with asynchronous programming in C#, this syntax looks familiar.

Example

try
{
 let data= await myPromise();
 console.log(data);
}catch(ex){
 console.log(ex);
}

consider , myPromise as promise mentioned above,

if promise rejects, control flows to catch block if promise resolves, response is stored in data variable and statements below it are executed

This method removes the drawbacks of callback hell and chaining then blocks and is most cleaner compared to above methods.

Thank you for reading !!!