Skip to content

Latest commit

 

History

History
2646 lines (2146 loc) · 71.8 KB

Notes.md

File metadata and controls

2646 lines (2146 loc) · 71.8 KB

Namaste JavaScript

What is Javascript ?

JavaScript, often abbreviated JS, is a programming language that is one of the core technologies of the World Wide Web, alongside HTML and CSS. Many websites use JavaScript on the client side for web page behavior, often incorporating third-party libraries.

"Javascript is a synchronous single-threaded language and everything in it happens inside an execution context"

  • This means that it can only move on the next line once the single line operation is complete
  • The execution context consists of Memory(Variable Enviornment) and Code(Thread of Execution) as two components, the memory component consists of variable and functions as key-value pairs and the code component consists of the instructions that are executed.
// Code Sample

var n = 2;
function square(num) {
  var ans = num * num;
  return ans;
}

var square2 = square(n);
console.log(square2);

Output:

4

Memory Allocation Phase

  • In the first step, javascript will skim through the code and will allocate memory to all the variables and functions.

  • It stores a special value of undefined for all the variables and incase of functions it will store the whole function code.

Code Exectution Phase

  • In the second step, javascript will again rundown the code line by line

  • It will set n=2 and since there is nothing to exectue from line 2 to 5 it will move on to var sqaure2 where it will encounter a function invocation.

  • When it encouters a function invocation, a brand new execution context is created inside the code section

  • So again the memory will be allocated to the varibales num and ans and they will store the value undefined

  • Now comes the part of code execution, in which we will be executing each line inside the function

  • In square(n) the value of n is 2 (argument) is passed to num as a parameter

  • Now in the code section, the operation num * num is performed and the result is stored in ans

  • In the end, we have a return keyword which means that we are done with the execution of the function and have to return the control back to the execution context where the function was invoked.

  • The return ans which is present in the code section finds the value of 4 inside the memory section and returns it to the execution context where the function was invoked and hence the value of square2 changes from undefined to 4.

  • Once the whole code is completed, then all the execution contexts are destroyed and the memory is freed up

Call Stack

JavaScript uses the call stack to keep track of several function calls. It works in data structures like a true stack, allowing data to be pushed and popped and adhering to the Last In First Out (LIFO) principle. We use the call stack to remember which function is currently active.

Names for stack

  • Execution Stack:
  • Program Stack
  • Control Stack
  • Runtime Stack
  • Machine Stack


Hoisting is JavaScript

Hoisting as a core concept relies on the way how Execution Context is created. In the first phase i.e. the Memory Allocation Phase all the variables and functions are allocated memory, even before any code is executed. All the variables are assigned undefined at this point in time in the local memory.

Case -1

getName();
console.log(x);

var x = 7;
function getName()
{
    console.log("JavaScript");
}

The Output would be:

JavaScript 
undefined

This is because javascript first skims through the code and looks for the variables and functions and then assigns the value to the variables which is undefined.

If we remove var x = 7 from the code, then the console.log(x) will give Uncaught error: x is not defined.

Case-2

var x = 7;
console.log(getName);
function getName()
{
    console.log("JavaScript");
}

The Output would be:

function getName()
{
    console.log("JavaScript");
}

Case-3

console.log(getName);
var x = 7;
var getName  = () =>
{
    console.log("JavaScript");
}

// function expresssion
var b = function () {
console.log("This declaration makes function not hoisted!");
}

Output would be:

undefined
Not Hoisted

Here, the output will be undefined because javascript will take this var as a typical variable and not count it as a function.



How Function work in JS ?

Code Sample 1

var x = 1;
a();
b(); 
console.log(x);
function a(){
    var x = 10;
    console.log(x);
}

function b(){
    var x = 1000;
    console.log(x);
}

Output would be:

10 
1000 
1 
  • Global Execution context is created and memory is allocated to var x, function a and b.
  • Var x will store the value undefined and function a and b will point to the function body.
  • When the code will start running, first thing that will take place is that x's undefined will be replaced with the value 1
  • After this the function a will be invoked and its own execution context will be created in the call stack ,which will store the value of seperate x=10
  • Similar thing will happen with function b
  • As soon as these functions are executed, their memory and space is deleted from the call stack.
  • In the end the control goes to global execution context which console logs the value of x = 1 in the end.


Global Object

  • Whenever you create an execution context, this is created along with it even for the functional execution of the context and even for this global execution context.
  • At Global level the this points to global object that is the window incase of the browser
  • Anything that is not inside any function is called as global object.
var x = 1;           ---> Global Varibale  
function a () = {
   var b = 3;
}
console.log(window.a);
console.log(this.a);
console.log(b); 
  • When we will go to console in browser and type window we will get x: 1 and a(): f a() as a part of the window
  • b wont be there as a part of window, Thus the output will be undefined


Undefined and Not-Defined

  • Undefined in javascript is some undefined value given to a variable or function before the execution of the program. It is not considered empty as it consumes memory space as well
  • Not-Defiend in javascript is somehing which is not allocated space and not declared in the program
  • JavaScript is losely typed language, which means that anything can be allocated to anything i.e. var can be allocated as string, int, boolean.
var a;
console.log(a);
var a = 10;
console.log(a);
a = "Hello World" 
console.log(a);

Output:

undefined 
10 
Hello World
foo = 1;
console.log(foo);
var foo;

var x; 
console.log(x);
x = 1;

Output:

1
undefined


The Scope Chain and Lexical Enviornment

function a(){
    var b = 10;
    c();
    function c(){
        console.log(b);
    }
}
a();

Output:

10
  • In Javascript the lexical enviornment is created when a local memory of every execution context of javascript is nested in one another and accesed by the lowest lexicographic enviornment
  • Scope Chaining is the method by which the lexical enviornment is created and accessed by the execution context.
  • In the above example: The javascript first creates the lexical enviornment for a and then for c and then for b
  • Then the lexical enviornment is accessed by the c function and the value of b is printed.
  • For reference do watch the video below. ( Important )


let and const in Javascript

console.log(x);
console.log(a);
console.log(b);
let a = 10;
var b = 100;

Output:

Uncaught ReferecneError: x is not defined
Uncaught ReferenceError: Cannot access 'a' before initialization
undefined
  • Hoisting is done for let and const variables as well, but they are stored in a different meomry space which the users cannot access. (Script)
  • Any line written above let variable for accessing it is defiend as Temporal Dead Zone for that variable.
  • let and const variables give an output of undefined on accessing them through the keywords this and window in global execution
  • Let and Const are block Scoped and Var is function scoped
let a = 10;
let a = 100;
var a = 212;
console.log("abcd");

Output:

Uncaught SyntaxError: Identifier 'a' has already been declared 
  • This means that you cannot redeclare the same variable in the same scope for let and const
  • Incase of var it is possible
// Correct Way  
const b = 10;

// Incorrect Way - 1
const b; 
b = 100;

// Incorrect Way - 2
const b = 8; 
b = 88;

Output:

Uncaught SyntaxError: Missing initializer in const declaration
Uncaught TypeError: Assignment to constant variable.
  • This is because const is a constant variable and it is not possible to change the value of it.

  • That's the reason why all the initializations are kept on the top to minimize the temporal dead zone.

  • Other Examples

//Works fine
{
  var y = 10;
}
function inner() {
  console.log(y);
}
inner();

