DEV Community

Cover image for Modern JavaScript Essentials: From Basics to Asynchronous Programming
Preyum Kumar
Preyum Kumar

Posted on • Edited on

Modern JavaScript Essentials: From Basics to Asynchronous Programming

In an AI-driven world, a strong foundation in core technologies is essential. To keep my skills sharp, I'm starting a regular blog series covering the tech stack I use. Welcome to the first post in my Next.js journey, where we'll start by mastering the JavaScript fundamentals.

JavaScript

Index

  1. Index
  2. Foundations
  3. Operators and Logic
  4. Strings
  5. Control Flow
  6. Arrays
  7. Functions and Scope
  8. Modern ES6+ Features
  9. Objects
  10. Classes and OOP
  11. Built-in Objects and Web APIs
  12. Document Object Model (DOM)
  13. Asynchronous JavaScript
  14. Modules (Import and Export)

Foundations

How to Install JavaScript

  • We don't install javascript. If we have any internet browser, there is support for JavaScript there.
  • Chrome/Safari/Firefox all have javascript support.
  • Along with browsers we can also use platforms like codepen.io or platforms like scrimba.com

Putting Comments in the Code

  • To put comment in the code we use //
let val = "apple" // It is a comment
Enter fullscreen mode Exit fullscreen mode
  • For multiline comment we can use /* */
let fruit = "apple"
/* This is a 
    Multiline
    Comment
*/
let number = 9
Enter fullscreen mode Exit fullscreen mode

The Variables

  • JavaScript has three types of variables var, let and const.
  • var can be used to define a variable and the values can be redefined. It was used mainly in the past as it was the only option but now a days it is not intended to be used normally. It is because it has issues like ignoring block scope, and getting attached to the window scope in global scope. var has function scope meaning if defined inside a function it will be remembered only inside that function. var variables can also be redeclared which can cause silent bugs.
var fruitName = "Apple";
var fruitName = "Mango"; // Redeclaration is allowed (can cause bugs)
fruitName = "Banana"; // Reassignment is allowed
console.log(fruitName); // "Banana"

if (true) {
    var blockVar = "I ignore block scope!";
}
console.log(blockVar); // "I ignore block scope!" (Accessible outside the block)
Enter fullscreen mode Exit fullscreen mode
  • Implicit global variable creation - Recipe for bugs: If you assign a value to a variable without declaring it with var, let, or const, JavaScript automatically makes it a global variable (attached to the window object). This can lead to unexpected overwrites and hard-to-trace bugs.
function createBug() {
    // Implicit global variable creation
    rogueFruit = "Mango"; 
}
createBug();
console.log(rogueFruit); // Outputs "Mango" even outside the function!
Enter fullscreen mode Exit fullscreen mode
  • let is similar as var but with block scoping. Also in case of hoisting var is set as undefined while let and const can go to TDZ (Temporal Dead Zone) meaning they will have no value and get Reference Error if accessed. let does not allow redeclaration of the same variable and so does const.
let myName = "Preyum Kumar";
myName = "Preyum"; // Allowed: Reassignment
// let myName = "Kumar"; // Error: Cannot redeclare block-scoped variable
Enter fullscreen mode Exit fullscreen mode
  • const is used when we want to define a variable only once and freeze its reference. Strings and numbers defined as const are locked in while arrays can be inserted with more values as only their reference is constant. const needs to be defined at the time of declaration unlike the other two variables.
const favoriteFruit = "Apple";
// favoriteFruit = "Banana"; // TypeError: Assignment to constant variable.

const fruitBasket = ["Apple"];
fruitBasket.push("Orange"); // Allowed: We are modifying the array, not the reference.
console.log(fruitBasket); // ["Apple", "Orange"]
Enter fullscreen mode Exit fullscreen mode

The DataTypes

  • undefined: Represents a variable that has been declared but not yet assigned a value.
  • number: Represents both integer and floating-point numbers.
  • boolean: Represents a logical entity and can have two values: true or false.
  • string: Represents a sequence of characters used to represent text.
  • null: Represents the intentional absence of any object value.
  • object: Represents a collection of properties (key-value pairs) or complex data structures (like arrays, functions).
  • symbol: Represents a unique and immutable primitive value, often used as object property keys.
  • bigint: Represents whole numbers larger than the maximum safe integer (Number.MAX_SAFE_INTEGER, which is 9007199254740991 or 2^53 - 1).
let notAssigned;
console.log(typeof notAssigned); // "undefined"

let age = 25;
console.log(typeof age); // "number"

let isComputerScienceExpert = true;
console.log(typeof isComputerScienceExpert); // "boolean"

let name = "Preyum Kumar";
console.log(typeof name); // "string"

let emptyValue = null;
console.log(typeof emptyValue); // "object" (This is a known quirk in JavaScript!)

let person = { name: "Preyum", domain: "Computer Science" };
console.log(typeof person); // "object"

let uniqueId = Symbol("id");
console.log(typeof uniqueId); // "symbol"

// The 'n' at the end tells JavaScript to treat this as a BigInt
let largeNumber = 9007199254740992n; // 1 larger than MAX_SAFE_INTEGER
console.log(typeof largeNumber); // "bigint"
Enter fullscreen mode Exit fullscreen mode

Auto-Boxing

  • Auto-boxing is a process where JavaScript automatically wraps primitive data types (like strings, numbers, and booleans) in their corresponding object wrappers (String, Number, Boolean) behind the scenes when you try to access properties or methods on them.
  • Primitives themselves don't have properties or methods. However, thanks to auto-boxing, you can use methods as if they were objects. JavaScript creates a temporary object, calls the method, and then immediately discards the temporary object.
let myString = "hello world";

// "myString" is a primitive string, not an object. 
// But when we access .length, JavaScript temporarily wraps it in a String object.
console.log(myString.length); // 11

// Same happens when we call methods like .toUpperCase()
console.log(myString.toUpperCase()); // "HELLO WORLD"

let myNum = 123.456;
// Number primitive temporarily boxed to use .toFixed()
console.log(myNum.toFixed(1)); // "123.5"
Enter fullscreen mode Exit fullscreen mode

Operators and Logic

Operators, Shorthand and Semicolon

  • The lines are recognizable with or without semicolon. Semicolons are optional but recommended for clarity.
let fruit1 = "Apple"
let fruit2 = "Orange"; // Both lines are valid
Enter fullscreen mode Exit fullscreen mode
  • Operators +, -, /, %, <, >, <=, >=, *, &&, || etc and their shorthand use:
let total = 10 + 5; // 15
let diff = 10 - 5; // 5
let mult = 10 * 5; // 50
let div = 10 / 5; // 2
let remainder = 10 % 3; // 1

// Comparison
console.log(10 > 5); // true
console.log(10 <= 10); // true

// Logical
console.log(true && false); // false
console.log(true || false); // true

