Basics of Asynchronous JS

Basics of Asynchronous JS

Learn in simple way!

Hi Reader, nice to meet you! In this blog I will teach you about asynchronous JavaScript. It seems like such an important topic if you are studying JavaScript and It really is important !! . . . Of course you can go through docs or GeeksForGeeks but here I will try to make it very simple and clear to you with many examples. So, get on the ride ! ^ ^

Index

  1. Single Threaded Nature of JavaScript

  2. Role of web browsers

  3. Callbacks

  4. Callback Hell

  5. Promises in JavaScript

  6. Async/ Await in JavaScript

If JavaScript is single threaded then how asynchronous?

Now, before moving onto this question ( a pretty trick ques sometimes asked in interviews ) lets first understand what is this thing ! " single threaded nature " . No, its not related to actual thread :) 🧵( I wish it was ) ... Anyways -

It simply means that JavaScript has a one track mind. Like it can't do multitask. JavaScript executes code line by line. It will finish Task1, then move to task2 and then to task3..While other While other programming languages 📔allow for multiple threads to run simultaneously, each executing its own sequence of instructions,🛠️ JavaScript does not offer this capability.

Single Thread

Just like, some person X cant cook while reading a book. Maybe he will first cook and then read or vice versa but can't do both together. Similarly, Js can't execute one function at the same time executing another function. This is single threaded nature. It's synchronous, but at times that can be harmful. For example, if a function takes awhile to execute or has to wait on something, it freezes everything up in the meanwhile.

If you want to know more about why Js don't have multithreading like other languages

Read this..

Asynchronous