// Works fine
let x = 10;
function inner() {
  console.log(x);
}
inner();


// Gives Error as let is block scoped
{
  let z = 10;
}
function inner() {
  console.log(z);
}
inner();


Block Scope and Shadowing in Javascript

  • We group multiple statements in a block so that we can use it where javascript expects one statement
{
    //Compound Statement
    var a = 10;
    let b = 20;
    const c = 30;
}
console.log(a);
console.log(b);
console.log(c);

Output:

ReferenceError: c is not defined
  • This is because the var is hoisted in the global block level, let and const variables are not hoisted to the top of the block instead they are hoisted in Block level.
var a = 100; 
console.log(a);
{
    //Compound Statement
    var a = 10;
    let b = 20;
    const c = 30;
    console.log(c);
}
console.log(a);

Output:

100 
30
10
  • Here comes the concept of shadowing, as in the above example we can see that when var a is declared on the first line and it gives 100 as the output for the first time, but later when we declare it in the block level scope and then try to log the value of a it gives 10 as the output. This happens because of the concept of shadowing.
  • Shadowing does not take place in the case of let and const variables.
const c = 100;
function x () {
    const c = 10;
    console.log(c);
}
x();
console.log(c);

Output:

10
100
  • Shadowing works the same in case of functions as well
let a = 10;

// Illegal Shadowing 
{
    var a = 20;
}
// Legal Shadowing 
{
    let a = 20;
}
  • Illegal shadowing is when you try to declare a variable with the same name as an existing variable in the same scope.
const a = 20;
{
    const a = 19;
    {
        const a = 12;
    }
    console.log(a);
}

Output:

19
  • Lexical Scope is also followed incase of Block level functions i.e each and every block has its own lexical enviornment. It follows lexical scope chain pattern as well.



Closures

  • Closure is a function bundled along with its lexical scope
function x() {
  var a = 7;
  function y() {
    console.log(a);
  }
  return y;
}
var z = x();
console.log(z);
z();

Output:

f y(){
    console.log(a);
}

7 
  • In the first case when console.log(z) is executed, it will return the function y() itself as it is the return value of the x() function.
  • In the second case, 7 is prinited as the function remembers it's lexical scope and remembers the value of a which is 7.
  • return y statement means that a closure is rerturned and it is put inside z.
function x() {
  var a = 7;
  function y() {
    console.log(a);
  }
  a = 100;
  return y;
}
var z = x();
z();

Output:

100
  • Here the value of a is changed to 100 and the function remembers it's lexical scope and remembers the value of a which is 100.
function z() {
  let b = 912;
  function x() {
    var a = 7;
    function y() {
      console.log(a,b);
    }
    y();
  }
  x();
}
z();

Output:

7 912
  • Another example how closures helps us to avoid the use of global variable by remembering the value of the variable in the lexical scope.


First Class Functions ft. Anonymous Functions

  • Difference between Function Statement and Function Expression
  • The difference lies in the hoisting of the function
  • The function a will be hoisted and will give undefined as the output
  • But function b will give Error as it it will be treated like any other varibale and will be given undefined intially but on final execution it will see that b is a varibale and not a function, thus it will give Error
//Function Statement 
a();
function a(){
  console.log("a is called");
}

//Function Expression 
b();
var b = function (){
  console.log("b is called");
}
  • Function Statement and Function Declearation are the same in javascript.
//Anonymus Function 
function () {

}

Output:

Uncaught SyntaxError: Function statements requires a function name
  • The Use of Anonymus function is used in places like function expression where there is no name given to the function
//Named Function Expression 
var b = function xy(){
  console.log("Hello World");
}
b();
xy();

//Named Function Expression - 2
var b = function xy(){
  console.log(xy);
}
b();

Output:

Hello World
Reference error: xy is not defined
function xy(){
  console.log(xy);
}
  • The use of named function expression is used in places where we want to call the function later on.
  • This gives error because xy is not present in the outer scope but it is created a s local variable
  • We can access this function in its own scope by using the name of the function.
//Difference Between Parameters and Arguments 

function a(x,y){  // -- Paramenters
  console.log(x,y);
}
a(1,3); //-- Arguments 

Ouptut

1 3
//First Class Function - 1 
var b = function(param1){
  console.log(param1);
}
b(function xyz(){
});

//First Class Function - 2
var b = function(param1){
  console.log(param1);
}
function xyz(){

}
b(xyz);


//First Class Function - 3
var b = function(param1){
   return function(){

   };
};
console.log(b());

Output:

[Function: xyz] 
[Function: xyz]
[Function: anonymous]
  • First Class Functions are the ability of the functions to be used as values and passed into as arguments in another function and can be returned as a function
  • It is also called First Class Citizens


setTimeout and Closure - Interview Questions

function x(){
   var i = 1;
   setTimeout(function(){
      console.log(i);
   },3000);
   console.log("Hello World");
}
x();

Output:

Hello World
//Waits for 3 seconds and then prints 1
1
  • Here the setTimeout function is called with a function as an argument.
  • The function is called after 3 seconds and prints the value of i which is 1.
  • setTimeout takes this call back function and attaches a timer, when the timer expires it calls the function
//Code Example -1 
function x() {
  for (var i = 1; i < 5; i++) {
    setTimeout(function () {
      console.log(i);
    }, i * 1000);
  }
  console.log("Hello World");
}
x();

//Code Example - 2
function x() {
   for (let i = 1; i < 5; i++) {
     setTimeout(function () {
       console.log(i);
     }, i * 1000);
   }
   console.log("Hello World");
 }
 x(); 

 //Code Example - 3
 function x() {
   for (var i = 1; i < 5; i++) {
      function close(x){
         setTimeout(function () {
            console.log(x);
          }, x * 1000);
        }
      close(i);
      }
 }
 x();

Output:

//Ouptut of Code Example - 1
Hello World
5
5
5
5
//Ouptut of Code Example - 2
Hello World
1
2
3
4
//Ouptut of Code Example - 3
Hello World 
1 
2
3
4
  • In Code Example -1 the value of 'i' is remembered as reference and not as the value
  • When the loop runs the first time, makes a copy of this function, attaches a timer and also remebers the reference of 'i'
  • Javascript does not wait for anything, it will run the loop again and again, it will not wait for the timer to expire.
  • And when the timer expires it is too late, because the loop was constantly running, the value of i became 5 and when the call back function runs, the var i becomes 5
  • In Code Example -2, we have used let keyword instead of var keyword, which has a block level scope and each and every time this setTimeout is called, the function forms a closure with a new variable itself, this means the value of i is new in every iteration.
  • In Code Example -3 we have made a closure named close() , because here everytime we create x in the above part everytime the loop is executed


Callback Functions in JS ft. Event Listeners

  • Callback Functions are functions that are passed as arguments to other functions.
setTimeout(function () {
 console.log("timer");
}, 2000);

function x(y) {
 console.log("Hello");
 y();
}

x(function y() {
 console.log("World");
});

Output:

Hello
World 
timer
  • Any function that block the Call Stack is termed as blocking the main thread
  • To sum up, if javascript did not have this first class citizen concept, it would not be able to use callback functions. (like setTimeout function is used) or else everything would be kept on getting blocked if any operation took a longer time to complete.
  • If it wasnt for the first class citizen concept, we would be unable to complete this asyncronus operations