// Shorthand assignments
let count = 5;
count += 5; // Equivalent to: count = count + 5 (Result: 10)
count *= 2; // Equivalent to: count = count * 2 (Result: 20)
Enter fullscreen mode Exit fullscreen mode
  • Operators like == and === also != and !==:
    • == checks for value equality (with type coercion).
    • === checks for strict equality (both value and type must match).
console.log(5 == "5"); // true (Type coercion happens)
console.log(5 === "5"); // false (Different types: Number vs String)
console.log(5 != "5"); // false
console.log(5 !== "5"); // true
Enter fullscreen mode Exit fullscreen mode
  • Object.is(): A static method that determines whether two values are the same value. It is similar to === but has two important differences: it considers NaN to be equal to NaN, and it considers +0 and -0 to be different.
console.log(Object.is(5, 5)); // true
console.log(Object.is("5", 5)); // false

// Differences from ===
console.log(NaN === NaN); // false
console.log(Object.is(NaN, NaN)); // true

console.log(+0 === -0); // true
console.log(Object.is(+0, -0)); // false
Enter fullscreen mode Exit fullscreen mode
  • Also incrementing and decrementing shorthand like ++, --:
let step = 1;
step++; // Increments step by 1 (Result: 2)
step--; // Decrements step by 1 (Result: 1)
Enter fullscreen mode Exit fullscreen mode

Operator Precedence

  • Operator precedence determines the order in which operators are evaluated. Operators with higher precedence are evaluated first.
  • Multiplication (*) and division (/) have higher precedence than addition (+) and subtraction (-).
  • You can use parentheses () to override the default precedence and force a specific order of evaluation.
let result1 = 10 + 5 * 2; // 20 (Multiplication first)
let result2 = (10 + 5) * 2; // 30 (Parentheses evaluated first)

console.log(result1);
console.log(result2);
Enter fullscreen mode Exit fullscreen mode

Type Coercion and Conversion

  • Type Coercion is the implicit (automatic) conversion of values from one data type to another by JavaScript. Because JavaScript is a weakly-typed language, it will often try to "help" you by converting types on the fly to complete an operation. This can lead to unexpected bugs.
// --- Implicit Type Coercion ---
// 1. String Coercion: Happens when using the + operator with a string
let strCoercion = "5" + 2; // "52" (The number 2 is coerced into a string)
let boolCoercion = "Hello " + true; // "Hello true"

// 2. Number Coercion: Happens with math operators like -, *, / (but NOT +)
let numCoercion1 = "5" - 2; // 3 (The string "5" is coerced into a number)
let numCoercion2 = "10" * "2"; // 20 

// 3. Boolean Coercion: Happens in if-statements or logical operators
if ("hello") {
    console.log("This runs because 'hello' is truthy!");
}
Enter fullscreen mode Exit fullscreen mode

Type Conversion (Explicit Casting)

  • To avoid bugs caused by implicit coercion, you should use Type Conversion (explicitly forcing a value to be a specific type).
  • You can do this using the built-in functions: Number(), String(), and Boolean().
// --- Explicit Type Conversion ---
// 1. Convert to Number (Prevents the "52" concatenation bug)
let userInput = "5";
let total = Number(userInput) + 2; 
console.log(total); // 7 (Correct math, not "52")

// 2. Convert to String
let myNum = 100;
let explicitString = String(myNum); // "100"
// Alternatively, using the auto-boxed .toString() method:
// let explicitString = myNum.toString();

// 3. Convert to Boolean
// Often used to quickly force a truthy/falsy value into an actual true/false boolean
let hasData = Boolean("Some User Data"); // true
let hasEmptyData = Boolean(""); // false

// A common shorthand for converting to boolean is using double negation (!!)
let quickBool = !!"Some Data"; // true
Enter fullscreen mode Exit fullscreen mode

Truthy and Falsy Values

  • In JavaScript, a truthy value is a value that is considered true when encountered in a Boolean context (like an if statement). A falsy value is a value that is considered false.
  • Everything in JavaScript is truthy except for a specific list of falsy values.
  • The Falsy values are exactly these:
    • false
    • 0 (zero)
    • -0 (minus zero)
    • 0n (BigInt zero)
    • "", '', (empty strings) * null * undefined * NaN (Not-a-Number) * Truthy values are everything else, including empty arrays ([]), empty objects ({}), and strings with just a space (" ").
if ("") {
    console.log("This won't run because an empty string is falsy.");
}

if (" ") {
    console.log("This WILL run because a string with a space is truthy.");
}

if ([]) {
    console.log("This WILL run because an empty array is truthy.");
}
Enter fullscreen mode Exit fullscreen mode

Ternary Operator and Short-Circuit Evaluation

  • Ternary Operator: A concise way to write an if-else statement. It takes three operands: a condition followed by a question mark (?), an expression to execute if truthy, and a colon (:), followed by an expression to execute if falsy. Heavily used in React for conditional rendering.
let age = 20;
// Condition ? exprIfTrue : exprIfFalse
let canVote = age >= 18 ? "Yes" : "No";
console.log(canVote); // "Yes"
Enter fullscreen mode Exit fullscreen mode
  • Short-Circuit Evaluation (&& and ||):
    • && (Logical AND): Returns the first falsy value, or the last truthy value if all are truthy. Very commonly used in React to render components conditionally (e.g., isLoggedIn && <Dashboard />).
    • || (Logical OR): Returns the first truthy value, or the last falsy value if all are falsy. Useful for assigning default values.
let isLoggedIn = true;
// The second part only executes if the first part is true
isLoggedIn && console.log("User dashboard rendered");

let userTheme = null;
let defaultTheme = "dark";
// If userTheme is falsy (like null), it falls back to "dark"
let activeTheme = userTheme || defaultTheme;
console.log(activeTheme); // "dark"
Enter fullscreen mode Exit fullscreen mode

Optional Chaining and Nullish Coalescing

  • Optional Chaining (?.): Enables reading the value of a property located deep within a chain of connected objects without having to check that each reference in the chain is valid. It returns undefined if the reference is nullish (null or undefined) instead of throwing an error. Very helpful when dealing with API data in Next.js.
let userProfile = {
    name: "Preyum Kumar",
    // address is missing here
};

// Trying to access userProfile.address.city directly would throw an error.
// Using ?. safely handles it:
let city = userProfile?.address?.city;
console.log(city); // undefined
Enter fullscreen mode Exit fullscreen mode
  • Nullish Coalescing Operator (??): A logical operator that returns its right-hand side operand when its left-hand side operand is null or undefined, and otherwise returns its left-hand side operand.
    • Why use ?? instead of ||? The Logical OR (||) operator falls back to the right side if the left side is any falsy value (like 0, "", false, null, undefined). This causes bugs when 0 or "" are actually valid, intended values (e.g., a video with 0 views). The ?? operator fixes this by only falling back if the value is explicitly missing (null or undefined).
// Scenario: A video has exactly 0 views.
let videoViews = 0; 
let defaultViews = 10;

// Bug with OR operator (||): 
// JavaScript sees 0 as "falsy" and thinks the value is missing, so it applies the default.
console.log(videoViews || defaultViews); // Output: 10 (Incorrect: it overwrote our valid 0 views!)

