Skip to main content

Command Palette

Search for a command to run...

JavaScript Concepts

Published
•12 min read•View as Markdown

Code execution in JavaScript

We know that JavaScript is a synchronous single-threaded language. While we execute the JavaScript code a global execution context is created. It has two phases memory phase and a code phase. In the first step, JavaScript allocates memory to all the variables and functions in the memory phase. For variables, it will give undefined as value, and for functions it stores as functions. Then the execution starts line by line it will allocate the value to the variables and it will call the functions. Let's understand it by having a small example

console.log(name); // undefined
var name = "sharath";
let person;
console.log(person); // undefined
function call(){
    person = "Bruce wayne";
call()
console.log(person); // Bruce Wayne

In the above example first, the name is undefined because JavaScript still does not assign a value to it. Then the same goes for the person and later the function call is stored in the memory stack then to the next line it will call the function inside the function, and a value is assigned to the person variable. Then in the last line, it prints the person variable with a value

Synchronous vs Asynchronous

As in the above example we discussed how the JavaScript code executes synchronously. But in JavaScript asynchronous programming is also there because callback exists in JavaScript. By using callbacks we can write asynchronous JavaScript also. Let's see it by a small example

console.log("Hi I am first")
setTimeout(()=>{
    console.log("Hi i am second")
 },2*1000)
console.log("Hi I am third")

/*
output:
Hi I am first
Hi I am third
Hi I am second
*/

As we can see in the above code the first console.log("Hi I am first") was executed then the next line had the callback with setTimeout which delayed the 2 sec. so JavaScript did not stop for anything it just moved to the next line and executed in the call stack. so next it prints the console.log("Hi I am third") after the call stack is empty then the macro stack queue has the code to execute it will send to the call stack after the call stack is empty the the code is executed and prints console.log("Hi I am second") at last.

Callback in JavaScript

In JavaScript we have callback functions we use in our code. Callback means the function passed as an argument inside another function is called a callback function. Let's see it with an example

function add(value1,value2){
    console.log(value1+value2); // output : 11
}

function main(value1,value2,add){
    add(value1,value2);
}

main(5,6,add);

In the above example, we can observe the add function is passed as an argument in the main function, and inside the main function the add function is taken as a parameter and it called inside the main function and the add function takes the values and gives the output in the console.

Callback Hell

Callback hell means maintaining the code using callbacks is very difficult because the function inside another function calls another function inside it and the cycle continues. Because of this, the maintainability and readability of the is very confusing. And also there are some disadvantages in callback hell. Let's see it by an example

function getData(callback) {
  setTimeout(() => {
    const data = { name: 'bruce', age: 32 };
    callback(data);
  },1*1000);
}

function processData(data, callback) {
  setTimeout(() => {
    data.email = 'bruce@example.com';
    callback(data);
  },2*1000);
}

function saveData(data, callback) {
  setTimeout(() => {
    callback('Data saved successfully');
  },3*1000);
}

getData(function(data) {
  processData(data, function(processedData) {
    saveData(processedData, function(response) {
      console.log(response); //output : Data saved successfully
    });
  });
});

In the above example, we can observe that we wrote a callback function in that we are depending upon the other function output. That means we are depending upon the third party to give us the out. We don't know whether the callback is called or not and we don't know how the code is written it calls the callback two times. Like this, there are many disadvantages to callbacks. To overcome this promises are created to write our code cleanly and maintainability and promises have many advantages compared to the callbacks.

Inversion Of Control


function getUserData(id,callback){
    let data = {name:"bruce",id:123}
      if (data == undefined){
            callback("error",null)
      }else{
            callback(null,data)
    }
}
function getFullDetails(id,name,callback){
    let data = {name:"bruce",id:123,village:"gauthom"}
    if(data != undefined){
        callback(null,data)
    }else{
        callback("error",null)
    }
}
getUserData(123,function(error,data){
    if(error){
        console.log(error);
    }else{
          getFullDetails(data.id,data.name,function(error,data){
            if(error){
                console.log(error)
            }else{
                getPhoneNumberData(data.id,function(error,data){
                    if (error){
                        console.log(error)
                    }else{
                        console.log(data)
                    }
                }
            }
         }
    }
})

using of callback calling a function inside another function and the cycle goes on. Due to this, there are some disadvantages in the callback hell such as inversion of control. Some of the disadvantages are nesting multiple callback functions can lead to what is commonly referred to as "callback hell" or "pyramid of doom."

Callback-based code can be less readable and maintainable, especially as the number of callbacks increases. Understanding the flow of execution and managing error handling can become complex and convoluted.

Error handling in callback-based code can be difficult, as errors must be propagated through each level of nesting.

Debugging callback-based code can be more difficult, especially when dealing with asynchronous code. Tracing the flow of execution and identifying the source of errors can be challenging due to the nested nature of callback functions.

Callback Queue

console.log("Beginning");
setTimeout(()=>{
    console.log("Inside the callback")
},1*1000);
console.log("Ending")
/*
output:
Beggining
Ending
Inside the callback
*/

In the above example, the code starts to execute first console.log("Beginning") pushed inside the call stack and executed and prints the "Beginning" and then the next setTimeout function is called, which schedules the provided callback function to be executed asynchronously after a specified delay. In this case, the delay is 1000 milliseconds. The setTimeout function and its callback are offloaded to the browser's APIs for execution. Next, the console.log("Ending") is pushed into the call stack and it executes and prints "Ending" Then after the completion of all the code in the call stack when it is empty the event loop sends the callback queue events sequentially into the call stack to execute. so at last the console.log("Inside the callback") is executed.

Event Loop

The event loop is a crucial component of JavaScript's runtime environment, responsible for managing the execution of code in an asynchronous and non-blocking manner. It continuously monitors the call stack and the event queue, ensuring that tasks are executed efficiently without blocking the main thread.

console.log("Beginning");
setTimeout(()=>{
    console.log("Inside the callback1");
},2*1000);
setTimeout(()=>{
    console.log("Inside the callback2");
},1*1000);
console.log("Ending");
/*
output:
Beginning
Ending
Inside the callback2
Inside the callback1
*/

In the above example, the first line console.log("Beginning") was entered into the call stack and prints the Beginning and then the next setTimeout function is called, which schedules the provided callback function to be executed asynchronously after a specified delay. In this case, the delay is 2000 milliseconds. The setTimeout function and its callback are offloaded to the browser's APIs for execution. The same happened for the next setTimeout . Next, the console.log("Ending") pushes into the call stack it executes and prints the Ending and then the call stack becomes empty. The event loop continuously monitors the call stack when the call stack becomes empty then the event loop sends it to the call stack sequentially. First, the setTimeout with 1-sec gits into the call stack console.log("Inside the callback2") executes, and prints the Inside the callback2 then the setTimeout with 2 sec pushed into the call stack and console.log("Inside the callback1") gets executes.

Promises in JavaScript

A Promise is an object representing the eventual completion or failure of an asynchronous operation and its resulting value. In JavaScript, Promises provide a way to handle asynchronous operations more cleanly and synchronously, making it easier to reason about asynchronous code.

A Promise can be in one of three states:

  1. Pending: This is the initial state of a Promise. It represents that the asynchronous operation associated with the Promise hasn't been completed yet. At this stage, the Promise is neither fulfilled nor rejected.

  2. Fulfilled (Resolved): A Promise enters the fulfilled state when the asynchronous operation completes successfully. This means that the Promise has a value (result) associated with it, and the associated .then() handler(s) will be called with this value. Once a Promise is fulfilled, it remains in this state and cannot transition to any other state.

  3. Rejected: A Promise enters the rejected state if the asynchronous operation encounters an error or fails for some reason. This means that the Promise has a reason (error) associated with it, and the associated .catch() or .then() handler(s) with rejection handling will be called with this error. Once a Promise is rejected, it remains in this state and cannot transition to any other state.

Creation of Promise


function fetchUserData(userId) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      const user = { id: userId, name: 'John Doe' };
       if(user != undefined){
            resolve(user);
        }else{
            reject("error");
        }
    }, 1000); 
  });
}