Event Listeners in JavaScript

  • Event Listeners are functions that are attached to an event.
  • In the below example we have a button on the page and when the button is clicked an event listener is already attached to the button, which calls the function xyz() (Callback function)
<!DOCTYPE html>
<html lang="en">
<head>
    <title>Document</title>
</head>
<body>
    <h1>Event Listeners in JavaScript</h1>
    <button id="clickMe">Click Me</button>
    <script src="index.js"></script>
</body>
</html>
//Example - 1
document.getElementById("clickMe").addEventListener("click", function xyz() {
  alert("Button clicked");
});
  • In simple terms, this snippet of code means that it will pick up the "clickMe" element and it will add an event listener (which is "click"), and when the event is triggered it will call the function xyz() (Callback function) thus pulling it into the call stack.
//Example - 2: Couting Number of Clicks -- Wrong Approach 
 let count = 0;
  document.getElementById("clickMe").addEventListener("click", function xyz() {
    console.log(count++);
  });
//Example - 3: Couting Number of Clicks -- Right Approach 
function x() {
  let count = 0;
  document.getElementById("clickMe").addEventListener("click", function xyz() {
    console.log(count++);
  });
}
x();
  • In the above code snippet we have used the conept of closures, so that the count variable cannot be accessed outside the function.

  • let is used instead of var so that it block scoped i.e value cannot be modified beyond the function

  • Event Listeners are heavy, it takes memory, so whenever you attach an event listener it forms a closure and even when the call stack is empty, still the program will not free up that memory because you never know when someone can click on that button and might need this closure to function

  • So once we remove this event listener, so all the varibales that were held by this closure are garbage collected



Asynchronus JavaScript and Event Loop from scratch

FAQs

  1. When does the event loop actually start? - Event loop, as the name suggests, is a single-thread, loop that is almost infinite. It's always running and doing its job.
  2. Are only asynchronous web API callbacks are registered in the web API environment? - YES, the synchronous callback functions like what we pass inside map, filter, and reduce aren't registered in the Web API environment. It's just those async callback functions that go through all this.
  3. Does the web API environment stores only the callback function and pushes the same callback to queue/microtask queue? - Yes, the callback functions are stored, and a reference is scheduled in the queues. Moreover, in the case of event listeners(for example click handlers), the original callbacks stay in the web API environment forever, that's why it's advised to explicitly remove the listeners when not in use so that the garbage collector does its job.
  4. How does it matter if we delay for setTimeout would be 0ms. Then callback will move to queue without any wait? No, there are trust issues with setTimeout(). The callback function needs to wait until the Call Stack is empty. So the 0 ms callback might have to wait for 100ms also if the stack is busy. It's a very beautiful concept, and I've covered this in detail in the next episode of Namaste JavaScript.

The Video linked below is an amazing explanation for it! Save it and watch it again!



Trust Issues with setTimeout()

  • A setTimeout() with a delay of 5000ms does not always exactly wait for 5000ms.
  • The code snippet given below demonstrates the problem
// Example - 1

console.log("Start");

//The callback function is delayed for 5000ms
setTimeout(function cb(){
  console.log("Callback Cb");
}, 5000);

console.log("End");

// This code snippet is for simulating ~1 million lines of code which can take a time of around 10ms to execute. (Blocking the main thread). 
// We have a startDate which gets the currentTime in milliseconds and then in the while loop we are adding 10,000ms to it and making endDate equal to the currentTime in milliseconds.
let startDate = new Date().getTime();
let endDate = startDate;
while(endDate < startDate + 10000) {
   endDate = new Date().getTime();
}
console.log("While Expires");

Output:

Start 
End 
While Expires 
Callback Cb 

Explanation

  • First of all Start will be printed on the console.
  • Next we have a setTimeout(), the function inside it will be registered in the web API environment and the timer will start from 5000ms.
  • Now End will be printed in the console
  • Next, the while loop which is a part of the global execution context will start for next 10,000ms, it will block the main thread
  • But after 5000ms the callback function will be finished executing but it will stay inside the Callback Queue and will only execute after the Event Loop finds that after 10,000 ms the Call Stack is empty (Since GEC is still present because of Time code) and then only it will shift the callback function to the main call stack.
  • Thus, While Expires will be printed in the console first and then Callback Cb will be printed
  • This is also known as the Concurrency Model in JavaScript
// Example-2: setTiemout() with a delay of 0ms

console.log("Start");

setTimeout(function cb(){
  console.log("Callback Cb");
}, 0);

console.log("End");

Output:

Start 
End
Callback Cb 
  • A misconception about this code is that output would be Start,Callback Cb,End because the callback function is executed for 0ms.
  • But that is certainly not the case, even when the timer is for 0ms, this callback function has to go through a queue and wait for the Call Stack to be empty i.e when GEC is also not present.
  • Thus, the correct output would be Start,End,Callback Cb
  • The usecase of setting setTimeout() to 0ms will be if you want to defer a piece of code i.e execute that piece of code when the whole program is executed. Example: Implementing infinite scrolling in a web application.


Map, Filter and Reduce

  • Map function is being used here to convert the array into a new array with modified values.
//Example of Map
const arr = [4,5,6,23,4];
const newArr = arr.map(function(item){
    return item*2;
});
console.log(newArr);

//--- Just Another Way to write it 

// function double(item)
// {
//   return item*2;
// }
// const output = arr.map(double);
// console.log(output);

//---- Short Hand-Fat Arrow Syntax
// const opt = arr.map((x) => x*2);

Output:

[ 8, 10, 12, 46, 8 ]
  • Filter function is used to filter the array based on the condition.
const arr = [4,5,6,23,4];
//Filter out Values odd 
const output = arr.filter((item) => item % 2 !== 0);
console.log(output);

Output:

[ 5, 23 ]
  • Reduce Function- It is used in places where we have to find a particular value, Used for Sum or Max
const arr = [4,5,6,23,4]; 

//Traditional Way
function findSum(arr){
 let sum =0;
 for(let i=0;i<arr.length;i++){
   sum = sum + arr[i];
 }
 return sum;
}
console.log(findSum(arr));

//acc- Accumilator and curr- Current
//0 is the is the initial value of accumilator
const output = arr.reduce(function(acc,curr){
  return acc + curr;
},0);
console.log(output);

//Find Maximum Number in the Array
const max = arr.reduce(function(acc,curr){
 if(curr>acc){
   acc = curr;
 }
 return acc;
},-Infinity);
console.log(max);

Output:

42 
42 
23
  • Task: To get the First And Last Name of the User
const users = [
  {firstName: 'John', lastName: 'Doe', age: 20},
  {firstName: 'Mark', lastName: 'Holland', age: 24},
  {firstName: 'Jeff', lastName: 'Musk', age: 21},
  {firstName: 'Tobby', lastName: 'Wayne', age: 20},
]

const getUsers = users.map(function(x){
  console.log(x.firstName+" "+x.lastName);
});

//---- Short-Hand Fat Arrow Syntax
// const getUsers = users.map(x => console.log(x.firstName+" "+x.lastName));

Output:

John Doe
Mark Holland
Jeff Musk
Tobby Wayne
  • Task-2: Get the output as { '20': 2, '21': 1, '24': 1 }
