You, Me And Asynchronous JavaScript.

You, Me And Asynchronous JavaScript.

Get Rid Of Async Js Problems Once And For All

Intro

In JavaScript, synchronous operations mean that each stage of an operation waits for the previous step to complete completely. This means that regardless of how long a prior phase takes, the subsequent process will not begin before the former is completed. In contrast, asynchronous operations postpone operations. Any process that takes a long time to complete is normally run concurrently with other synchronous operations and finishes later. Let's look at a simple example

console.log("one");
console.log("two");
console.log("three");

If we run the code above, we have the following logged in the console:

one
two
three

Now let's tweak that a bit so that console.log("two") take longer than the other console logs.

console.log("one");

setTimeout(function() {
  console.log("two")
},3000);

console.log("three");

We get the following in the console:

one
three
two

What is the reason for this? By deferring console.log("two") for 3 seconds, the setTimeout feature renders the process asynchronous. The entire process does not pause for three seconds in order to record "two." After 3 seconds, the code switches to console.log("one"), console.log("three"), and finally console.log("two").

Functions === First-Class Objects

Before continuing with the rest of the blog, it's important to note that JavaScript Functions are first-class objects, and as such, they have the ability to:

  • Be assigned to variables (and treated as a value)
  • Have other functions in them
  • Return other functions to be called later

Callbacks

The contained function is known as a callback function when it simply accepts another function as an argument. Callback functions are a fundamental functional programming principle that can be found in almost all JavaScript code, whether in basic functions like setInterval, event listening, or when making API calls.

function greet(name, callback) {
    console.log('Hi' + ' ' + name);
    callback();
}

// callback function
function callMe() {
    console.log('I am callback function');
}

// passing function as an argument
greet('Pratik', callMe);

In the above program, there are two functions. While calling the greet() function, two arguments (a string value and a function) are passed. The callMe() function is a callback function. The advantage of using a callback feature is that you can perform another function call after waiting for the outcome of a previous function call.

Have you noticed how the callback is used? When passing the function as a parameter, the following parentheses, (), are not used. Pro tip: The callback function is not executed unless it is called by its containing function, in which case it is called back. As a result, the word "call back function" was coined.

Callback hell

As callback functions, multiple functions can be generated and used independently. Multi-level functions are created as a result of this. When the function tree that has been generated becomes too big, the code may become complicated and difficult to refactor. This is referred to as "callback hell." Consider the following scenario:

firstFunction(args, function() {
  secondFunction(args, function() {
    thirdFunction(args, function() {
      // And so on…
    });
  });
});

For short asynchronous operations, callback functions are useful. This is not considered the best option when dealing with wide collections. Promises were created in response to this challenge in order to make postponed tasks more manageable.

Promises

A promise is used to manage an operation's asynchronous outcome. JavaScript is designed to allow other synchronous sections of the code to run without having to wait for an asynchronous block of code to finish. When making API requests to servers, for example, we have no idea if the servers are online or offline, or how long it might take to execute.

Creating a Promise

The Promise object is generated with the new keyword and contains the promise; this is an executor feature with resolve and reject callbacks. Each of these callbacks, as the names suggest, returns a value, with the reject callback returning an error property.

let promise = new Promise((resolve, reject) => { 
  // Code to perform the promised task
  let task_performed = true;
  if(task_performed) { 
    resolve('The promised task was performed successfully.'); 
  } else { 
    reject('The promised task was not performed.'); 
  } 
});

Using Promises

Using a promise that has been created is relatively straightforward; we chain .then() and .catch() to our Promise like so:

promise.then((fromRes) => console.log(fromRes)). 
  catch((fromRej) => console.log(fromRej));

Async/Await

An async function is a change to the notation used to write promises. You might call it syntactic sugar on top of promises. It merely facilitates the drafting of promises. An async function returns a promise; if the function returns a value, the promise is resolved; if the async function throws an error, the promise is refused for that value.

Await is only used with an async function. The await keyword is used in an async function to ensure that all promises returned in the async function are synchronized, ie. they wait for each other. Await eliminates the use of callbacks in .then() and .catch(). In using async and await, async is prepended when returning a promise, await is prepended when calling a promise. try and catch are also used to get the rejection value of an async function. Let's see an example

// a promise
let promise = new Promise(function (resolve, reject) {
    setTimeout(function () {
    resolve('Promise resolved')}, 4000); 
});

// async function
async function asyncFunc() {
    try {
        // wait until the promise resolves 
        let result = await promise; 

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

// calling the async function
asyncFunc(); // Promise resolved

Benefits of Using async Function

  • The code is more readable than using a callback or a promise.
  • Error handling is simpler.
  • Debugging is easier.

Conclusion.

Understanding the concepts of Callbacks, Promises, and Async/Await may be difficult, but we've already seen how they function in JavaScript while performing asynchronous operations.

They're extremely useful when creating API requests and managing events.