Skip to content

Latest commit

 

History

History
692 lines (523 loc) · 21.3 KB

File metadata and controls

692 lines (523 loc) · 21.3 KB

1. Async/Await in JavaScript

What is async / await?

  • async is a keyword that is placed before a function declaration to indicate that the function will return a Promise.
  • await can only be used inside an async function. It pauses the execution of the function until the promise resolves and returns the resolved value.

How does it work?

  • async makes a function return a promise, and await makes JavaScript wait for a promise to resolve and returns its result.

Example:

// Async function using await
async function fetchData() {
  const response = await fetch('https://jsonplaceholder.typicode.com/posts');
  const data = await response.json();
  console.log(data);
}

// Calling async function
fetchData();

Here, fetchData is an async function that fetches data from an API. The await ensures that JavaScript waits for the API response before proceeding.

Key Points:

  • Error handling: You can use try/catch blocks to handle errors in async functions.
  • await can only be used inside async functions.

Example with error handling:

async function fetchData() {
  try {
    const response = await fetch('https://invalid-url.com');
    const data = await response.json();
    console.log(data);
  } catch (error) {
    console.error('Error:', error);
  }
}

fetchData();

2. Callbacks in JavaScript

What is a Callback?

A callback is a function passed as an argument to another function. It is executed after the completion of that function’s operation.

Example of Callback:

function fetchData(callback) {
  setTimeout(() => {
    console.log('Data fetched');
    callback('Fetched data'); // Callback is executed after data is fetched
  }, 2000);
}

function processData(data) {
  console.log('Processing: ', data);
}

fetchData(processData); // Passing processData as a callback

Key Points:

  • Callback Hell: This happens when callbacks are nested within other callbacks, making the code hard to read and maintain. It can be avoided using promises or async/await.

Example of Callback Hell:

function fetchData(callback) {
  setTimeout(() => {
    console.log('Data fetched');
    callback(null, 'Fetched data');
  }, 2000);
}

function processData(callback) {
  setTimeout(() => {
    console.log('Data processed');
    callback(null, 'Processed data');
  }, 2000);
}

fetchData((err, data) => {
  if (err) {
    console.log('Error:', err);
    return;
  }
  processData((err, processedData) => {
    if (err) {
      console.log('Error:', err);
      return;
    }
    console.log(processedData);
  });
});

3. Promises in JavaScript

What is a Promise?

A Promise represents a value that may be available now, or in the future, or never. It is used for handling asynchronous operations.

  • Pending: The promise is still in progress.
  • Resolved (Fulfilled): The promise was successful, and the result is available.
  • Rejected: The promise failed.

Promise Syntax:

let promise = new Promise((resolve, reject) => {
  let success = true;
  if (success) {
    resolve('Data fetched successfully');
  } else {
    reject('Error in fetching data');
  }
});

promise
  .then(result => console.log(result))  // Success callback
  .catch(error => console.log(error));  // Error callback

Example of a Promise:

function fetchData() {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      const data = { user: 'John Doe' };
      resolve(data); // Resolve promise with data
    }, 2000);
  });
}

fetchData()
  .then(data => console.log(data))   // Success: Data
  .catch(err => console.log(err));   // Error: Handle any error

Key Points:

  • A Promise has three states: Pending, Resolved, and Rejected.
  • .then() is used for success, and .catch() is used for errors.

Comparison Between Callback, Promise, and Async/Await

Feature Callback Promise Async/Await
Syntax Simple but can become messy Cleaner than callbacks for async ops Clean, easy to read and write
Error Handling Requires manual checks .catch() handles errors try/catch handles errors easily
Readability Hard to manage with nested cb Easier than callbacks but less clear Most readable and concise
Error Propagation Manually passed through cb Automatically propagated Propagates like promises
Control Flow Callback hell is common Better control flow, but chaining is needed Linear flow, like synchronous code

4. Important Interview Questions

1. What is the difference between async/await and promises?

  • Answer: async/await provides a more synchronous style of handling asynchronous code, which makes it more readable and easier to maintain compared to using promises with .then() and .catch().