// Fix with Nullish Coalescing (??):
// JavaScript checks if videoViews is exactly `null` or `undefined`. Since it's a valid 0, it keeps it.
console.log(videoViews ?? defaultViews); // Output: 0 (Correct!)

// If the value was actually missing:
let missingViews = null;
console.log(missingViews ?? defaultViews); // Output: 10 (Correctly applies default)
Enter fullscreen mode Exit fullscreen mode

Strings

The Escape Character and Strings

  • Example of string with "" and inside also double quotes:
let quote = "Preyum Kumar said, \"Computer Vision is fascinating!\"";
Enter fullscreen mode Exit fullscreen mode
  • Other ways like using single quote to cover double quotes:
let singleQuoteStr = 'Preyum Kumar said, "NLP is amazing!"';
Enter fullscreen mode Exit fullscreen mode
  • Other way to use back ticks `` to put both single and double quotes inside:
let backtickStr = `Preyum's favorite subject is "Computer Science".`;
Enter fullscreen mode Exit fullscreen mode
  • To put backslash itself we use two backslashes:
let path = "C:\\Users\\PreyumKumar\\Documents";
Enter fullscreen mode Exit fullscreen mode

Backtick for Strings with values

  • Backticks () are used to create Template Literals, allowing for string interpolation (embedding variables/expressions inside strings).
  • Example with backtick strings with values:
let author = "Preyum Kumar";
let topic = "NLP";
let message = `${author} is an expert in ${topic}.`;
console.log(message);
Enter fullscreen mode Exit fullscreen mode
  • Also if we want $ sign also to be printed use escape character for the same:
let price = 500;
let costMessage = `The total cost is \$${price}.`;
console.log(costMessage); // "The total cost is $500."
Enter fullscreen mode Exit fullscreen mode

Control Flow

For, While, Do While and Switch

  • For Loop: Repeats a block of code a specific number of times.
let fruits = ["Apple", "Mango", "Banana"];
for (let i = 0; i < fruits.length; i++) {
    console.log(fruits[i]);
}
Enter fullscreen mode Exit fullscreen mode
  • While Loop: Executes a block of code as long as a specified condition is true.
let count = 0;
while (count < 3) {
    console.log("Count is: " + count);
    count++;
}
Enter fullscreen mode Exit fullscreen mode
  • Do...While Loop: Similar to a while loop, but it executes the block of code at least once before checking the condition.
let runs = 0;
do {
    console.log("This runs at least once!");
    runs++;
} while (runs < 0);
Enter fullscreen mode Exit fullscreen mode
  • Switch Statement: Evaluates an expression, matching the expression's value to a case clause, and executes statements associated with that case.
let chosenFruit = "Apple";

switch (chosenFruit) {
    case "Banana":
        console.log("You chose Banana.");
        break;
    case "Apple":
        console.log("You chose Apple.");
        break;
    default:
        console.log("Unknown fruit.");
}
Enter fullscreen mode Exit fullscreen mode

Break and Continue

  • Control flow statements like loops can be altered using break and continue.
  • break: Immediately exits the entire loop (or switch statement). Execution continues with the code that follows the loop.
  • continue: Skips the current iteration of the loop and immediately jumps to the next iteration.
// Using 'break' to stop the loop early
for (let i = 1; i <= 5; i++) {
    if (i === 3) {
        console.log("Found 3! Breaking out of the loop.");
        break; // Loop stops completely here
    }
    console.log("Iteration: " + i);
}
/* Output:
Iteration: 1
Iteration: 2
Found 3! Breaking out of the loop.
*/

// Using 'continue' to skip a specific iteration
for (let j = 1; j <= 5; j++) {
    if (j === 3) {
        console.log("Skipping 3!");
        continue; // Skips to j = 4
    }
    console.log("Iteration: " + j);
}
/* Output:
Iteration: 1
Iteration: 2
Skipping 3!
Iteration: 4
Iteration: 5
*/
Enter fullscreen mode Exit fullscreen mode

Error Handling (try...catch and throw)

  • try...catch: Allows you to test a block of code for errors (try) and handle the error if one occurs (catch). This prevents the entire script from crashing.
  • throw: Used to create custom errors. You can "throw" an exception (an object, string, or number).
  • finally: A block that runs regardless of whether an error was thrown or caught.
function checkAge(age) {
    if (age < 18) {
        // Throwing a custom error
        throw new Error("You must be at least 18 years old.");
    }
    return "Access granted.";
}

try {
    console.log(checkAge(15));
} catch (error) {
    // Handling the error
    console.error("Caught an error:", error.message);
} finally {
    console.log("Age check process completed.");
}
Enter fullscreen mode Exit fullscreen mode
  • Synchronous Limitation of throw: A standard throw statement is synchronous. It only works within the current execution stack. If you try to use throw inside an asynchronous callback (like setTimeout), it cannot be caught by a try...catch block that surrounds the async call. This is why reject is used for future (asynchronous) errors.
// --- 1. The PROBLEM: throw fails in Async ---
try {
    setTimeout(() => {
        // throw new Error("Async Error!"); 
        // ^ This would crash the program because the 'try...catch' 
        // block below has already finished executing!
    }, 1000);
} catch (e) {
    console.log("This will NEVER run:", e); 
}

// --- 2. The SOLUTION: reject() in a Promise ---
const asyncTask = () => {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            reject("Async Error caught via Promise!"); 
        }, 1000);
    });
};

asyncTask()
    .then(msg => console.log(msg))
    .catch(err => console.log("SUCCESSFULLY caught:", err));
Enter fullscreen mode Exit fullscreen mode

Arrays

Array Methods

  • push() and pop(): push() adds one or more elements to the end of an array and returns the new length. pop() removes the last element from an array and returns that element.
let fruits = ["Apple", "Mango"];
fruits.push("Banana"); // ["Apple", "Mango", "Banana"]
let last = fruits.pop(); // Removes "Banana". Array is back to ["Apple", "Mango"]
Enter fullscreen mode Exit fullscreen mode
  • splice(): Changes the contents of an array by removing or replacing existing elements and/or adding new elements in place. It mutates the original array.
    • Syntax: array.splice(startIndex, deleteCount, item1, item2, ...)
let colors = ["Red", "Green", "Blue", "Yellow"];
// Start at index 1, delete 2 elements ("Green", "Blue"), and insert "Purple"
colors.splice(1, 2, "Purple"); 
console.log(colors); // ["Red", "Purple", "Yellow"]
Enter fullscreen mode Exit fullscreen mode
  • forEach(): Executes a provided function once for each array element. It is used purely for executing side-effects (like logging or modifying external variables) and always returns undefined, unlike map() which returns a new array.
let techStack = ["JavaScript", "React", "Next.js"];
techStack.forEach((tech, index) => {
    console.log(index + ": " + tech);
});
// Output:
// 0: JavaScript
// 1: React
// 2: Next.js
Enter fullscreen mode Exit fullscreen mode
  • map(): Creates a new array populated with the results of calling a provided function on every element in the calling array. This is the primary way to render lists of elements in React.