const users = [
  {firstName: 'John', lastName: 'Doe', age: 20},
  {firstName: 'Mark', lastName: 'Holland', age: 24},
  {firstName: 'Jeff', lastName: 'Musk', age: 21},
  {firstName: 'Tobby', lastName: 'Wayne', age: 20},
]

const output = users.reduce(function(acc,curr){
   if(acc[curr.age]){
      acc[curr.age]++;
   }
   else{
     acc[curr.age]=1;
   }
   return acc;
},{});

console.log(output);

Output:

{ '20': 2, '21': 1, '24': 1 }
  • Task-3 - Find the First and Last name of people with age greater than 21
//Using Chain of Map and Filter
const users = [
  {firstName: 'John', lastName: 'Doe', age: 20},
  {firstName: 'Mark', lastName: 'Holland', age: 24},
  {firstName: 'Jeff', lastName: 'Musk', age: 21},
  {firstName: 'Tobby', lastName: 'Wayne', age: 20},
]

const output = users.filter((x)=> x.age > 20).map(function(y){
  return y.firstName + ' ' + y.lastName;
});
console.log(output);

Output:

[ 'Mark Holland', 'Jeff Musk' ]
  • Task 4: Achieve the above task using Reduce
const users = [
  {firstName: 'John', lastName: 'Doe', age: 20},
  {firstName: 'Mark', lastName: 'Holland', age: 24},
  {firstName: 'Jeff', lastName: 'Musk', age: 21},
  {firstName: 'Tobby', lastName: 'Wayne', age: 20},
]

const output = users.reduce((acc, curr)=>{
     if(curr.age>21) {
        acc.push(curr.firstName + ' ' + curr.lastName);
      }
      return acc;
}, []);
console.log(output);

Output:

[ 'Mark Holland', 'Jeff Musk' ]


Higher Order Functions

  • A function which takes another function as an argument or returns a function as a result is called Higher Order Function.
function x(){
  console.log("Hello World");
}
function y(x){
   y();
}

Functional Programming advantages:

  • DRY principles are followed
  • Each function is independent with its own task
  • Functions are reusable
//--- Code written without following DRY (Don't Repeat Yourself) Principles 

// Radius of circles 
const radius = [1,2,3,4];

// Area Function 
const calculateArea = function(radius){
  const output = [];
  for(let i=0;i<radius.length;i++)
  {
    output.push(Math.PI*radius[i]*radius[i]);
  }
  return output;
}
console.log(calculateArea(radius));

// Circumference Function 
const calculateCircumference = function(radius){
  const output = [];
  for(let i=0;i<radius.length;i++)
  {
    output.push(2*Math.PI*radius[i]);
  }
  return output;
}
console.log(calculateCircumference(radius));

// Diameter Function 
const calculateDiameter = function(radius){
  const output = [];
  for(let i=0;i<radius.length;i++)
  {
    output.push(2*radius[i]);
  }
  return output;
};
console.log(calculateDiameter(radius));

Output:

[
  3.141592653589793,
  12.566370614359172,
  28.274333882308138,
  50.26548245743669
]
[
  6.283185307179586,
  12.566370614359172,
  18.84955592153876,
  25.132741228718345
]
[ 2, 4, 6, 8 ]
//---- Modular or Reusable Code using Higher Order Functions
const radius = [1,2,3,4];


const calculate = function(radius, logic){
  const output = [];
  for(let i = 0; i < radius.length; i++){
    output.push(logic(radius[i]));
  }
  return output;
};


const area = function(radius){
  return Math.PI*radius*radius;
};

const diameter = function(radius){
  return radius*2;
};

const circumference = function(radius){
  return 2*Math.PI*radius;
};

console.log(calculate(radius,area));
console.log(calculate(radius,diameter));
console.log(calculate(radius,circumference));

Output:

[ 3.141592653589793, 12.566370614359172, 28.274333882308138, 50.26548245743669 ]
[ 2, 4, 6, 8 ]
[ 6.283185307179586, 12.566370614359172, 18.84955592153876, 25.132741228718345 ]


Core JavaScript Fundamentals

Call, Apply and Bind Methods in Javascript

  • Brute way is to call the function directly, but we will use call method -- function borrowing, call method is used to call the function with the context of the object
  • Apply method is used to call the function with the context of the array
  • Bind method is used to call the function with the context of the object and also to store the context of the object in a variable
  • The bind() method creates a new function, when invoked, has the this sets to a provided value. The bind() method allows an object to borrow a method from another object without making a copy of that method
let printFullName = function(hometown , state) {
  console.log(this.firstname + " " + this.lastname + " " + "is from " + hometown + " " + "and lives in " + state);
}

let name = {
  firstname: "John", 
  lastname: "Doe",
};

let name2 = {
  firstname: "Jane",
  lastname: "Night",
};

// Call Method 
printFullName.call(name, "Manhattan", "New York");
printFullName.call(name2, "Brooklyn", "New York");

// Apply Method 
printFullName.apply(name2, ["Brooklyn", "New York"]);

// Bind Method 
let printFullName2 = printFullName.bind(name, "Manhattan", "New York");
console.log(printFullName2);
printFullName2();

Output

John Doe is from Manhattan and lives in New York
Jane Night is from Brooklyn and lives in New York
Jane Night is from Brooklyn and lives in New York
[Function: bound printFullName]
John Doe is from Manhattan and lives in New York


Polyfill for Bind Method

let name = {
  firstname: "John",
  lastname: "Doe",
}

let printName = function( hometown ,state, country){
  console.log(this.firstname + " " + this.lastname + " " + hometown + " " + state + " " + country);
};

// Inbuilt Bind Method 
let printMyName = printName.bind(name, "New York", "NY");
console.log(printMyName);
printMyName("USA");

// Creating our own Bind method 
//1. Put the function in the global scope
//2. Create a new function
//3. Create a new context
//4. Return the new function

Function.prototype.mybind = function (...args) {
  let context = this, 
  params = args.slice(1);               //Except the first argument everything is consoidered
     return function (...args2){
       context.apply(args[0], [...params, ...args2]);
     }
};

let printMyName2 = printName.mybind(name, "New York", "NY");
printMyName2("USA");

Output:

[Function: bound printName]
John Doe New York NY USA
John Doe New York NY USA


Debouncing in Javascript

  • Debouncing is a technique to avoid the function to be called too often, hence avoiding UI freezing.
  • The below example is of debouncing function, where user searches for a product in search bar, if he/she types more than 1 character, the function will be called again and again until the user types faster than 300 milliseconds, in that case lesser number of function calls will be made.
let counter = 0;
const getData = () => {
  console.log("Fetching Data.. ", counter++);
};

//Only debounce and call the getData() method when the difference between two key presses is greater than 300 ms
const debounce = (fn, delay) => {
  let timer;
  return function () {
    let context = this,
      args = arguments;

    clearTimeout(timer);
    timer = setTimeout(() => {
      fn.apply(context, arguments);
    }, delay);
  };
};

const betterFunction = debounce(getData, 300);

Without Debouncing:

With Debouncing:



Debouncing vs Throttling

  • Both are used to optimize the performance of the web app, it happens by limiting the rate of function calls
  • Debouncing happens when the user gives a pause of that specified delay (say 300ms) amount set by the website and then only further results are fetched from the server.
  • Example: If a user wants to search for 'Adidas Shoes' on an e-commerce webiste, if debouncing is not there in the website then for every word that user will type, API calls will be made to the server which can lag the UI (imagine for millions of users accessing that website). If debouncing is being used in the website at a delay of 300ms, then after typing without a gap of 300ms user can get the results without making lot of function/API calls to the server. Like if he/she types 'Adidas' within 300ms then only 1 call will be made and next call will be made for 'Adidas Shoes' after 300ms.
  • Throttling is also similar to Debouncing but here there is a time frame specified by the website, in that time frame only one function call will be made.
  • Example: When a user searches for 'Adidas Shoes', and if the throttling dealy time is 300ms, then no matter how fast the user types, the function call will only be made after 300ms after the user starts searching.
  • So in-case of a searching bar of an e-commerce website, debouncing is better than throttling because if you type at a decent speed, then it will make API calls faster for that keyword. In throttling you might have to wait for a while to get the results, as the website has a specified delay.
  • In-case we have to track website resizing. In debouncing suppose the website is being resized at varying speeds then in that case if the user gets slow while resizing then it will make few API calls. So it will perfect for finding how many times the user is resizing.In throttling, the function call will be made only after certain intervals of time, which will satiate our need for finding the frequency of the user resizing the screen.


Throttling

  • Throttling is similar to Debouncing, but here there is a time frame specified by the website, in that time frame only one function call will be made.

Normal Thorttling Code:

const expensive = () => {
  console.log('expensive');
}

const betterExpensive = () => {
  throttle(expensive, 5000);
}

window.addEventListener('resize', betterExpensive);

Making your own throttling function:

const throttle = (func, limit) => {
  let flag = true;
  return function () {
    let context = this;
    args = arguments; 
    if (flag) {
      func();
      flag = false;
      setTimeout(() => {
        func.apply(context, args);
        flag = true;
      }, limit);
    }
  };
};

window.addEventListener('resize', throttle(() => {
  console.log('Window resized');
}, 5000));

Output:



Function Currrying

Using Bind Method

  • In the below example we have made a copy of the subtract method and we can make more methods out of it and pass some arguments in the function. Like 2 is set as x in subtract function.
let subtract = function (x, y) {
  console.log(x - y);
};

let subtractByTwo = subtract.bind(this, 2);
let subtractByNum = subtract.bind(this, 9, 1);

//  How bind function works by creating a copy of the original function. 

// let subtractByTwo = function (y){
//   x = 2;
//   console.log(x * y);
// }

// Passed as the second argument 
subtractByTwo(3);   //This is Currying 
subtractByNum(3);   //This will be ignored

Output:

-1
8

Using Closure Method

  • In the below example we are using closure for currying the function.
let multiply = function (x) {
  return function (y){
    console.log(x * y);
  };
};

let multiplyByTwo = multiply(2);
multiplyByTwo(4);

Output:

8


Event Bubbling and Capturing (Event Trickling)

  • These are the two ways of event propogating in a DOM Tree
  • In Event Bubbling the event is propogated from the selected element and up the hierarchy till the end of the DOM
  • In Event Capturing the event goes down the hierarchy till the selected element and propogates the event to the selected element. Oppposite order of Event Bubbling.
  • Some examples of Bubbling and Capturing are given below:
<!DOCTYPE html>
<html lang="en">
<head>
    <title>Document</title>
    <style>
       div{
             min-width: 100px;
             min-height: 100px;
             padding: 30px;
             border: 1px solid black;
       }
    </style>
</head>
<body>
    <div id="grandparent">
        <div id="parent">
            <div id="child"></div>
        </div>
    </div>
    <script src="index.js"></script>
</body>
</html>

Event Bubbling without any argument being passed:

document.querySelector('#grandparent').addEventListener('click', ()=>{
  console.log('Grandparent clicked!');
});
//If no value is passed as the second argument, the default is false which means bubbling of events.

document.querySelector('#parent').addEventListener('click', ()=>{
  console.log('Parent clicked!');
});

document.querySelector('#child').addEventListener('click', ()=>{
  console.log('Child clicked!');
});
  • On Clicking Child


Event Trickling on passing true as the second argument:

document.querySelector('#grandparent').addEventListener('click', ()=>{
  console.log('Grandparent clicked!');
}, true);

document.querySelector('#parent').addEventListener('click', ()=>{
  console.log('Parent clicked!');
}, true);

document.querySelector('#child').addEventListener('click', ()=>{
  console.log('Child clicked!');
}, true);
  • On Clicking Child

Event Trickling and Event Bubbling simultaneously:

  • Capturing takes place first and then Bubbling
//Example - 1

document.querySelector('#grandparent').addEventListener('click', ()=>{
  console.log('Grandparent clicked!');
}, true); //Capturing

document.querySelector('#parent').addEventListener('click', ()=>{
  console.log('Parent clicked!');
}, false); //Bubbling

document.querySelector('#child').addEventListener('click', ()=>{
  console.log('Child clicked!');
}, false); //Bubbling 
  • On Clicking Child


//Example - 2 

document.querySelector('#grandparent').addEventListener('click', ()=>{
  console.log('Grandparent clicked!');
}, true); //Capturing

document.querySelector('#parent').addEventListener('click', ()=>{
  console.log('Parent clicked!');
}, false); //Bubbling

document.querySelector('#child').addEventListener('click', ()=>{
  console.log('Child clicked!');
}, true); //Capturing 
  • On Clicking Child

Stopping Propogation of Event

  • It is used to stop the propogation of the event.

Incase of Event Bubbling

document.querySelector('#grandparent').addEventListener('click', ()=>{
  console.log('Grandparent clicked!');
}, false); 

document.querySelector('#parent').addEventListener('click', (e)=>{
  console.log('Parent clicked!');
   e.stopPropagation();
}, false); 

document.querySelector('#child').addEventListener('click', ()=>{
  console.log('Child clicked!');
}, false); 
  • On first clicking Grandparent, then Parent (Event Propogation stopped it from going to Grandparent), then Child (Same Case with Child as well, unable to propogate after parent)

Incase of Event Bubbling and Trickling simulataneously

document.querySelector('#grandparent').addEventListener('click', ()=>{
  console.log('Grandparent clicked!');
}, false); //Bubbling

document.querySelector('#parent').addEventListener('click', (e)=>{
  console.log('Parent clicked!');
  e.stopPropagation();
}, false);  //Bubbling

document.querySelector('#child').addEventListener('click', (e)=>{
  console.log('Child clicked!');
}, true); //Trickling
  • On Clicking Child



Event Delegation

  • It is a technique of handling evenets on our web page in a better way
  • Capturing and bubbling allow us to implement one of the most powerful event handling patterns called event delegation
  • In layman terms, Instead of attaching event handlers to each and every child elements, we should rather attach an event handler to parent of these elements
  • Example for Event Delegation:
<!DOCTYPE html>
<html lang="en">
<head>
    <title>Document</title>
</head>
<body>
    <div>
        <ul id="category">
            <li id="laptop"> Laptops </li>
            <li id="cameras"> Cameras </li>
            <li id="phones"> Phones </li>
        </ul>
    </div>
    <script src="index.js"></script>