2. What is "callback hell" and how do you avoid it?

  • Answer: Callback hell occurs when you nest multiple callbacks, making the code hard to read and maintain. It can be avoided by using Promises or async/await for better flow and error handling.

3. Explain the three states of a Promise.

  • Answer: A promise can be in one of three states:
    • Pending: Initial state, neither fulfilled nor rejected.
    • Fulfilled: The operation was successful, and the promise is resolved.
    • Rejected: The operation failed, and the promise is rejected.

4. What is the difference between Promise.all() and Promise.race()?

  • Answer:
    • Promise.all() waits for all promises to be resolved (or one to be rejected).
    • Promise.race() returns as soon as one of the promises resolves or rejects.

5. How do you handle errors in promises?

  • Answer: Errors can be handled using .catch() method for promises or try/catch for async/await.

6. What is the advantage of async/await over promises?

  • Answer: async/await makes asynchronous code look synchronous, improving readability and making error handling more intuitive. It avoids the need for chaining multiple .then().

7. What happens if you don’t use await inside an async function?

  • Answer: If await is not used, the async function will return a promise immediately, which may cause unexpected behavior.

8. Can a promise be resolved or rejected more than once?

  • Answer: No, once a promise is resolved or rejected, its state cannot change.

Summary:

  • async / await offers a more readable and synchronous-like approach to handle promises.
  • Callbacks are the basic mechanism for asynchronous behavior but can result in "callback hell."
  • Promises provide a cleaner alternative, offering better error handling and chaining support.

JavaScript Scopes with var, let, and const

1. Global Scope

Variables defined outside of any function or block are in the global scope and accessible throughout the code.

Example:

var globalVar = "I am a global variable";

function displayGlobalVar() {
    console.log(globalVar); // Accessible here
}

displayGlobalVar(); // Output: I am a global variable
console.log(globalVar); // Output: I am a global variable

2. Function Scope

Variables declared with var, let, or const inside a function are function-scoped, meaning they are accessible only within that function.

Example:

function testFunctionScope() {
    var functionVar = "I am a function-scoped variable";
    console.log(functionVar); // Output: I am a function-scoped variable
}

testFunctionScope();
// console.log(functionVar); // Error: functionVar is not defined

3. Block Scope

Variables declared with let or const inside a block ({}) are block-scoped, meaning they are accessible only within that block. var does not have block scope.

Example with let and const:

{
    let blockLet = "I am block scoped (let)";
    const blockConst = "I am block scoped (const)";
    console.log(blockLet); // Output: I am block scoped (let)
    console.log(blockConst); // Output: I am block scoped (const)
}

// console.log(blockLet); // Error: blockLet is not defined
// console.log(blockConst); // Error: blockConst is not defined

Example with var (no block scope):

{
    var blockVar = "I am not block scoped (var)";
}
console.log(blockVar); // Output: I am not block scoped (var)

Differences Between var, let, and const

Feature var let const
Scope Function or Global Block Block
Re-declaration Allowed Not Allowed Not Allowed
Re-assignment Allowed Allowed Not Allowed
Hoisting Hoisted and initialized to undefined Hoisted but not initialized Hoisted but not initialized

4. Examples of var, let, and const

var Example (Function Scope and Hoisting)

console.log(varVariable); // Output: undefined (hoisted)
var varVariable = "I am a var variable";
console.log(varVariable); // Output: I am a var variable

let Example (Block Scope and No Re-declaration)

let letVariable = "I am a let variable";
{
    let letVariable = "I am block-scoped";
    console.log(letVariable); // Output: I am block-scoped
}
console.log(letVariable); // Output: I am a let variable

const Example (Block Scope and No Re-assignment)

const constVariable = "I am a constant";
// constVariable = "New value"; // Error: Assignment to constant variable
{
    const constVariable = "I am block-scoped constant";
    console.log(constVariable); // Output: I am block-scoped constant
}
console.log(constVariable); // Output: I am a constant

Practical Example Combining Scopes

var globalVar = "Global";

