javascript, typescript

Callbacks vs. Promises vs. Async/Awaits – Understanding Asynchronous JavaScript 2019

We will understand asynchronous JavaScript and detailed analysis of Callback vs. Promises vs. Async/Await

Written by admin · 5 min read >
Callbacks vs. Promises vs. Async/Awaits

In this article, We will understand asynchronous JavaScript and discuss fundamental concepts, their differences that JavaScript relies on to handle asynchronous operations. These concepts include Callback vs. Promises vs. Async/Await. We will cover why we need async/await when we could achieve the same fit with JavaScript Promises.

JavaScript is Synchronous Or Asynchronous

JavaScript is synchronous, blocking, single-threaded language. Which means that only one operation can be in progress at a time.

  • In single-threaded languages, you do not need to worry about the concurrency issues/ requests.
  • Javascript blocks the thread, It means, You can not perform long or time taking operations such as log-file operation, network access, fetching data, without blocking the main thread.
  • The JavaScript engine can only process one statement at a time in a single thread.
  • Synchronous means each statement in your code is executed one after the other, in sync. This means each statement has to wait for the previous one to finish its executing.

How Does Synchronous JavaScript Work?

To simplify, How synchronous JavaScript work? let’s take a real-life example.

Suppose you need your boss mobile number and you do not have it on your phone. now you call to your colleague and ask the number. Now here, two possibilities take place:

First, he asks you to wait and hold on the phone until he finds the number. Second, he asks you to do your work and I will call you back as soon as I find the number.

The First solution represents a blocking, synchronous javascript while the Second solution represents a non-blocking, asynchronous javascript.

//simple example of blocking, synchronous javascript 
console.log('First Statement');
console.log('Second Statement');
console.log('Third Statement');

//Output: 
First Statement 
Second Statement
Third Statement

How Does Asynchronous JavaScript Work?

As discussed previously, Synchronous operations in javascript block the thread and each statement have to wait till the completion of the first statement.

That’s why asynchronous JavaScript comes into the picture. Using asynchronous JavaScript (such as callbacks, promises, and async/await), you can perform long network requests without blocking the main thread.

The asynchronous code takes statements outside of the main program flow, allowing the code after the asynchronous call to be executed immediately without waiting.

console.log('First Statement');
setTimeout(()=>{
console.log('Second Statement');
}, 1000)
console.log('Third Statement');

//Output: 
First Statement 
Third Statement
Second Statement

Why Asynchronous javascript need?

Let’s take one more example, imagine you are requesting some data from an API. Depending upon the situation the server might take some time to process the request while blocking the main thread making and the user cannot perform any new action, the web page becomes unresponsive.

Long-running blocking JavaScript functions can make the UI or server unresponsive until the function has returned. Obviously, this can result in terrible user experience.

Asynchronous JavaScript, which is JavaScript that uses callbacks, promises, and async/await, helps with functions that take time to return some value or to produce some result.

JavaScript can have the asynchronous code, but it is generally single-threaded. Node.js is a non-blocking environment but single-threaded.

// Synchronous
feeds = getFeed();
// Wait till feeds completes
doSomeTaskRelatedToFeed()
// User has to wait till feeds loaded & feeds related task completes 
// User can't perform any other task
displayUserProfilePic();
doSomeOtherTask();
// Asynchronous
getFeedAsync = ()=>{
setTimeout(() => {
    console.log('Async Code: Wait till feeds completes');
    doSomeTaskRelatedToFeed()
  }, 2000)};
getFeedAsync();
// User need not to wait till feed loaded
// User can perform any other task
displayUserProfilePic();
doSomeOtherTask();

Callback Functions

A callback function is usually used as a parameter to another function.

A callback is a function that is passed to another function. When the first function is done, it will run the second function.

The function that receives the callback function as a parameter is normally fetching data from a database, making an API request, or completing some other task that could block the code thread for some time.

In below example welcome/welcomeUser is a callback function.

function welcome(name) {
  console.log(`Hi ${name}, welcome to jsforall`);
}
function userCredentials(firstName, lastName, welcomeUser) {
  const fullName = `${firstName} ${lastName}`;

 //now call callback function welcomeUser
  welcomeUser(fullName);
}

userCredentials('John','Doe', welcome); 

// Hi John Doe, welcome to jsforall

Callbacck Hell

When you have nested callback functions in your code! It gets harder to work with them.