let numbers = [1, 2, 3];
let doubled = numbers.map(num => num * 2);
console.log(doubled); // [2, 4, 6]
Enter fullscreen mode Exit fullscreen mode
  • filter(): Creates a shallow copy of a portion of a given array, filtered down to just the elements from the given array that pass the test implemented by the provided function. Constantly used for manipulating state arrays, like deleting an item from a list.
let ages = [15, 21, 18, 12, 30];
let adults = ages.filter(age => age >= 18);
console.log(adults); // [21, 18, 30]
Enter fullscreen mode Exit fullscreen mode
  • reduce(): Executes a user-supplied "reducer" callback function on each element of the array, in order, passing in the return value from the calculation on the preceding element. The final result of running the reducer across all elements of the array is a single value.
let prices = [10, 20, 30];
let totalCost = prices.reduce((acc, current) => acc + current, 0);
console.log(totalCost); // 60
Enter fullscreen mode Exit fullscreen mode

Typed Arrays

  • Typed Arrays are array-like objects that provide a mechanism for reading and writing raw binary data in memory buffers. While standard arrays can grow dynamically and hold any data type, Typed Arrays have a fixed length and only store a specific numeric type (like integers or floats).
  • They are heavily used in performance-critical applications, WebGL, and when handling binary data from APIs or files.
  • Common types: Int8Array, Uint8Array, Int32Array, Float64Array, etc.
// Create a buffer of 16 bytes
let buffer = new ArrayBuffer(16);

// Create a view that treats the buffer as an array of 32-bit signed integers
let int32View = new Int32Array(buffer);

int32View[0] = 42;
int32View[1] = 100;

console.log(int32View.length); // 4 (Since 16 bytes / 4 bytes per Int32 = 4)
console.log(int32View[0]); // 42

// You can also initialize directly with an array
let uint8 = new Uint8Array([10, 20, 30]);
console.log(uint8[1]); // 20
Enter fullscreen mode Exit fullscreen mode

Functions and Scope

Regular, Anonymous and Arrow Functions

  • Regular Functions: Declared using the function keyword. They have their own this context.
function greet(name) {
    return "Hello, " + name;
}
console.log(greet("Preyum Kumar"));
Enter fullscreen mode Exit fullscreen mode
  • Anonymous Functions: Functions without a name, often assigned to variables or passed as callbacks (discussed in detail later).
let sayGoodbye = function(name) {
    return "Goodbye, " + name;
};
Enter fullscreen mode Exit fullscreen mode
  • Arrow Functions: A concise syntax for writing functions. They do not have their own this binding (they inherit it from the parent scope).
    • Explicit Return: When using curly braces {}, you must explicitly use the return keyword to return a value.
    • Implicit Return: If you omit the curly braces, the arrow function implicitly returns the single evaluated expression. You can wrap the expression in parentheses () for multi-line implicit returns, which is very common in React JSX.
// Explicit Return (uses {})
const greetArrow = (name) => {
    return "Hello, " + name;
};

// Implicit Return (no {}, evaluates the expression on the same line)
const greetShort = name => "Hello, " + name;

// Implicit Return with Parentheses (useful for returning objects)
const getUser = (name) => ({
    name: name,
    role: "Admin"
});
Enter fullscreen mode Exit fullscreen mode

Closures

  • A Closure is a feature in JavaScript where an inner function has access to the outer (enclosing) function's variablesโ€”a scope chain. Closures are a fundamental concept that explains how React Hooks (like useState and useEffect) "remember" values between renders.
function makeCounter() {
    let count = 0; // count is a local variable created by makeCounter

    return function() { // The inner function is a closure
        count++; // It has access to count from the outer function
        return count;
    };
}

let myCounter = makeCounter();
console.log(myCounter()); // 1
console.log(myCounter()); // 2
console.log(myCounter()); // 3
Enter fullscreen mode Exit fullscreen mode

The this Keyword

  • The this keyword refers to the context in which a function is executed. Its value depends on how the function is called, not where it's defined (except for arrow functions).

1. Global Context

  • In the global scope (outside any function), this refers to the global object (window in browsers).
console.log(this === window); // true
Enter fullscreen mode Exit fullscreen mode

2. Inside a Regular Function

  • Non-strict mode: this defaults to the global object (window).
  • Strict mode ('use strict'): this is undefined. This is a safety feature to prevent accidental modification of the global object.
function showThis() {
    "use strict";
    console.log(this); 
}
showThis(); // undefined
Enter fullscreen mode Exit fullscreen mode

3. Inside a Method (Object Context)

  • When a function is called as a method of an object, this refers to the object itself.
const user = {
    name: "Preyum",
    greet() {
        console.log(`Hello, my name is ${this.name}`);
    }
};
user.greet(); // "Hello, my name is Preyum"
Enter fullscreen mode Exit fullscreen mode

4. Arrow Functions (Lexical this)

  • Arrow functions do not have their own this. Instead, they inherit this from the surrounding (lexical) scope at the time they are created. This makes them ideal for callbacks (like in setTimeout or array methods) where you want to keep the original context.
const group = {
    title: "Our Team",
    members: ["Preyum", "John"],
    showList() {
        // Regular function inside forEach would have 'this' as undefined (in strict)
        this.members.forEach((member) => {
            // Arrow function inherits 'this' from showList()
            console.log(`${this.title}: ${member}`);
        });
    }
};
group.showList(); 
// "Our Team: Preyum"
// "Our Team: John"
Enter fullscreen mode Exit fullscreen mode

5. Changing this Context (call, apply, bind)

  • JavaScript provides three methods to explicitly set the value of this.

  • .call(thisArg, arg1, arg2, ...): Invokes the function immediately with the specified this and arguments passed individually.

  • .apply(thisArg, [argsArray]): Invokes the function immediately with the specified this and arguments passed as an array.

  • .bind(thisArg, arg1, ...): Returns a new function with this permanently bound to the specified object. It does not invoke the function immediately.

const person1 = { name: "Preyum" };
const person2 = { name: "Alice" };

function introduce(skill, hobby) {
    console.log(`I'm ${this.name}. I'm good at ${skill} and I like ${hobby}.`);
}

// .call() - Arguments passed individually
introduce.call(person1, "NLP", "Chess");

// .apply() - Arguments passed as an array
introduce.apply(person2, ["Computer Vision", "Gaming"]);

// .bind() - Creates a new function
const boundIntro = introduce.bind(person1, "Next.js", "Blogging");
boundIntro(); // Can be called later
Enter fullscreen mode Exit fullscreen mode

Function Arguments

  • Default Arguments use case: We can assign default values to parameters to ensure the function works even if some arguments are missing.