function testScopes() {
    var functionVar = "Function";
    if (true) {
        let blockLet = "Block Let";
        const blockConst = "Block Const";
        console.log(globalVar); // Output: Global
        console.log(functionVar); // Output: Function
        console.log(blockLet); // Output: Block Let
        console.log(blockConst); // Output: Block Const
    }
    // console.log(blockLet); // Error: blockLet is not defined
    // console.log(blockConst); // Error: blockConst is not defined
}

testScopes();

Summary

  • Global Scope: Accessible everywhere.
  • Function Scope: var, let, const declared inside a function are accessible only within that function.
  • Block Scope: let and const are confined to the block; var is not.
  • Use const for immutable values, let for block-scoped variables, and avoid var unless necessary for legacy support.

Hoisting in JavaScript

Hoisting is a JavaScript mechanism where variable and function declarations are moved to the top of their scope during the compile phase, before the code is executed. This allows you to use variables and functions before they are declared in the code.


1. Variable Hoisting

var and Hoisting

Variables declared with var are hoisted to the top of their scope and initialized with undefined. This means you can reference them before the declaration, but the value will be undefined.

Example:

console.log(hoistedVar); // Output: undefined (hoisted but uninitialized)
var hoistedVar = "I am hoisted!";
console.log(hoistedVar); // Output: I am hoisted!

let and const and Hoisting

Variables declared with let and const are hoisted but are not initialized. Accessing them before their declaration results in a ReferenceError.

Example:

console.log(hoistedLet); // Error: Cannot access 'hoistedLet' before initialization
let hoistedLet = "I am not accessible before declaration!";

console.log(hoistedConst); // Error: Cannot access 'hoistedConst' before initialization
const hoistedConst = "I must be declared before use!";

2. Function Hoisting

Function Declarations

Function declarations are hoisted along with their definitions. This means you can call the function before it is defined in the code.

Example:

sayHello(); // Output: Hello, I am hoisted!

function sayHello() {
    console.log("Hello, I am hoisted!");
}

Function Expressions

Function expressions (assigned to a variable) are hoisted only as variables. The function itself is not hoisted, and accessing it before the declaration will result in an error.

Example:

// console.log(sayHi); // Output: undefined (hoisted as a variable)
// sayHi(); // Error: sayHi is not a function
var sayHi = function () {
    console.log("Hi, I am not hoisted!");
};
sayHi(); // Output: Hi, I am not hoisted!

Arrow Functions

Arrow functions behave like function expressions and are not hoisted with their definitions.

Example:

// console.log(arrowFunc); // Output: undefined (hoisted as a variable)
// arrowFunc(); // Error: arrowFunc is not a function
var arrowFunc = () => {
    console.log("Arrow functions are not hoisted!");
};
arrowFunc(); // Output: Arrow functions are not hoisted!

3. Class Hoisting

Classes declared with the class keyword are not hoisted. Attempting to access them before their declaration results in a ReferenceError.

Example:

// console.log(MyClass); // Error: Cannot access 'MyClass' before initialization
class MyClass {
    constructor(name) {
        this.name = name;
    }
}
const obj = new MyClass("JavaScript");
console.log(obj.name); // Output: JavaScript

Hoisting Hierarchy

  1. Function declarations are fully hoisted (declaration and definition).
  2. var variables are hoisted but initialized to undefined.
  3. let and const variables are hoisted but remain in the "temporal dead zone" until they are declared.
  4. Function expressions and arrow functions are hoisted as variables, but their definitions are not.

Practical Example

// Hoisting demonstration
console.log(globalVar); // Output: undefined
var globalVar = "I am globally hoisted!";

// Function hoisting
hoistedFunction(); // Output: Functions are hoisted!
function hoistedFunction() {
    console.log("Functions are hoisted!");
}

// let and const hoisting
// console.log(letVar); // Error: Cannot access 'letVar' before initialization
let letVar = "I am let!";
console.log(letVar); // Output: I am let!

// Function expressions
// console.log(funcExpr); // Output: undefined
// funcExpr(); // Error: funcExpr is not a function
var funcExpr = function () {
    console.log("I am not hoisted!");
};
funcExpr(); // Output: I am not hoisted!

Summary of Hoisting