Asynchronous means its not synchronous (don't hate me for that line haha). But asynchronous in JS means codes executing parallelly, or not in any proper order.

I know I know, we just learnt that JS is single threaded then why there is a thing called Async Js and how is it possible! We have a workaround...

Role of Web browser

Lets take that example again, X is cooking and wants to read side by side. X can call his little brother and ask him to read out loud for him. At the same time, X cooks. Now, X is able to read while cooking. X is smart. Be like X, jk.

In layman language, there are certain functions recognized by JavaScript Engine that when it sees them, it hands off them to web browser to execute it in the background. For example,

  • setTimeout Function() - delay the code for given seconds or milliseconds and then runs the code which is inside the function

Here's a scenario -

console.log("I print First");  //First 

setTimeout( ()=>{    
    console.log("I print after 3 Seconds"); 
}, 3000);
//Third

console.log("I print second");  //second
  1. When the compiler gets to the setTimeout function, it has already printed the first console.log

  2. Then, it hands the setTimeout func to web browser to run in background. The web browser counts for 1....2....3 in the background

  3. and in the meanwhile, the compiler prints the second console.log (web browser is still counting in the background)

  4. By this time the 3 seconds are up and the web browser reminds the compiler "Hey, times up!".

  5. Compiler prints that 3rd console.log

Output -

So, this is how Js is working asynchronous even though it is single threaded🎈

Let's move onto next topic CALLBACKS ?? 📞

Callback Functions

"I will call back later!"

Thats where it got its name from

Callback functions are functions which are passed as arguments in other function. In this way, a function calls another function ( yeah similar to recursion ) But in recursion, the function calls itself.

In this case, the main function calls the "passed as argument function" / calls back some other functions. Don't get confused, we will look into examples :)

function myDisplayer(some){
document.getElementById('demo').innerHTML = some;
};

function myMessage(fName, lName, myCallBack){
//This is a function which has 3 arguments: firstname, lastname
//and a callback function

//Notice, I didn't declare that function as 'myCallBack()'
let message = `Hello, ${fName} ${lName}. How are you ?`;
myCallBack(message);     //callback
};

myMessage('Tony', 'Chopper', myDisplayer); //myDisplayer function is passed 
//as a callback function

In the above example, you will notice that myDisplayer function is passed as a callback function in myMessage function. Heck you can even create an anonymous function like -

myMessage('Tony', 'Chopper', (some)=>{
alert(some);
document.getElementById('demo').innerHTML = some;
}

Now, this was a simple example just to explain the syntax of callbacks but why do we study them ? A callback function can run after another function has finished. Where callbacks really shine are in asynchronous functions, where one function has to wait for another function (like waiting for a file to load).

But, sometimes using callbacks is a bad idea.

Callback hell

After the dramatic introduction lets see what is a callback hell.

It is nested callbacks making a pyramid like structure which is not something you will want to read. It looks so messy and difficult to read. Every callback is dependent/ waits for the previous callback. This code is also hard to maintain.

Example -

const delayedMessage(firstName, lastName, myCallback){
    setTimeout(()=>{
        let message = `Hi, ${firstName} ${lastName}. Welcome!`;
        document.getElementById('container').innerHTML = message;
        myCallback();   //it will be called after 2 seconds 
    }, 2000)
}

Above is a function which prints the message after 2 seconds. It takes three arguments and one of them is a callback function. Still no problem until now...

delayedMessage('Sagar', 'Sharma', ()=>{
//this callback function being passed calls delayedMessage again
    delayedMessage('Rome', 'Beck', ()=>{
        delayedMessage('Riya', 'Sims', ()=>{
               delayedMessage('John', 'Ben', ()=>{
                    delayedMessage('Charles' , 'Afflick');
                });
            });
        });
    });

This pyramid structure is so hideous to read and maintain. Let's take another somewhat real world looking example -

searchMoviesAPI('amadeus', () => {
    saveToMyDB(movies, () => {
        //if it works, run this:
    }, () => {
        //if it doesn't work, run this:
    })
}, () => {
    //if API is down, or request failed
})

Another Example-

const fs = require('fs');

fs.readFile('file1.txt', 'utf8', (err, data1) => {
  if (err) {
    console.error('Error reading file1.txt');
  } else {
    fs.readFile('file2.txt', 'utf8', (err, data2) => {
      if (err) {
        console.error('Error reading file2.txt');
      } else {
        fs.writeFile('output.txt', data1 + data2, 'utf8', (err) => {
          if (err) {
            console.error('Error writing to output.txt');
          } else {
            console.log('Files successfully read and merged into output.txt');
          }
        });
      }
    });
  }
});  // I know this code is hard to understand..thats the point

All these nested Callbacks create the "callback hell"

Promises in JavaScript

To tackle this problem, promises were introduced in JavaScript. These are recent additions and promise for a better future I guess, ... that's why the name. Anyways -

A promise is an object representing the eventual completion or failure of an Asynchronous operation.

Let's say you are getting data from some API... It might work or it might not. In the previous method using callbacks you have to pass two callbacks one for failure and one for success. If its success, we have to fetch next set of data and pass another two callbacks ( one for success and one for failure ). and on and on ... ahh.. callback hell.

promises syntax

const someFunction = (url) =>{ //its some function which takes url 
                               //from user and return a promise object

//we declare a promise object using 'Promise" keyword
    return new Promise((resolve, reject) => {
        //some api called..it will either work or won't

    })  // resolve and reject are two arguments passed. These are user-
        // defined names..but in programming world these words are commonly
        // used

};

We have created a promise object above.. although usually, we don't have to create promises rather we just have to work with created ones...using .then() and .catch() methods. You just need to focus on understanding how these two work

someFunction()
.then(data){
    console.log("Promise is resolved");
    console.log("here is your data!", data);
}
.catch(err){
    console.log("Error 404", err);
}

If the promise is resolved, then() method is called. If error is here , catch() method is called. Right now, its similar to callback hell if there is nested structure like if API is success, fetch next set of data..[ .then and .catch for that too ]

But, we can chain these .then() methods to make the code more readable and finally escaping callback hell !

// RETURN A PROMISE FROM .THEN() CALLBACK SO WE CAN CHAIN!
fakeRequestPromise('yelp.com/api/coffee/page1')
    .then((data) => {
        console.log("IT WORKED!!!!!! (page1)")
        console.log(data)
        return fakeRequestPromise('yelp.com/api/coffee/page2')
    })
    .then((data) => {
        console.log("IT WORKED!!!!!! (page2)")
        console.log(data)
        return fakeRequestPromise('yelp.com/api/coffee/page3')
    })
    .then((data) => {
        console.log("IT WORKED!!!!!! (page3)")
        console.log(data)
    })
    .catch((err) => {
        console.log("OH NO, A REQUEST FAILED!!!")
        console.log(err)
    })  //lot more readable and easy to understand

Async / Await in JavaScript

Finally, the last section of this looong blog.. I hope you didn't get bored reading till here :) Congrats reader if you have reached this section.

Async and Await are newer and cleaner syntax for working with async code! Syntax "make up" for promises. We are kinda just beautifying promises.

  1. Async

  2. Await

Async Keyword

const message = async () =>{
 //some code here
    return 'hello user';
}

When we declare a function as async, now this function automatically returns a promise. We don't even need to create a new promise object. We can still use .then() and .catch() methods

message()
    .then((data) => {
        console.log("PROMISE RESOLVED WITH:", data)
    })   //output will be "PROMISE RESOLVED WITH: hello user"
    .catch(()=> {
        console.log("OH NO, PROMISE REJECTED!")

    })
  • if returning a value -> promise resolved

  • if there is an error -> rejected

//Example 2
const login = async (username, password) => {
    if (!username || !password) throw 'Missing Credentials'
    if (password === 'jackofalltrades') return 'WELCOME!'
    throw 'Invalid Password'
}

login('jack', 'jackofalltrades')
    .then(msg => {
        console.log("LOGGED IN!")
        console.log(msg)
    })
    .catch(err => {
        console.log("ERROR!")
        console.log(err)
    })

Await keyword

This keyword can only be declared inside an async function. It waits for the promise to be resolved. If it is resolved then, that data can be stored inside a variable for future use.

For example, instead of using .then() and .catch() methods for the above code,

async function main(){
    let data1 = await login('Jack', 'jackofalltrades');
    let data2 = await login('Pisces' 'masterofnone');
    console.log("data 1 is: ", data1);
    console.log("data 2 is: ", data2);
}

'await' waits for the promise to be resolved and only if it is resolved we can store that value somewhere. NO CALLBACKS, NO HELL, NO .THEN() AND .CATCH(), NO PROMISES. So Simple.

Suppose you are fetching some data from API, you can simply use await keyword before inside an Async function and you are sorted !!

But, what about errors?

Yes, we don't use .catch() or any callback to handle errors. So , how can we handle errors now ?? What if our API doesn't work or something happens ??

async function makeTwoRequests() {
    try {
        let data1 = await fakeRequest('/page1');
        console.log(data1);
        let data2 = await fakeRequest('/page2');
        console.log(data2);
    } catch (e) {
        console.log("CAUGHT AN ERROR!")
        console.log("error is:", e)
    }

} //Good ol.. try catch will do the job!

Asynchronous JavaScript concepts are necessary to learn if you are going to interact with APIs and work with unpredictable real world requests and files access in JavaScript.

Let's end our blog here. I hope I was able to teach you all about basic Asynchronous JavaScript. If you have any doubts or remarks, please share in the comments 😊