</body>
</html>
document.querySelector('#category').addEventListener('click', (e) => {
  console.log(e.target.id);
  if(e.target.tagName === 'LI') {
  window.location.href = `/category/${e.target.id}`;
  }
})
  • Example of Behaviour Pattern using Event Delegation
<!DOCTYPE html>
<html lang="en">
<head>
    <title>Document</title>
</head>
<body>
    <div id="form">
        <input type="text" id="name" data-uppercase> 
        <input type="text" id="surname">
        <input type="text" id="address" data-uppercase>
    </div>
    <script src="index.js"></script>
</body>
</html>
document.querySelector('#form').addEventListener('keyup',(e)=>{
   console.log(e);
   if(e.target.dataset.uppercase != undefined){
     e.target.value = e.target.value.toUpperCase();
   }
});

Pros of Event Delegation

  • It saves a lot memory by reducing the number of additional event listeners
  • It also mitigates the risk of performance bottleneck
  • Writing less code (Rather than attaching to each child, we attach it to parent)

Cons of Event Delegation

  • All the events are not bubbled up like scroll, blur etc.
  • If you are using stop propogation anytime, then eventually that would work, to let event delegation work you dont have to use stop propogation and let the events bubble up


Async vs Defer Attributes

  • These are boolean attributes that are used along with script tags to load the external scripts efficiently to the web page
  • When we are loading a web page then they are two major things happening in your browser, one is the HTML parsing and the second is the loading of the scripts
  • The loading of the scripts consists of two parts, one is fetching scripts from the network and the other is executing the scripts

  • Normal Case: The browser is parsing the HTML line by line and suddenly encounters a script tag, then the browser stops the parsing at that point of time and then sees the script tag and fetches the script from the network and then executes the script. Then after the script work is done, the HTML parsing will continue.

  • Async: In this case, meanwhile the HTML parsing is going on, the scripts are fetched from the network asyncronously, then HTML parsing is stopped, and the scripts are executed. Once these scripts are executed, then the remaining HTML parsing is completed.

  • Defer: In this case, meanwhile the HTML parsing is going on, the scripts are fetched from the network in parallel. Once the HTML parsing completes then the scripts are executed.

When to use what ?

  • The async attributes does not guarantee the order of execution of the scripts but defer does
  • Like if some scripts are dependent on other scripts, then we should use defer attribute, since defer attribute will execute the scripts in the order of their loading
  • But suppose we have to load some external scripts like Google Analytics which are quite modular and are independent of our modular code, so it makes sense to use async attribute


Local Storage and Session Storage

  • Web Storage API is nothing but key value pair storage, which is used to store data in the browser
  • There are two ways to store this data in the browser, one is local storage and the other is session storage
  • Lets say a user is visiting a web app, as soon as he visits the web app, session is started and data which is stored in the session storage will only be persistant till the user closes the browser
  • But Session Storage is very useful than cookies, session storage data is not being sent to the server while making the network request calls and this session storage has a larger capacity to hold (~5MB)
  • Local Storage is also similar to session storage but the difference here is that once the user closes the tab, the data is still persistant in the local storage

Same Origin Policy of Local Storage and Session Storage

  • Local Storage and Session Storage are not accessible from the web page which is not same origin policy because of security reasons.
  • The Origin comprises of three things: Protocol, Host and Port
  • Lets suppose for the website: http://akshaysaini.in
  • We can access this website from the browser by typing in the address bar: http://akshaysaini.in/data.php
  • We cant access this website on:
    https://akshaysaini.in
    https://blog.akshaysaini.in
    https://akshaysaini.in:8080
    because of the changes made to protocol, host and port respectively.

Important Commands of Local Storage

Example:

Wrong way of parsing:

Correct way of parsing:



Prototype and Prototypal Inheritence

  • Whenever we create a JavaScript Object/Function, the JavaScript engine automatically without even letting you know attaches your objects/function to some hidden functions and properties which is called Prototype.


Example for Prototype Chaining in Array:

let arr = ["Aditya", "Akshay"]; 

  • Array.prototype === arr.__proto__
  • arr.__proto__ === Object.protoype because the Array is an object and the Object is an object
  • arr.__proto__.__proto__.__proto__ === null because the Object's prototype has no prototype

Example for Prototype Chaining in Object:

let object = {
    name: "John",
    city: "New York",
    getIntro : function() {
       console.log(`Name is ${this.name} and city is ${this.city}`);
    }
}

  • Prototype chaining proves the statement that everything in JavaScript is an object.

  • So when you try to access the property of object2 which is not there like city then it goes to the object proto and prints when it finds it.

Example of Global Prototype:

Function.prototype.mybind= function(){
    console.log("abcde");
}
function fun(){

}



Namaste JavaScript (Season 2)


Callback Hell

  • Using callback we can do asynchronous programming in JavaScript
  • We can take a piece of code and pass it as an argument to another function and then that function can be called at a later point of time
  • Async programming in JavaScript exist because of callback exist
console.log("Namaste");

 setTimeout( function() {
    console.log("I am inside the setTimeout function");
 }, 2000);
  • An example of callback hell for a shopping cart application
const cart = ["shoes", "pant", "kurta"];
api.createOrder(cart, function () {
  api.proceedToPaymnet(function () {
    api.showOrderSummary(function () {
      api.updateWallet(function () {
        
      });
    });
  });
});
  • When we have a large codebase and we have so many apis here and there, they are dependent one after the another so we fall into this trap of callback hell
  • Our code starts to grow horizontally instead of vertically, this style of code is unreadable and unmaintainable
  • The structure is also called Pyramid of Doom

Inversion of Control

  • We are blindly trusting our createOrder API that it will call the callback function, but what if it doesnt call the callback function, then we are in trouble
  • The createOrder API is a risky API, it could have potential bugs that it might not call the callback function


Promises

  • Promises are objects representing the eventual completion of an asynchronous operation and it's resulting value
  • They are immutable, you cannot alter a promise once it's resolved, it comes with a lot of trust
const cart = ["shoes", "hat", "kurta"];

// Will cause callback hell
createOrder(cart, function(orderId){
   proceedToPayment(orderId);
});

//Resolved using promises 
const promise = createOrder(cart);

// {data: undefined} changes to {data: orderDetails}

promise.then(function(orderId){
   proceedToPayment(orderId);
})
  • Promise is an object which has a function called then which takes a callback function as an argument
  • It is undefined in the beginning but when the promise is resolved then it gets the data
  • createOrder API is an async operation and it returns a promise object
  • The promise object will be filled with the value after an async time, meanwhile other lines of code will be executed

How is it better ?

  • Earlier we were passing the function into the createOrder API i.e another function, but now we are attaching the function to the promise object
  • Promises give us the guarantee that the function present inside the promise object will be called
  • Promises will call the inner function once and only once



const GITHUB_API = "https://api.github.com/users/rishit30g";
const user = fetch(GITHUB_API);

user
  .then((response) => {
    if (response.ok) {
      return response.json();
    } else {
      throw new Error("Something went wrong");
    }
  })
  .then((data) => {
    console.log(data.id);
  })
  .catch((error) => {
    console.log(error);
  });
  • The promise object contains three things: [[Prototype]], [[PromiseState]], [[PromiseResult]]
  • Prototype will say 'Promise'
  • PromiseState will say 'pending' or 'fulfilled' or 'rejected'
  • PromiseResult will say 'undefined intially' or 'data that will be fetched' or 'error'
  • Promise Result will be filled with the data that we get from the API after execution of the promise object

