asyncis a keyword that is placed before a function declaration to indicate that the function will return a Promise.awaitcan only be used inside anasyncfunction. It pauses the execution of the function until the promise resolves and returns the resolved value.
asyncmakes a function return a promise, andawaitmakes JavaScript wait for a promise to resolve and returns its result.
// 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.
- Error handling: You can use
try/catchblocks to handle errors in async functions. awaitcan only be used insideasyncfunctions.
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();A callback is a function passed as an argument to another function. It is executed after the completion of that function’s operation.
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- 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);
});
});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.
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 callbackfunction 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- A Promise has three states: Pending, Resolved, and Rejected.
.then()is used for success, and.catch()is used for errors.
| 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 |
- Answer:
async/awaitprovides 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().
- 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.
- 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.
- 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.
- Answer: Errors can be handled using
.catch()method for promises ortry/catchfor async/await.
- Answer:
async/awaitmakes asynchronous code look synchronous, improving readability and making error handling more intuitive. It avoids the need for chaining multiple.then().
- Answer: If
awaitis not used, theasyncfunction will return a promise immediately, which may cause unexpected behavior.
- Answer: No, once a promise is resolved or rejected, its state cannot change.
async/awaitoffers 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.
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 variableVariables 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 definedVariables 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 definedExample with var (no block scope):
{
var blockVar = "I am not block scoped (var)";
}
console.log(blockVar); // Output: I am not block scoped (var)| 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 |
console.log(varVariable); // Output: undefined (hoisted)
var varVariable = "I am a var variable";
console.log(varVariable); // Output: I am a var variablelet 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 variableconst 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 constantvar 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();- Global Scope: Accessible everywhere.
- Function Scope:
var,let,constdeclared inside a function are accessible only within that function. - Block Scope:
letandconstare confined to the block;varis not. - Use
constfor immutable values,letfor block-scoped variables, and avoidvarunless necessary for legacy support.
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.
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!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!";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 (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 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!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- Function declarations are fully hoisted (declaration and definition).
varvariables are hoisted but initialized toundefined.letandconstvariables are hoisted but remain in the "temporal dead zone" until they are declared.- Function expressions and arrow functions are hoisted as variables, but their definitions are not.
// 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!| 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 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.
In the global context (outside of any function), this refers to the global object:
- In a browser, this is the
windowobject. - In Node.js, this is the
globalobject.
Example:
console.log(this); // In a browser: Window object, In Node.js: global objectIn 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 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 FunctionWhen 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 JohnIn 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 JohnWith 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 soundcallandapplyallow you to explicitly set the value ofthis.bindreturns a new function withthisbound 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 AliceIn 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
});In setTimeout or setInterval, this depends on how the function is defined:
- Regular functions:
thisrefers to the global object. - Arrow functions:
thisis 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();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| 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!