Feature var let / const Function Declarations Function Expressions/Arrow
Hoisted Yes Yes Yes Partially (as variables only)
Initialization undefined Not initialized Fully initialized Not initialized
Usage before declaration Allowed (undefined) ReferenceError Allowed ReferenceError

The this Keyword in JavaScript

The this keyword in JavaScript refers to the object that is currently executing the code. Its value depends on where and how it is used. It can refer to different objects based on the execution context.


1. this in the Global Context

In the global context (outside of any function), this refers to the global object:

  • In a browser, this is the window object.
  • In Node.js, this is the global object.

Example:

console.log(this); // In a browser: Window object, In Node.js: global object

2. this Inside a Function

Regular Functions

In a regular function, this refers to the global object (non-strict mode). In strict mode, it is undefined.

Example:

function regularFunction() {
    console.log(this); // In non-strict mode: Global object, In strict mode: undefined
}
regularFunction();

"use strict";
function strictFunction() {
    console.log(this); // undefined
}
strictFunction();

Arrow Functions

Arrow functions do not have their own this. Instead, they inherit this from the surrounding lexical scope.

Example:

function outerFunction() {
    this.name = "Outer Function";

    const arrowFunction = () => {
        console.log(this.name); // Inherits from outerFunction
    };

    arrowFunction();
}
outerFunction(); // Output: Outer Function

3. this in Methods

When a function is invoked as a method of an object, this refers to the object that owns the method.

Example:

const person = {
    name: "John",
    greet: function () {
        console.log(`Hello, my name is ${this.name}`);
    },
};

person.greet(); // Output: Hello, my name is John

4. this in Constructors and Classes

In a constructor function or class, this refers to the specific instance of the object being created.

Example:

function Person(name) {
    this.name = name;
    this.greet = function () {
        console.log(`Hello, my name is ${this.name}`);
    };
}

const john = new Person("John");
john.greet(); // Output: Hello, my name is John

With ES6 class:

class Animal {
    constructor(name) {
        this.name = name;
    }

    speak() {
        console.log(`${this.name} makes a sound`);
    }
}

const dog = new Animal("Dog");
dog.speak(); // Output: Dog makes a sound

5. this with call, apply, and bind

  • call and apply allow you to explicitly set the value of this.
  • bind returns a new function with this bound to a specific value.

Example:

const person = {
    name: "Alice",
};

function introduce(greeting) {
    console.log(`${greeting}, my name is ${this.name}`);
}

// Using call
introduce.call(person, "Hello"); // Output: Hello, my name is Alice

// Using apply
introduce.apply(person, ["Hi"]); // Output: Hi, my name is Alice

// Using bind
const boundIntroduce = introduce.bind(person);
boundIntroduce("Hey"); // Output: Hey, my name is Alice

6. this in Event Handlers

In an event handler, this refers to the element that received the event.

Example:

document.querySelector("button").addEventListener("click", function () {
    console.log(this); // The button element
});

With an arrow function:

document.querySelector("button").addEventListener("click", () => {
    console.log(this); // Inherits `this` from the outer scope, likely the Window object
});

7. this in SetTimeout/SetInterval

In setTimeout or setInterval, this depends on how the function is defined:

  • Regular functions: this refers to the global object.
  • Arrow functions: this is inherited from the surrounding scope.

Example:

function Timer() {
    this.seconds = 0;

    setInterval(function () {
        this.seconds++;
        console.log(this.seconds); // NaN (because `this` refers to the global object)
    }, 1000);

    setInterval(() => {
        this.seconds++;
        console.log(this.seconds); // Works correctly
    }, 1000);
}
new Timer();

8. Explicit Binding with new

When using the new keyword, this refers to the new object being created.

Example:

function Car(brand) {
    this.brand = brand;
}

const myCar = new Car("Toyota");
console.log(myCar.brand); // Output: Toyota

Summary of this

Context this Refers To
Global Context Global object (window in browsers)
Regular Function Global object (non-strict mode) or undefined (strict mode)
Arrow Function Inherits this from the enclosing scope
Method in Object The object owning the method
Constructor/class The instance being created
call, apply, bind Explicitly defined object
Event Handlers The DOM element that triggered the event

Understanding this is key to mastering JavaScript!