function getUserData(userId) {
  return fetchUserData(userId)
    .then(user => {
      console.log('User data:', user);
      return user; 
    })
    .catch(error => {
      console.error('Error fetching user data:', error.message);
      throw error; 
    });
}

getUserData(123)
  .then(user => {
    console.log('User:', user);
  })
  .catch(error => {
    console.error('Error:', error.message);
  });

In the above example, a promise is created in the fetchUserData the promise will definitely return data whether it will resolve or reject the error. It gives a promise that it will definitely return data and it will execute only one time in the function. Promise gives an assurance that it will give data or an error definitely. By using promises the debugging of code and the correctness of code will be done. Having many advantages over callbacks using promises.

Microtask Queue

In JavaScript, the event loop continuously checks the call stack and the task queue to determine what should be executed next. Alongside the task queue, there's also a microtask queue. The microtask queue, also known as the job queue or microtask checkpoint, is a queue that holds tasks that are executed asynchronously but at a higher priority than regular tasks in the task queue.

console.log('Start');
setTimeout(function() {
  console.log('Timeout');
}, 0);
Promise.resolve().then(function() {
  console.log('Promise resolved');
});
console.log('End');
/*
output:
Start
End
Promise resolved
Timeout
*/

In the above example the console.log('Start') goes into the call stack and executes and then the setTimeout will send to the macro task queue and then the promise.resolve() goes into the microtask queue and then console.log("End") goes into the call stack and gets executed and then the call stack becomes empty the event loop sends the promise. resolve().then function because it was in the microtask queue it has more priority than the macro task queue so it will get executed and print the Promise resolved. Then the call stack again becomes empty the macro task queue has the task it will send and the console.log('Timeout') got executes.

Promise.all()

const promise1 = Promise.resolve(1);
const promise2 = Promise.resolve(2);
const promise3 = Promise.resolve(3);

Promise.all([promise1, promise2, promise3])
  .then(values => console.log(values)); // Output: [1, 2, 3]