function makeJuice(fruit1, fruit2 = "Water") {
    return `Making juice with ${fruit1} and ${fruit2}`;
}
console.log(makeJuice("Apple")); // "Making juice with Apple and Water"
Enter fullscreen mode Exit fullscreen mode
  • Order in which default arguments must be: Default arguments must be placed at the end of the parameter list. If they are placed first, JavaScript won't know which arguments to skip when fewer arguments are passed.
// WRONG: function wrongOrder(fruit1 = "Apple", fruit2) { ... }
// CORRECT:
function correctOrder(fruit1, fruit2 = "Banana") {
    console.log(fruit1, fruit2);
}
Enter fullscreen mode Exit fullscreen mode

Modern ES6+ Features

Spread Operator

  • The spread operator (...) allows an iterable (like an array or string) to be expanded in places where zero or more arguments or elements are expected, or an object expression to be expanded.
  • How spread operator is used to actually copy array:
let originalFruits = ["Apple", "Mango"];
// let badCopy = originalFruits; // This just copies the reference!

let goodCopy = [...originalFruits]; // Creates a true, independent copy
goodCopy.push("Banana");

console.log(originalFruits); // ["Apple", "Mango"]
console.log(goodCopy); // ["Apple", "Mango", "Banana"]
Enter fullscreen mode Exit fullscreen mode

Rest Parameters

  • The Rest Parameter syntax (...args) allows a function to accept an indefinite number of arguments as an array. While the spread operator expands iterables into individual elements, the rest parameter collects multiple elements and condenses them into a single array.
function calculateSum(...numbers) {
    // numbers is an array containing all passed arguments
    return numbers.reduce((total, num) => total + num, 0);
}

console.log(calculateSum(1, 2, 3)); // 6
console.log(calculateSum(10, 20, 30, 40, 50)); // 150
Enter fullscreen mode Exit fullscreen mode

Destructuring object values for assignment

  • Destructuring makes it possible to unpack values from arrays, or properties from objects, into distinct variables.
  • Destructing single layer example:
let user = {
    name: "Preyum Kumar",
    domain: "Computer Science"
};

let { name, domain } = user;
console.log(name); // "Preyum Kumar"
Enter fullscreen mode Exit fullscreen mode
  • Destructuring nested object for assignment:
let expertUser = {
    profile: {
        firstName: "Preyum",
        expertise: "Computer Vision"
    }
};

let { profile: { firstName, expertise } } = expertUser;
console.log(expertise); // "Computer Vision"
Enter fullscreen mode Exit fullscreen mode

Objects