Promise Chaining

const cart = ["apple", "orange", "banana"];


// Callback Hell
createOrder(car, function(orderID) {
    proceedToPaymeNt(orderID, function(status){
        showOrderStatus(status, function(){
            updateWalletBalance();
        });
    });
});


// Equivalent Code using Promise Chaining 
const promise = createOrder(cart);

promise.then(function(orderID){
    return proceedToPaymeNt(orderID);
}).then(function(status){
    return showOrderStatus(status);
}).then(function(){
    return updateWalletBalance();
});


Promises - Part 2

  • Example of Simple promise with resolve and reject
const cart = ["shoes", "pants", "kurta"];

const promise = createOrder(cart);

promise.then(function(orderID){
    console.log("Order ID: ", orderID);
}).catch(function(err){
    console.log("Error: ", err.message);
});

function createOrder(cart){
     const pr = new Promise(function(resolve, reject){
        if(isValidCart(cart))
        {
            const orderID = "1223";
            if(orderID)
            {
                resolve(orderID);
            }
            else{
                const err = new Error("Invalid Order");
                reject(err);
            }
        }
        else{
            const err = new Error("Invalid Cart");
            reject(err);
        }
     })
     return pr;
}

function isValidCart(cart){
    return Array.isArray(cart) && cart.length > 0;
}
  • Example of Promise with Chaining
const cart = ["shoes", "pants", "kurta"];

const promise = createOrder(cart);

promise.then(function(orderID){
    console.log("Order ID: ", orderID);
    return orderID;
}).then(function(orderID){
    return proceedToPayment(orderID);
}).then (function(paymentInfo){
    console.log("Payment Info: ", paymentInfo);
}).catch(function(err){
    console.log("Error: ", err.message);
});

function createOrder(cart){
     const pr = new Promise(function(resolve, reject){
        if(isValidCart(cart))
        {
            const orderID = "1223";
            if(orderID)
            {
                resolve(orderID);
            }
            else{
                const err = new Error("Invalid Order");
                reject(err);
            }
        }
        else{
            const err = new Error("Invalid Cart");
            reject(err);
        }
     })
     return pr;
}

function isValidCart(cart){
    return Array.isArray(cart) && cart.length > 0;
}

function proceedToPayment(orderID){
    const pr = new Promise(function(resolve, reject){
        resolve(`Payment Info is ${orderID}`);
    });
    return pr;
}

Async Await

  • Async is a keyword that is used before a function to make it asyncronous
  • Async functions always returns a promise, so even if we add boolean, float, string as a return type in async function, it will get wrapped in a promise and then it will be returned
  • Here are few examples of async functions:
async function getData() {
  return "Namaste";
}

const dataPromise = getData();
dataPromise.then((res) => console.log(res));

Output:

Namaste 
  • In this example we are creating a promise and returning it through an async function
const p = new Promise((resolve, reject) => {
    resolve("Promise Resolved Value!!");
});

async function getData(){
    return p; 
}

const dataPromise = getData();
dataPromise.then((res) => console.log(res));

Output:

Promise Resolved Value!!
  • Async and Await combination are used to handle promises
  • Await is used in front of a promise to wait for the promise to resolve and then it will return the value
  • Await can only be used inside an async function
const p = new Promise(resolve => {
    resolve("Promise Resolved Value");
});


async function handlePromise() {

  const val = await p;
  console.log(val);
}
handlePromise();

Output:

Promise Resolved Value

Example 1

  • An example of deplayed promise using async and await vs traditional handling
const p = new Promise(resolve => {
    setTimeout(() => {
        resolve("Promise Resolved Value!!")
    }, 5000);
});


async function handlePromise() {

    // JS Engine will wait for the promise to be resolved 
    const val = await p;
    console.log(val);
    console.log("Hello World");
}
handlePromise();


function getData(){
    
    // JS Engine will not wait for the promise to be resolved 
    p.then((res) => console.log(res));
    console.log("Namaste JavaScript");
}

getData();

Output (On only executing handlePromise())

## After 5 seconds.. 

Promise Resolved Value!!
Hello World

Output (On only executing getData())

Namaste JavaScript

## After 5 seconds..

Promise Resolved Value!!

Example 2

  • How async await behaves when there is a statement before the await statement
const p = new Promise(resolve => {
    setTimeout(() => {
        resolve("Promise Resolved Value!!");
    }, 5000);
});


async function handlePromise() {

    console.log("Hello World");
    const val = await p;
    console.log(val);
    console.log("Bye World");
}
handlePromise();

Output:

Hello World

## After 5 seconds 

Promise Resolved Value!!
Bye World

Example 3

  • Having multiple promises in async await function, with same delay both the promises will be resolved at the same time
const p = new Promise(resolve => {
    setTimeout(() => {
        resolve("Promise Resolved Value!!");
    }, 5000);
});


async function handlePromise() {

    console.log("Hello World");
    const val = await p;
    console.log(val);

    const val2 = await p;
    console.log(val2);
    console.log("Hello World 2");
}
handlePromise();
  

Output

Hello World 

## After 5 seconds

Promise Resolved Value!!
Promise Resolved Value!!
Hello World 2

Example 4

  • Having multiple promises in async await function, the promise with a higher delay will be resolved first and then the other promise will be resolved in this case first i.e the promise with higher time is resolved and then the promise with lower time is covered in that time
const p1 = new Promise(resolve => {
    setTimeout(() => {
        resolve("Promise Resolved Value 1!!");
    }, 2000);
});

const p2 = new Promise(resolve => {
    setTimeout(() => {
        resolve("Promise Resolved Value 2!!");
    }, 4000);
});


async function handlePromise() {

    console.log("Hello World!!");
    const val2 = await p2;
    console.log(val2);

    const val = await p1;
    console.log(val);

}
handlePromise();

Output:

Hello World!!

## After 4 seconds 

Promise Resolved Value 2!!
Promise Resolved Value 1!!

Example 5

  • Having multiple promises in async await function, the promise with a higher delay will be resolved first and then the other promise will be resolved in this case first i.e the promise with higher time is resolved and then the promise with lower time is covered in that time
const p1 = new Promise(resolve => {
    setTimeout(() => {
        resolve("Promise Resolved Value 1!!");
    }, 2000);
});

const p2 = new Promise(resolve => {
    setTimeout(() => {
        resolve("Promise Resolved Value 2!!");
    }, 4000);
});


async function handlePromise() {

    const val = await p1;
    console.log(val);

    console.log("Hello World!!");
    const val2 = await p2;
    console.log(val2);

}
handlePromise();

Output:

## After 2 seconds 

Promise Resolved Value 1!!
Hello World!!

## After 2 more seconds

Promise Resolved Value 2!!
  • What we ideally start thinking is that JavaScript has started waiting for functions but that not true, the fact that time, tide and JavaScript wait for none is 100% true.

  • What is happening is that in the callstack the handlePromise() is called and then the first promise is resolved, after that the handlePromise() moves out of the callstack.

  • Again the ongoing line by line process of JavaScript goes on and when another promise is encountered the handlePromise() is bought to the callstack again and it will continue from where it left off in the suspended state.