Imagine a case where we must do HTTP to get three resources – employee, the company where the employee works and Stock value of that company. Our objective is to find the stock value of the company where an employee works.

First, we must get the employee, then the company information, then the Stock value. That’s three callbacks for three asynchronous operations.

getEmployee(employee => { 
   getCompany(employee, (company) => {
       getCompanyStock(company, (companyStock) => {
           console.log(companyStock);
       });
   });
});

So working with a lot of dependent asynchronous operations, you quickly end up in callback hell.

In the above example, each callback is nested. Each inner callback is dependent on its parent. This leads to the “pyramid of doom” style of callback hell.

Promises

To avoid the callback hell situation, ES6 introduces a solution: Promises, Let’s discuss how a promise works in JavaScript.

A promise is used to handle the asynchronous result of an operation.

Promises have three states:

  • Pending: This is the initial and default state of the Promise before an operation begins
  • Fulfilled [ resolve ]: This means the specified operation was completed
  • Rejected [ reject ]: The operation did not complete; an error value is usually thrown
var promise = new Promise(function(resolve, reject) {
  // do some async operation, then…

  if (/* everything turned out fine */) {
    resolve("Fulfilled, worked!");
  }
  else {
    reject(Error("Rejected, some error occured"));
  }
});

The promise constructor takes in one argument: a callback function with two parameters — resolve and reject. Here’s how you use that promise:

promise.then(function(result) {  
  console.log(result); // "Fulfilled, worked!"
}, function(err) {  
 console.log(err); // Error: "Rejected, some error occurred"
});

If promise was successful, a resolve will happen and the console will log, "Fulfilled, worked!" otherwise"Rejected, some error occurred". That state between resolveand reject where a response hasn’t been received is a pending state. 

Promises – Methods

  • Promise.all(iterable)Wait for all promises to be resolved, or for any to be rejected.
  • Promise.allSettled()Wait until all promises have settled (each may resolve, or reject).
  • Promise.race(iterable)Wait until any of the promises is resolved or rejected.
  • Promise.reject()Returns a new Promise object that is rejected with the given reason.
  • Promise.resolve()Returns a new Promise object that is resolved with the given value.

Benefits of Promises over callbacks

  • Improves Code Readability in comparison of nested callbacks.
  • Better handling of asynchronous operations than callback hells
  • Resolve maps to then and Reject maps to catch for all practical purposes.
  • Better flow of control definition in asynchronous logic.
  • Better Error Handling, make sure to write both .catch and .then methods for all the promises.
  • If something needs to be done in both the cases you can use .finally
  • Promise.all the order of the promises are maintained in values variable irrespective of which promise was first resolved.

Assync/Await

Async /await is an alternative for consuming promises, and it was implemented in ES8 or ES2017.

Async/await makes the asynchronous code appear and behave like synchronous code. Being that it was built on top of Promises, you could simply see it as a new way of writing synchronous code.

Async/await is a new way of writing promises that are based on asynchronous code but make asynchronous code look and behave more like synchronous code. This is where the magic happens.

Inside a function marked as async, you are allowed to place the await keyword in front of an expression that returns a Promise. When you do, the execution is paused until the Promise is resolved and it is similar to ES6 promise based solutions, but with even cleaner markup.

async function someOperation() {

      let promise = new Promise((resolve, reject) => {
        setTimeout(() => resolve("Fulfilled, worked!"), 2000)
      });

      let result = await promise; // pause till the promise resolves 

      console.log(result); // "Fulfilled, worked!"
    }

Promises vs. Async/Await

So should I use promises or async-await?

The answer is that we will use both. Following are some points by which you can decide when to use promises and when to use async-await

  • The Async function returns a promise. Every function that returns a promise can be considered as an async function.
  • Await is used for calling an async function and wait for it to resolve or reject.
  • Async/await is actually just syntax sugar built on top of promises. It cannot be used with plain callbacks or node callbacks.
  • Async/await is a new way to write asynchronous code. Previous alternatives for asynchronous code are callbacks and promises.
//promise - then
function logFetch(url) {
  return fetch(url)
    .then(response => response.text())
    .then(text => {
      console.log(text);
    }).catch(err => {
      console.error('fetch failed', err);
    });
}

Same above example using async functions:

async function logFetch(url) {
  try {
    const response = await fetch(url);
    console.log(await response.text());
  }
  catch (err) {
    console.log('fetch failed', err);
  }
}

Leave a Reply

Your email address will not be published. Required fields are marked *