Promise.all() is used in promises it takes all the promises in the array and passes them to Promise.all(). It will give the output if all the promises in the array are resolved otherwise it will stop the execution where it got the error return the error and stop the execution to the other promise.

Promise.allSettled()

const promise1 = Promise.resolve(1);
const promise2 = Promise.reject('Error!');
const promise3 = Promise.resolve(3)

Promise.allSettled([promise1, promise2, promise3])
  .then(results => console.log(results));
// Output: [{ status: 'fulfilled', value: 1 }, { status: 'rejected', reason: 'Error!' },{ status: 'fulfilled', value: 3 }]

Promise.allSettled() takes the array of promises and executes the array of promises it will settle all the promises to settle whether it is a success or failure it will wait for all the promises to settle and gives the output of all the promises.

Promise.race()

const promise1 = new Promise(resolve => 
    setTimeout(() => 
        resolve('first'), 1000
    ));
const promise2 = new Promise(resolve => 
    setTimeout(() => 
        resolve('second'), 500
    ));

Promise.race([promise1, promise2])
  .then(value => console.log(value)); 
// Output: second (since it resolves first

Promise.race() takes the promises in the array and executes the array of promises it will return the output which promises executes fast and whether it may be a success or failure it will return the one which executes fast.

Promise.any()

const promise1 = new Promise((resolve, reject) => 
    setTimeout(() => 
        reject('Error 1'), 1000
    ));
const promise2 = new Promise((resolve, reject) => 
    setTimeout(() => 
        resolve('Success'), 500
    ));

Promise.any([promise1, promise2])
  .then(value => console.log(value)); 
// Output: Success (since it resolves first)

It returns a single Promise that resolves as soon as any of the promises in the iterable fulfills (not rejects), with the value of the first promise that fulfills. If all of the promises are rejected, it returns a Promise that is rejected with an AggregateError, containing all the rejection reasons.

Error Handling in Promises


function fetchUserData(userId) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      const user = { id: userId, name: 'John Doe' };
       if(user != undefined){
            resolve(user);
        }else{
            reject("error");
        }
    }, 1000); 
  });
}

function getUserData(userId) {
  return fetchUserData(userId)
    .then(user => {
      console.log('User data:', user);
      return user; 
    })
    .catch(error => {
      console.error('Error fetching user data:', error.message);
      throw error; 
    });
}

getUserData(123)
  .then(user => {
    console.log('User:', user);
  })
  .catch(error => {
    console.error('Error:', error.message);
  });

We use .then() to handle the successful resolution of the promise returned by getUserData, logging the fetched data's message.

We use .catch() to handle any errors that occur during the promise chain, logging the error message.

Async and Await

function callback(value,time,number){
  return new Promise (function(resolve,reject){
    if(value){
      setTimeout(function() {
        resolve("successfully done",number)
      }, time*1000);
     }else{
      reject("Error got",number)
     }
   })
}
async function main(){
  const data1 = await callback(true,10,1)
  const data2 = await callback(true,2,2)
  const data3 = await callback(true,3,3)
}
main()
/* output:
successfully done 1
successfully done 2
successfully done 3
*/

Async/await is a feature in JavaScript that allows you to work with asynchronous code in a more synchronous-like manner, making it easier to write and understand asynchronous code.

Async Functions always return a promise. Await Keyword is used only in Async Functions to wait for promise.

JS Async/Await is the extension of promises that we get as support in the language. In this article, we will be learning about async and await in JavaScript with examples.

Promises vs Async..Await

Promises are objects representing the eventual completion or failure of an asynchronous operation. They have three states: pending, fulfilled, or rejected. You create a promise using the Promise constructor, passing a function with resolve and reject parameters. Promises use .then() to handle the resolved value and .catch() to handle any errors.

Async functions always return a promise. The async keyword before a function declaration indicates that the function will always return a promise. await is used to wait for a promise to settle. It can only be used inside async functions. It allows writing asynchronous code in a more synchronous style, making it easier to understand and maintain.

Web Browser APIs

Web browser APIs (Application Programming Interfaces) in JavaScript provide functionalities and capabilities for interacting with various aspects of the web browser environment. These APIs are built into web browsers and are accessible to JavaScript code running in the browser environment. They enable developers to create dynamic, interactive, and feature-rich web applications.

Fetch API: Fetch API is used to make HTTP requests in the browser. It provides a more powerful and flexible interface compared to older XMLHttpRequest.


fetch('https://github.com/561sharath')
    .then(response => response.json())
    .then(data => console.log(data))
    .catch(error => console.error('Error fetching data:', error));

Console API: The Console API is used for logging information to the browser's console.

console.log('This is a log message');
console.error('This is an error message');
console.warn('This is a warning message');

Geolocation API: The Geolocation API provides access to the device's geolocation data (latitude and longitude).

if ('geolocation' in navigator) {
    navigator.geolocation.getCurrentPosition(position => {
        console.log('Latitude:', position.coords.latitude);
        console.log('Longitude:', position.coords.longitude);
    }, error => {
        console.error('Error getting location:', error);
    });
} else {
    console.error('Geolocation is not supported by this browser');
}