Callback, Promises and Async/Await

We will study Callback, Promises and Async/Await in the below sections and will explain which one came first, what was its shortcoming and which one is the latest way of writing asynchronous code.

Callback:

In the earlier versions of JavaScript, callbacks were widely used to handle asynchronous operations.
A callback is a function that is called when an asynchronous task has been performed and is supplied as an argument to another function. The callback function enables you to provide the logic that needs to be carried out when the asynchronous task is complete. Callback hell, or complicated and nested code structures, are a result of poorly managed callbacks.

Example using callbacks:

function dataFetch(callback) {
  setTimeout(function () {
    const data = 'Howdy, Romeo!';
    callback(data);
  }, 2000);
}

function manipulateData(data) {
  console.log(data);
}

dataFetch(manipulateData); // Output: Howdy, Romeo!


In the previous illustration, the callback function manipulateData is used as an argument for the function dataFetch.


Some Scenarios where callback is extremely useful :
1. Once you fills up the login form and click on Submit button then a callback function helps in redirecting into home or dashboard page.
2. Once you click on logout button then a callback function helps in redirecting into login page.

Promise:

A promise in JavaScript is an object that denotes the value of an asynchronous operation's outcome as well as the operation's ultimate success or failure.
Compared to callbacks, it offers a simpler and more organized method of controlling asynchronous code.


One of three states are possible for promises:

Pending: The initial state of a promise after it is created is Pending. It signifies that the asynchronous activity is ongoing and hasn't been completed or refused.

Fulfilled: When an asynchronous operation has successfully finished, it is said to be in the fulfilled state. The Promise is changed from being a meaningless promise to one that has a purpose or outcome.

Rejected: An asynchronous activity is deemed unsuccessful when it encounters an error. An explanation or error message for the rejection is given.

Here's a basic example of creating and using a Promise:

const newPromise = new Promise((resolve, reject) => {
  // Asynchronous operation which will be resolved or rejected by promise
  setTimeout(() => {
    const result = 'Promise resolved';
    resolve(result); // Fulfilled state
    // or
    // reject('Promise rejected'); // Rejected state
  }, 2000); // Delaying the execution for 2 seconds
});

// Handle Promises by a .then() function
newPromise
  .then((result) => {
    console.log('Fulfilled:', result);
  })
  .catch((error) => {
    console.log('Rejected:', error);
  });


In this example:

A new Promise is created using the Promise constructor, which takes a callback function with two parameters: resolve and reject. These parameters are functions used to transition the Promise to the fulfilled or rejected states.

Inside the Promise, an asynchronous operation is performed using setTimeout as an example. After a 2-second delay, the operation is considered completed.

If the operation is successful, resolve is called with a result value, transitioning the Promise to the fulfilled state. If an error occurs, reject is called with an error message, transitioning the Promise to the rejected state.

The Promise is then consumed using the .then() and .catch() methods. The .then() method is used to handle the fulfillment state, and the .catch() method is used to handle the rejection state.

Depending on the state of the Promise, the corresponding callback function is executed. If the Promise is fulfilled, the result value is passed to the .then() callback. If the Promise is rejected, the error message is passed to the .catch() callback.

Promises provide a more structured and readable way to handle asynchronous operations in JavaScript. They allow you to chain multiple asynchronous operations together using .then() and handle errors using .catch().

Additionally, Promises can be used with async/await syntax, further simplifying the handling of asynchronous code.

Async/await

Async/await is a syntactical enhancement(only syntax changes) introduced in ES8 (ES2017) that simplifies working with promises. So async/await is the same as promise just a different way of writing.

It enables you to create asynchronous code that resembles synchronous code, which makes it simpler to comprehend and maintain. The await keyword is used to delay execution while waiting for a promise to resolve, and the async keyword is used to declare an asynchronous function.

Example using async/await:

function dataFetch() {
  return new Promise(function (resolve, reject) {
    setTimeout(function () {
      const data = 'Oyo, Mundo!';
      resolve(data);
    }, 2000);
  });
}

async function manipulateDate() {
  try {
    const data = await dataFetch();
    console.log(data); // Output: Oyo, Mundo!
  } catch (error) {
    console.error(error);
  }
}

manipulateDate();


The syntax is much simpler and there is no longer a requirement for explicit .then() and .catch() methods thanks to async/await. You may manage any errors that might arise during the asynchronous procedure by using the try-catch block.
And one more trick is always use try-catch block with async/await and that is the way to write code at jobs.

Some Scenarios where Promise and async/await is extremely useful :
Since both are same just different way of writing so its useful in similar situations.

1. Whenever you are making an API request using fetch or axios.
2. Whenever you are making a request to database with or without the use of ORM in backend.