Asynchronous Javascript

JS Code Execution

Javascript is a synchronous and single-threaded language. When js engine reads the script then it creates an environment where all the code executes called Execution context.

Two types of execution context:

  1. Global execution context: It represents the global scope of js.

  2. Function execution context: It represents function's local scope

Two Phases

Creation Phase:

Creates global object and sets up memory to store the variables and functions, variables value stored as undefined and function with references

Execution Phase :

Now it reads the whole code line by line and evaluates it, then the value of undefined changes to the allocated value after completion of execution it is destroyed.

Example:

//craetion phase
n=undefined

m=undefined

add{..}

addition=undefined

//execution phase

n=5

m=6

add{...}

addition = 11

Differences between Synchronous & Asynchronous

Synchronous: It executes in a sequence, after completion of the previous instruction only the next instruction starts. The drawback of synchronous was sometimes the important instructions may get blocked because of previous instructions which take a long time.

console.log("Hi")
console.log("bye")

Asynchronous: Asynchronous came into the picture to prevent the drawbacks of synchronous. It executes the next instruction immediately, it does not block the flow of previous ones. In the below code, there is a delay of the first instruction for 2 sec as it is asynchronous it will not block there and the first prints the hello and after 2 sec hi is printed.

setTimeout(()=>{
console.log("hi")
},2*1000)
console.log("hello")

Callstack queue

A Callstack queue is used to track the functions in a program. Functioning of it is when the script calls the function it is stored in the callstack after that if the function is called present in the callstack it runs and completes that function and then takes it out from the callstack. As in the name present it follows last in first out.

function first(){
console.log("first")
}
function second(){
first()
console.log("second")
}
second()

Event loop

It handles asynchronous operations like promises etc.

Components of the event loop

Callback queue: In callback queue callbacks are present like setTimeout(),setInterval() etc

Microtask queue: In microtask queue asynchronous functions present like promises etc.

The event loop will help to push the tasks in callback queue and microtask queue one by one into call stack.

Inversion of control and Problems causes

Inversion of control means having control in one part of a code, when we give another person this code for the next step, the control goes to him.

The problem with this was if the control went to another person then he may change before the written code there may arise problems.

Ways to Avoid callback hell

By using promises or by using async await we can avoid callback hell

//this is callback hell
main(function(result) {
  sub1(result1, function(final) {
    sub2(final, function(finalResult) {
      console.log(finalResult);
    });
  });
});
main()
//using promises how to resolve callback hell
main()
  .then(result => sub1(result))
  .then(result1 => sub2(final))
  .then(final => {
    console.log(finalResult);
  })
  .catch(error => {
    console.error(error);
  });

Promise

Promise is an object that represents the completion or failure of asynchronous operations.

It has three states

  • Pending - Execution of promise is ongoing

  • Fulfilled-Completed execution of promise and result is success

  • Reject-Completed execution of promise but it gets rejected

      //using of promises
      const fspromises = require('node:fs').promises   
      function sample(){
          fspromises.readFile('./data.js','utf-8')
          .then((data)=>{
              return data
          })
          .then((data)=>{
              console.log(data.toUpperCase())
          })
          .catch((error)=>{
              console.log(error)
          })
      }
      sample()
    

    The promise is solving the callback hell and inversion of control problems.

Create Promise

The below code illustrates how to create promise.

const newone = new Promise((resolve,reject)=>{
setTimeout(()=>{
resolve("Hello")
},2000)
}) 
newone
.then((data)=>{
console.log(data)})
.catch((err)=>console.log(error))

Promise API's

promise.all() - After solving all promises it will return values of array as output.If one of them is rejected then error of that promise is given as output.

promise.allsettled()-After solving all promises it will return values of array,if any one is rejected then also it will values of array but error as output for that reject promise.

promise.race()-It gives the value of first completion promise.If it is error then it will return error.

promise.any()- It gives the first completion promise,if there is any error then it will return the next success one.

Web browser APIs

Web browser APIs are sets tools web browsers provide to allow developers to interact with and manipulate the browser environment and the user’s device.

There are many Web Browser APIs, Some of them are

  • SetTimeout: To set time, after how much time function should be called

  • DOM API: To change structure or style we use this

  • fetch: It enables communication with servers.

  • console: To log information on the browser

Microtask Queue

Microtask queue is an essential component of the event loop, these are used to execute the next task by immediate completion of the present stack. In the microtask queue promises are present and it performs async operations.

console.log("hello")
promise.resolve()
.then(()=>{console.log("promises are utilized by microtask queue")})
console.log("bye")

Handling errors while using promises

Through the catch() method we can handle promises. Anywhere there is an error it will catch and handle it. Catch only handles errors for promises that are present before it.

function dosomething(){
    return new Promise((resolve,reject)=>{
        const data = "promise data"
        setTimeout(()=>{
            reject(new Error("Error occured"))
        },2000)
    })
}
dosomething()
.then(data=>{
    console.log(data)
 })
.catch((error)=>{
    console.log(error.message)
})

Async. Await

Async await is an asynchronous function alternative for promise that will look like synchronous code that means it provides clean and readable format of asynchronous code.

async-In this we have to write async keyword before function, it returns the promise and allow to write await keyword.

await-This keyword is used to wait till the promise completed after completing it will console.

const p1 = new Promise((resolve)=>{
    setTimeout(()=>{
        resolve("hi")
    },5000)
})
const p2 = new Promise((resolve)=>{
    setTimeout(()=>{
        resolve("hello")
    },2000)
})
async function handling(){
    console.log("first")
    const promise1 = await p1
    console.log(promise1)
    const promise2 = await p2
    console.log(promise2)
}
handling()

Different between promise & async..await

Promise involves chaining of then and catch whereas async await uses keywords of async and await and it looks like synchronous code.

In promises we use catch method for error handling and in async await uses try catch.Try catch is easy to handle errors.

Async await is more readable than promises.

When concurrent operations present it is better to use promises and in remaining cases better to use async await.

Best ways to avoid nested promises

1.Chaining promises

promisesChaining()
.then((data)=>{filter(data)})
.then((filtered)=>{update(filtered)})
.then(()=>{console.log("updated data successfully")})
.catch(()=>console.log("error"))

2.Using promise.all for concurrent actions

function data1(){
return new Promise((resolve,reject)=>{
setTimeout(()=>resolve("Hi")
},1000)
}
function data2(){
return new Promise((resolve,reject)=>{
setTimeout(()=>resolve("Hello")
},2000)
}
promise.all[data1(),data2()]
.then((result)=>{console.log(result)})
.catch((err)=>{console.log(error)})

3.async await

function filterData(){
  console.log("filter")  
}
function updateData(data){
    console.log("updated")
}  
async function handling(){
    const data = await filterData()
    await updateData(data)
}
handling()

These are some ways to avoid from nested promises,because of nested, it is tough to read and tough to solve if any errors occured.