Fetch API using async-await

  • Fetch is a promise, it gives a response in return, it is a readable stream which needs to converted to JSON which is again a promise that will give a promise in return which can be printed in the console.
const API_URL = "https://api.github.com/users/Rishit30G";

async function handlePromise() {

   const data =  await fetch(API_URL);

   const jsonValue = await data.json();

   console.log(jsonValue);
}

handlePromise();

Handling Errors using APIs

  • It is handled using try and catch block
const API_URL = "https://api.github.com/users/Rishit30G";

async function handlePromise() {
  try {
    const data = await fetch(API_URL);
    const jsonValue = await data.json();
    console.log(jsonValue);
  } catch (err) {
    console.log(err);
  }
}

handlePromise();
  • Another traditional method is by adding a catch statement to the handlePromise() function which is indeed a promise
const API_URL = "https://api.github.com/users/Rishit30G";

async function handlePromise() {
    const data = await fetch(API_URL);
    const jsonValue = await data.json();
    console.log(jsonValue);
}

handlePromise().catch((err) => console.log(err));
  • Equivalent promise code for better understanding
const API_URL = "https://api.github.com/users/Rishit30G";

function handlePromise() {
    return fetch(API_URL)
        .then(data => data.json())
        .then(jsonValue => {
            console.log(jsonValue);
            return jsonValue;
        });
}

handlePromise().catch((err) => console.log(err));

Async Await or Promises .then/catch

  • Async Await is just a syntactical sugar over promises, it is just a different way of writing promises
  • It is just a mordern way of writing promises, we don't have to deal with callbacks and promise chaining

Promise APIs

Promise.all()

  • Promise.all() is used to run multiple promises in parallel, it takes an array of promises as an argument and returns a promise
  • It is used to run multiple promises in parallel and wait for all of them to complete and then do something with the result
  • If one of them fails, then the entire promise.all() fails
const p1 = new Promise((resolve) => {
  setTimeout(() => {
    resolve("Promise Resolved Value 1!!");
  }, 1000);
});

const p2 = new Promise((resolve) => {
  setTimeout(() => {
    resolve("Promise Resolved Value 2!!");
  }, 10000);
});

const p3 = new Promise((resolve, reject) => {
  setTimeout(() => {
    reject("Promise Rejected Value 3!!");
  }, 2000);
});

async function handlePromise() {
  const result = await Promise.all([p1, p2, p3]);
  console.log(result);
}

handlePromise();

Output:

Will get error after 2 seconds, all promises will fail

Promise.allSettled()

  • Promise.allSettled() returns a promise that resolves after all of the given promises have either fulfilled or rejected, with an array of objects that each describes the outcome of each promise.
const p1 = new Promise((resolve) => {
  setTimeout(() => {
    resolve("Promise Resolved Value 1!!");
  }, 1000);
});

const p2 = new Promise((resolve) => {
  setTimeout(() => {
    resolve("Promise Resolved Value 2!!");
  }, 10000);
});

const p3 = new Promise((resolve, reject) => {
  setTimeout(() => {
    reject("Promise Rejected Value 3!!");
  }, 2000);
});

async function handlePromise() {
  const result = await Promise.allSettled([p1, p2, p3]);
  console.log(result);
}

handlePromise();

Output:

[
  { status: 'fulfilled', value: 'Promise Resolved Value 1!!' },
  { status: 'fulfilled', value: 'Promise Resolved Value 2!!' },
  { status: 'rejected', reason: 'Promise Rejected Value 3!!' }
]

Promise.race()

  • Promise.race() returns a promise that fulfills or rejects as soon as one of the promises in an iterable fulfills or rejects, with the value or reason from that promise.
const p1 = new Promise((resolve) => {
  setTimeout(() => {
    resolve("Promise Resolved Value 1!!");
  }, 1000);
});

const p2 = new Promise((resolve) => {
  setTimeout(() => {
    resolve("Promise Resolved Value 2!!");
  }, 10000);
});

const p3 = new Promise((resolve, reject) => {
  setTimeout(() => {
    reject("Promise Rejected Value 3!!");
  }, 2000);
});

async function handlePromise() {
  const result = await Promise.race([p1, p2, p3]);
  console.log(result);
}

handlePromise();

Output:


Promise Resolved Value 1 

Promise.any()

  • Promise.any() takes an iterable of Promise objects and, as soon as one of the promises in the iterable fulfills, returns a single promise that resolves with the value from that promise. If no promises in the iterable fulfill (if all of the given promises are rejected), then the returned promise is rejected with an AggregateError, a new subclass of Error that groups together individual errors.
const p1 = new Promise((resolve) => {
  setTimeout(() => {
    resolve("Promise Resolved Value 1!!");
  }, 1000);
});

const p2 = new Promise((resolve) => {
  setTimeout(() => {
    resolve("Promise Resolved Value 2!!");
  }, 10000);
});

const p3 = new Promise((resolve, reject) => {
  setTimeout(() => {
    reject("Promise Rejected Value 3!!");
  }, 2000);
});

async function handlePromise() {
  const result = await Promise.any([p1, p2, p3]);
  console.log(result);
}

handlePromise();

Output:

Promise Resolved Value 1!! 

// Whichever promise gets fulfilled first, that value will be returned
// Incase all the promises are rejected then it will throw an AggregateError 


this Keyword

  • The this keyword in the global space will represent the global object, it is 'window' in case of browser, and incase of NodeJS it will represent the 'global'

  • this keyword inside a function with strict mode

"use strict";

function x(){
    // Depends on strict / non-strict mode
    console.log(this);
}
x();

Output:

// Strict 
undefined 

// Non Strict
Window Object 
  • If the value of this keyword is undefined or null, then this keyowrd will be replaced with the gloablObject only in case of non strict mode, but in case of strict mode it will remain undefined or null, this is called this substitution

  • this keyword value depends on how the function is called

x(); 
window.x(); 

Output:

//Strict 
undefined 
windowObject 

//Non Strict 
windowObject
windowObject
  • this keyword in object method
"use strict"

const obj = {
    name: "James", 
    x: function(){
        console.log(this);
    }
}

obj.x(); 

Output:

//Strict
{name: "James", x: ƒ}

//Non Strict
{name: "James", x: ƒ}
  • this keyword in call, apply and bind
"use strict"

const student = {
    name: "James", 
    printName: function(){
        console.log(this.name);
    }
}

student.printName();

const student2 = {
    name: "John"
}

student.printName.call(student2); // Call method takes the object as an argument and then it will call the function with the object as the value of `this` keyword

Output:

James
John
  • this keyword in arrow function behaves differently than normal function, it is lexically scoped, it will take the value from the parent scope

Example 1

// var a = 10; 
const obj = {
    a: 10, 
    x: () => {
        console.log(this.a);
    },
}
obj.x();

Output:

// If var a = 10 is not commented 
10

// If var a = 10 is commented
windowObject  

Example 2

const obj2 = {
    a: 10, 
    x: function (){
        const y = () =>{
            console.log(this);
        }
        y();
    }
}
obj2.x();

Output:

{a: 10, x: f}
  • this inside DOM elements: It will give reference to the HTMLelement
<button onClick="alert(this)"> Click Me </button>

Output:

[object HTMLButtonElement]