Objects: Dot and Bracket Notation

  • Objects store data in key-value pairs. You can access these values using either dot notation or bracket notation.
  • Dot Notation (object.property): The most common and readable way to access properties. The property name must be a valid JavaScript identifier (no spaces, doesn't start with a number).
  • Bracket Notation (object["property"]): Required when the property name contains spaces, special characters, or when you are accessing a property dynamically using a variable.
let car = {
    make: "Toyota",
    "model year": 2022,
    color: "blue"
};

// Dot notation
console.log(car.make); // "Toyota"

// Bracket notation required for strings with spaces
console.log(car["model year"]); // 2022

// Bracket notation for dynamic property access
let propName = "color";
console.log(car[propName]); // "blue"
Enter fullscreen mode Exit fullscreen mode

Object Shorthand Syntax

  • ES6 introduced concise ways to define properties and methods inside objects, reducing boilerplate code.
  • Shorthand Property Names: If the key and the variable name you want to assign to it are the exact same, you can just write the variable name once instead of key: value.
  • Shorthand Method Names: You can omit the function keyword and the colon : when defining methods inside an object.
let name = "Preyum";
let role = "Admin";

// Traditional ES5 way
let oldUser = {
    name: name,
    role: role,
    greet: function() {
        return "Hello " + this.name;
    }
};

// ES6 Shorthand Syntax (Cleaner and more common in React/Next.js)
let modernUser = {
    name, // Shorthand property
    role, // Shorthand property
    greet() { // Shorthand method
        return "Hello " + this.name;
    }
};

console.log(modernUser.greet()); // "Hello Preyum"
Enter fullscreen mode Exit fullscreen mode

Object Methods

  • JavaScript provides several built-in methods to work with objects, returning arrays of their keys, values, or entries (key-value pairs).
  • Object.keys(): Returns an array of a given object's own enumerable string-keyed property names.
  • Object.values(): Returns an array of a given object's own enumerable string-keyed property values.
  • Object.entries(): Returns an array of a given object's own enumerable string-keyed property [key, value] pairs.
let user = {
    name: "Preyum Kumar",
    domain: "Computer Science",
    expertise: "Computer Vision"
};

console.log(Object.keys(user)); 
// ["name", "domain", "expertise"]

console.log(Object.values(user)); 
// ["Preyum Kumar", "Computer Science", "Computer Vision"]

console.log(Object.entries(user)); 
// [["name", "Preyum Kumar"], ["domain", "Computer Science"], ["expertise", "Computer Vision"]]
Enter fullscreen mode Exit fullscreen mode

Shallow and Deep Copy

  • In JavaScript, primitive types (like numbers and strings) are passed by value, but objects and arrays are passed by reference. This means if you assign an object to a new variable and modify it, you are modifying the original object. To prevent this, we need to create copies.

Shallow Copy

  • A shallow copy creates a new object and copies over the top-level properties. However, if the object contains nested objects or arrays, the nested structures are still passed by reference.
  • Ways to create a shallow copy:
    • Spread Operator (...) for both objects and arrays.
    • Object.assign() for objects.
    • .slice() specifically for arrays. It returns a shallow copy of a portion of an array into a new array object. If used without arguments, it copies the entire array.
const originalArray = ["Apple", "Mango", "Banana"];

// Direct assignment just copies the reference (BAD!)
// const badCopy = originalArray; 

// Using .slice() to create a true shallow copy (GOOD!)
const copiedArray = originalArray.slice();

copiedArray.push("Orange"); // Modifies only the copy

console.log(originalArray); // ["Apple", "Mango", "Banana"]
console.log(copiedArray); // ["Apple", "Mango", "Banana", "Orange"]
Enter fullscreen mode Exit fullscreen mode
let originalObj = { 
    name: "Preyum", 
    details: { age: 25 } 
};

// Shallow copy using Spread Operator
let shallowCopy = { ...originalObj };

shallowCopy.name = "Preyum Kumar"; // Only changes the copy (top-level)
shallowCopy.details.age = 30;      // Changes BOTH original and copy! (nested)

console.log(originalObj.name); // "Preyum"
console.log(originalObj.details.age); // 30 (Oops, original was mutated)
Enter fullscreen mode Exit fullscreen mode

Deep Copy

  • A deep copy creates a completely independent clone of the original object, including all nested objects and arrays. Modifying the deep copy will never affect the original.
  • Ways to create a deep copy:
    • JSON.parse(JSON.stringify(object)): The traditional, hacky way. It works for most simple objects but fails if the object contains functions, undefined, Date objects, or circular references.
    • structuredClone(object): The modern, native JavaScript way (introduced recently in browsers/Node.js). It perfectly clones complex objects, including Dates, Sets, Maps, and more.
let user = { 
    name: "Preyum", 
    details: { age: 25 },
    joined: new Date()
};

// Deep copy using structuredClone (Modern native method)
let deepCopy = structuredClone(user);

deepCopy.details.age = 30; // Modifies ONLY the copy

console.log(user.details.age); // 25 (Original remains completely untouched)
Enter fullscreen mode Exit fullscreen mode

Getters and Setters

  • Getters (get) bind an object property to a function that will be called when that property is looked up.
  • Setters (set) bind an object property to a function to be called when there is an attempt to set that property.
let person = {
    firstName: "Preyum",
    lastName: "Kumar",

    get fullName() {
        return `${this.firstName} ${this.lastName}`;
    },

    set updateFirstName(newName) {
        if(newName.length > 0) {
            this.firstName = newName;
        } else {
            console.log("Name cannot be empty");
        }
    }
};

console.log(person.fullName); // "Preyum Kumar"
person.updateFirstName = "PreyumK";
console.log(person.fullName); // "PreyumK Kumar"
Enter fullscreen mode Exit fullscreen mode

Classes and OOP

Classes and Private Fields (#)

  • Classes are a blueprint for creating objects. They encapsulate data with code that works on that data.
  • Private Fields (#): By default, class properties and methods are public. To make them private, prefix their name with a hash #. Private fields cannot be accessed or called from outside the class or from its subclasses.
  • Benefits of Private Fields: Encapsulation, preventing unintended access to internal state, and reducing potential for bugs.
class User {
    // 1. Private fields declaration (must be declared at the top)
    #id;
    #password;

    constructor(name, id, password) {
        this.name = name;      // Public property
        this.#id = id;        // Private property
        this.#password = password;
    }

    // 2. Public method
    displayInfo() {
        console.log(`User: ${this.name}, ID: ${this.#getId()}`);
    }

    // 3. Private method
    #getId() {
        return this.#id;
    }

    // 4. Getter for private field (can add validation)
    get userId() {
        return this.#id;
    }
}

const myUser = new User("Preyum", 12345, "securePass123");

console.log(myUser.name); // "Preyum"
// console.log(myUser.#id); // Syntax Error: Private field '#id' must be declared in an enclosing class

myUser.displayInfo(); // "User: Preyum, ID: 12345"

// Using getter to access private field
console.log(myUser.userId); // 12345
Enter fullscreen mode Exit fullscreen mode

Inheritance (extends and super())

  • Inheritance allows a class to inherit properties and methods from another class. This promotes code reuse and hierarchical organization.
  • extends: Keyword used to create a child class from a parent class.
  • super(): This keyword is used to call the constructor of the parent class. It must be called before using this in the child class constructor.
  • super.methodName(): It can also be used to call methods from the parent class, which is useful when overriding a method but still wanting to include the parent's logic.
// Parent Class
class Animal {
    constructor(name) {
        this.name = name;
    }

    eat() {
        console.log(`${this.name} is eating.`);
    }
}

// Child Class using extends
class Dog extends Animal {
    constructor(name, breed) {
        // Call the parent constructor using super()
        super(name);
        this.breed = breed;
    }

    // Overriding a parent method and calling it using super.methodName()
    eat() {
        super.eat(); // Call the parent's eat method
        console.log(`${this.name} the ${this.breed} is still hungry!`);
    }

    bark() {
        console.log("Woof! Woof!");
    }

    displayInfo() {
        console.log(`I am a ${this.breed} named ${this.name}.`);
    }
}

const myDog = new Dog("Buddy", "Golden Retriever");

myDog.eat();        
/* Output:
Buddy is eating.
Buddy the Golden Retriever is still hungry!
*/

myDog.bark();       // "Woof! Woof!"
myDog.displayInfo(); // "I am a Golden Retriever named Buddy."
Enter fullscreen mode Exit fullscreen mode

Built-in Objects and Web APIs

  • JavaScript provides several globally available built-in objects and functions to perform common tasks without needing to write custom logic.
  • Global Functions: Functions like parseInt(), parseFloat(), and isNaN() can be called directly from anywhere.
  • Static Objects: Objects like Math, JSON, and Array (when used for its static methods) are static. This means you do not use the new keyword with them; you call their properties and methods directly (e.g., Math.max(), JSON.stringify()).

Array.isArray()

  • In JavaScript, arrays are technically objects. If you use the typeof operator on an array, it returns "object", which isn't very helpful when you need to know if a variable is specifically a list.
  • To solve this, the Array built-in object provides a static method called Array.isArray() which reliably returns true if a value is an array, and false otherwise.
let myData = [1, 2, 3];
let myObj = { name: "Preyum" };

console.log(typeof myData); // "object" (Not very helpful!)
console.log(Array.isArray(myData)); // true (Reliable check)
console.log(Array.isArray(myObj)); // false
Enter fullscreen mode Exit fullscreen mode
  • Instantiable Objects: Objects like Date are constructors. You must use the new keyword to create an instance before you can use most of their methods.
// Global Functions: Parsing strings and checking values
console.log(parseInt("10.5")); // 10
console.log(parseFloat("10.5")); // 10.5
console.log(isNaN("Hello")); // true

// Static Object: Math (No 'new' keyword)
console.log(Math.round(4.7)); // 5
console.log(Math.floor(4.9)); // 4
console.log(Math.max(10, 20, 5)); // 20

// Instantiable Object: Date (Requires 'new' keyword)
let now = new Date();
console.log(now.getFullYear()); // Gets current year

// Instantiable Object: Error
// Used to create custom error objects, usually to be 'thrown'
let myError = new Error("Something went wrong!");
console.log(myError.message); // "Something went wrong!"

// Instantiable Object: Function
// Every JavaScript function is actually a Function object.
// While you can create them dynamically like this, standard declaration is preferred.
let sum = new Function('a', 'b', 'return a + b');
console.log(sum(2, 6)); // 8

// Primitive Wrapper Objects: String, Number, Boolean
// These are the objects that JavaScript uses behind the scenes for Auto-Boxing.
// WARNING: Do NOT use the 'new' keyword with them in modern code, as it creates an object instead of a primitive, leading to strict equality (===) bugs.
// They should only be used without 'new' for Type Conversion (e.g., Number("5")).
let badString = new String("Hello");
console.log(typeof badString); // "object" (This is bad!)
let goodString = String("Hello");
console.log(typeof goodString); // "string" (This is good!)
Enter fullscreen mode Exit fullscreen mode
  • JSON Object (Static): The JSON object contains methods for parsing JavaScript Object Notation (JSON) and converting values to JSON. This is crucial when sending or receiving data from APIs.
    • JSON.stringify(): Converts a JavaScript object or value to a JSON string.
    • JSON.parse(): Parses a JSON string, constructing the JavaScript value or object described by the string.
let user = { name: "Preyum", domain: "CS" };

// Converting an object to a string (e.g., to send to an API or save in local storage)
let jsonString = JSON.stringify(user);
console.log(jsonString); // '{"name":"Preyum","domain":"CS"}'
console.log(typeof jsonString); // "string"

// Parsing a string back into a JavaScript object
let parsedUser = JSON.parse(jsonString);
console.log(parsedUser.name); // "Preyum"
console.log(typeof parsedUser); // "object"
Enter fullscreen mode Exit fullscreen mode
  • Web Storage API (localStorage): While not technically part of the core JavaScript language (it's part of the browser's Web API), localStorage is an incredibly common built-in object used to store key-value pairs in the web browser. The data survives even after the browser window is closed.
    • Note: localStorage only stores strings. If you want to store an object, you MUST use JSON.stringify() before saving, and JSON.parse() when retrieving.
// Saving data (key, value)
localStorage.setItem("theme", "dark");

// Retrieving data
let currentTheme = localStorage.getItem("theme");
console.log(currentTheme); // "dark"

// Saving an object requires JSON.stringify
let sessionUser = { name: "Preyum", isLoggedIn: true };
localStorage.setItem("userSession", JSON.stringify(sessionUser));

// Retrieving an object requires JSON.parse
let retrievedSession = JSON.parse(localStorage.getItem("userSession"));
console.log(retrievedSession.isLoggedIn); // true
Enter fullscreen mode Exit fullscreen mode
  • Console Object: The console object provides access to the browser's debugging console (or the terminal in Node.js). It is incredibly useful for logging information, warnings, and errors during development.
    • console.log(): Prints general informational messages.
    • console.error(): Prints an error message (usually styled in red with a stack trace in browsers).
    • console.warn(): Prints a warning message (usually styled in yellow).
    • console.table(): Displays tabular data (like an array of objects) as a neat table.
    • console.time() and console.timeEnd(): Starts and stops a timer, useful for measuring how long an operation takes.
console.log("This is a standard log message.");
console.error("This is an error message!");
console.warn("This is a warning.");

// Displaying data nicely
let users = [
    { name: "Preyum", role: "Admin" },
    { name: "John", role: "User" }
];
console.table(users);

// Measuring performance
console.time("Loop Time");
for(let i = 0; i < 1000000; i++) {} // Some heavy operation
console.timeEnd("Loop Time"); // e.g., "Loop Time: 2.5ms"
Enter fullscreen mode Exit fullscreen mode

Document Object Model (DOM)

  • The Document Object Model (DOM) is a programming interface for web documents. It represents the page so that programs can change the document structure, style, and content.
  • When a web page is loaded, the browser creates a Document Object Model of the page, structured as a tree of objects.
  • The document itself is a built-in object provided by the browser that models the entire webpage. Because it represents the HTML document, it acts as the entry point to the DOM tree.
  • JavaScript can manipulate the DOM to create dynamic and interactive web experiences.
  • Note: In modern frameworks like React and Next.js, you rarely manipulate the DOM directly. Instead, you manipulate the "Virtual DOM" through state, and React handles the actual DOM updates. However, understanding the real DOM is essential.

The window Object (The Global Object)

  • While the DOM represents the document (the webpage content), the BOM (Browser Object Model) represents the browser window itself.
  • The core of the BOM is the window object. It is the global object in a browser environment.
  • Because it is the global object, every globally scoped variable, function, and built-in object is implicitly attached to it.
  • This means that objects like document, console, and functions like alert(), setTimeout(), or setInterval() are actually properties and methods of the window object. Because it is the global scope, you can usually omit typing window..
// The following two lines do the exact same thing:
window.console.log("Hello from the window!");
console.log("Hello from the window!");

// The 'document' is actually a property of the window:
// window.document.getElementById(...)

// Alert dialogs are window methods:
// window.alert("Warning!");
// alert("Warning!");

// Getting the inner width and height of the browser window itself
console.log("Window width:", window.innerWidth);
console.log("Window height:", window.innerHeight);
Enter fullscreen mode Exit fullscreen mode

The document Object Properties

  • You can directly access high-level parts of the webpage using built-in properties on the document object:
    • document.title: Gets or sets the title of the document (the text shown in the browser tab).
    • document.body: Returns the <body> element of the document, allowing you to manipulate the main content area directly.
// Reading the current page title
console.log(document.title); 

// Changing the page title dynamically
document.title = "New Page Title!";

// Changing the background color of the entire webpage body
document.body.style.backgroundColor = "lightblue";
Enter fullscreen mode Exit fullscreen mode

Common DOM Methods

  • Selecting Elements:

    • document.getElementById(id): Selects a single element by its ID.
    • document.querySelector(selector): Returns the first element that matches a CSS selector.
    • document.querySelectorAll(selector): Returns a NodeList of all elements that match a CSS selector.
  • Modifying Elements:

    • element.textContent: Gets or sets the text content of a node and its descendants.
    • element.innerHTML: Gets or sets the HTML markup contained within the element.
    • element.style.property: Modifies the inline CSS style of an element.
    • element.classList.add(className) / .remove() / .toggle(): Modifies the classes on an element.
  • Event Listeners and Handlers:

    • Event Handlers (e.g., onclick, onmouseover): Properties on DOM elements that allow you to assign a single function to an event. It's an older method but still widely used for simple interactions.
    • addEventListener(event, function): The modern, preferred way to attach events. It allows attaching multiple functions to the same event on the same element without overwriting them.
// --- Selecting an element ---
// Let's assume there is a <button id="myBtn" class="btn primary">Click Me!</button> in the HTML
let btn = document.getElementById("myBtn");
let sameBtn = document.querySelector(".btn.primary");

// --- Modifying the element ---
// Change the text inside the button
btn.textContent = "Processing...";

// Add a new CSS class
btn.classList.add("loading");

// Change a specific style directly
btn.style.backgroundColor = "blue";

// --- Adding interactivity (Events) ---

// Method 1: Using the event handler property (onclick)
// Note: If you assign another function to btn.onclick later, it will overwrite this one.
btn.onclick = function() {
    console.log("Button clicked using onclick!");
};

// Method 2: Using addEventListener (Preferred)
btn.addEventListener("click", function(event) {
    console.log("Button clicked using addEventListener!");
    // 'event' object contains details about the action (e.g., mouse coordinates)
    console.log("Mouse X:", event.clientX, "Mouse Y:", event.clientY);
});
Enter fullscreen mode Exit fullscreen mode

Asynchronous JavaScript

  • JavaScript is single-threaded and synchronous by default. Asynchronous JavaScript allows the code to continue running other tasks while waiting for long-running operations (like fetching data, reading files) to complete, preventing the browser or server from freezing.
console.log("1. Start");

// This mimics a long-running task
setTimeout(() => {
    console.log("2. Async Operation Finished");
}, 1000);

console.log("3. End");
// Output order will be: 1, 3, 2
Enter fullscreen mode Exit fullscreen mode

setTimeout() and setInterval()

  • Both are built-in methods of the window object used to schedule the execution of functions.

setTimeout()

  • setTimeout() executes a block of code once after a specified delay (in milliseconds).
console.log("Waiting for my Apple...");
let timeoutId = setTimeout(() => {
    console.log("Apple is served!");
}, 2000); // Delays execution by 2000 milliseconds (2 seconds)

// You can cancel the timeout before it executes using clearTimeout()
// clearTimeout(timeoutId);
Enter fullscreen mode Exit fullscreen mode

setInterval()

  • setInterval() executes a block of code repeatedly, with a fixed time delay between each call.
  • It will continue running indefinitely until you explicitly stop it using clearInterval().
let count = 1;
console.log("Starting timer...");

// Runs every 1 second (1000 ms)
let intervalId = setInterval(() => {
    console.log("Tick: " + count);

    // Stop the interval after 3 ticks
    if (count === 3) {
        clearInterval(intervalId);
        console.log("Timer stopped.");
    }
    count++;
}, 1000);
Enter fullscreen mode Exit fullscreen mode

Callbacks

  • A callback is a function passed as an argument to another function, which is then invoked inside the outer function to complete some kind of routine or action.
  • Example of order and production (without order, production cannot start):
let order = (call_production) => {
    console.log("Order placed, please call production.");
    // We execute the callback function here
    call_production();
};

let production = () => {
    console.log("Order received, starting production of Apple Juice.");
};

order(production); 
Enter fullscreen mode Exit fullscreen mode
  • Issues with callbacks, the callback hell: Callback hell (or Pyramid of Doom) happens when multiple asynchronous operations are chained together using nested callbacks, making the code hard to read and maintain. Promises (discussed next) solve this.
// Callback Hell Example
step1(function() {
    step2(function() {
        step3(function() {
            step4(function() {
                console.log("All steps finished!");
            });
        });
    });
});
Enter fullscreen mode Exit fullscreen mode

Promises

  • A Promise represents the eventual completion (or failure) of an asynchronous operation and its resulting value. It solves the callback hell problem by allowing chaining.
  • reject() vs throw:
    • throw is for immediate, synchronous errors. It pauses execution and looks for the nearest catch block in the current stack.
    • reject() is for future, asynchronous errors. It signals that an async operation failed. Since the original try...catch might have already finished executing while waiting for the async task, reject() allows the error to be handled later via .catch() or an async/await try...catch.
function makeOrder() {
    return new Promise((resolve, reject) => {
        let isStockAvailable = false;

        setTimeout(() => {
            if (isStockAvailable) {
                resolve("Order placed successfully");
            } else {
                // Signals failure for an ASYNC operation
                reject("Error: Out of stock"); 
            }
        }, 1000);
    });
}

makeOrder()
    .then(msg => console.log(msg))
    .catch(err => console.error("Caught async error:", err));
Enter fullscreen mode Exit fullscreen mode
  • Change example to use promises:
function makeOrder() {
    return new Promise((resolve, reject) => {
        let isStockAvailable = true;

        if (isStockAvailable) {
            resolve("Order placed successfully"); // This goes to the first .then()
        } else {
            reject("Error: Out of stock"); // This skips directly to .catch()
        }
    });
}

makeOrder()
    // No semicolons between then, catch, finally so the code can cascade
    .then((message) => {
        // Step 1: Receives the resolved value from makeOrder
        console.log(message); // Output: "Order placed successfully"
        return "Starting production"; // This returned value is passed to the NEXT .then()
    })
    .then((nextStep) => {
        // Step 2: Receives the returned value from the previous .then()
        console.log(nextStep); // Output: "Starting production"
    })
    .catch((error) => {
        // Step 3: Only runs if reject() was called, or if an error was thrown above
        console.log(error); 
    })
    .finally(() => {
        // Step 4: Always runs at the very end, whether it was resolved or rejected
        console.log("Operation closed (runs regardless of success or failure).");
    });
Enter fullscreen mode Exit fullscreen mode

Async and Await

  • async and await are syntactic sugar on top of Promises, making asynchronous code look and behave more like synchronous code, which is much easier to read.
  • An async function always returns a Promise. The await keyword pauses the execution of the async function until the Promise settles (resolves or rejects).
function fetchFruit() {
    return new Promise((resolve) => {
        setTimeout(() => {
            resolve("Mango");
        }, 1500);
    });
}

async function prepareSmoothie() {
    console.log("2. Starting prep inside async function...");

    try {
        // Execution inside this function pauses here until fetchFruit is done.
        // However, the rest of the synchronous code outside this function keeps running!
        let fruit = await fetchFruit(); 
        console.log(`4. Smoothie made with ${fruit}!`);
    } catch (error) {
        console.log("Failed to get fruit.");
    }
}

console.log("1. Ordering Smoothie...");

// Calling the async function. It starts running synchronously until it hits 'await'.
prepareSmoothie();

console.log("3. Doing other tasks while waiting for the smoothie...");

/* 
Output Order:
1. Ordering Smoothie...
2. Starting prep inside async function...
3. Doing other tasks while waiting for the smoothie...
(After 1.5 seconds delay)
4. Smoothie made with Mango!
*/
Enter fullscreen mode Exit fullscreen mode

Modules (Import and Export)

  • Modules allow you to break your code into separate files. This makes it easier to maintain the code base. React and Next.js are entirely built around importing and exporting components and functions.
  • Important Note on Usage: To actually use ES6 modules natively in a browser, the HTML script tag importing the main JavaScript file MUST include the type="module"\ attribute (e.g., <script type="module" src="app.js"></script>\). In modern frameworks like React/Next.js, the build tools handle this for you automatically.

Named Exports

  • You can create named exports two ways: individually or all at once at the bottom. You can have multiple named exports per file.
  • When importing, you must use the exact same name inside curly braces {}\. You can also rename (alias) imports using the as\ keyword to avoid naming conflicts.
// mathUtils.js (Exporting)

// 1. Exporting individually on the same line
export const add = (a, b) => a + b;

// 2. Declaring first, exporting at the bottom
const subtract = (a, b) => a - b;
const multiply = (a, b) => a * b;

export { subtract, multiply }; 

// app.js (Importing)
// Using 'as' to alias 'add' to 'addNumbers'
import { add as addNumbers, subtract, multiply } from './mathUtils.js';

console.log(addNumbers(5, 3)); // 8
console.log(multiply(2, 4)); // 8
Enter fullscreen mode Exit fullscreen mode

Default Exports

  • You can only have one default export per file. It is often used for exporting the main component in a file.
  • When importing, you can name it anything you want and you do not use curly braces.
// Greeting.js (Exporting)
const Greeting = () => "Hello, Preyum!";
export default Greeting;

// app.js (Importing)
// We can name it whatever we want, let's call it GreetComp
import GreetComp from './Greeting.js';
console.log(GreetComp()); // "Hello, Preyum!"
Enter fullscreen mode Exit fullscreen mode

Stay tuned for my next post, where we'll be exploring React! While this covers the essentials, If thereโ€™s anything you feel I should have included, let me know in the comments below and I'll gladly add it!

Top comments (0)