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. How to Install JavaScript
  3. Putting Comments in the Code
  4. The Variables
  5. The DataTypes
  6. Operators, Shorthand and Semicolon
  7. Ternary Operator and Short-Circuit Evaluation
  8. The Escape Character and Strings
  9. For, While, Do While and Switch
  10. Array Methods (map, filter, reduce)
  11. Regular, Anonymous and Arrow Functions
  12. Closures
  13. Function Arguments
  14. Spread Operator
  15. Rest Parameters
  16. Destructuring object values for assignment
  17. Object Methods
  18. Optional Chaining and Nullish Coalescing
  19. Backtick for Strings with values
  20. Getters and Setters
  21. Asynchronous JavaScript
  22. The setTimeout() Function
  23. Callbacks
  24. Promises
  25. Async and Await
  26. Modules (Import and Export)

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
  • For multiline comment we can use /* */
let fruit = "apple"
/* This is a 
    Multiline
    Comment
*/
let number = 9

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)
  • 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!
  • 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
  • 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"]

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"

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
  • 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)
  • 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
  • Also incrementing and decrementing shorthand like ++, --:
let step = 1;
step++; // Increments step by 1 (Result: 2)
step--; // Decrements step by 1 (Result: 1)

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"
  • 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 && ).
    • || (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"

The Escape Character and Strings

  • Example of string with "" and inside also double quotes:
let quote = "Preyum Kumar said, "Computer Vision is fascinating!"";
  • Other ways like using single quote to cover double quotes:
let singleQuoteStr = 'Preyum Kumar said, "NLP is amazing!"';
  • Other way to use back ticks

    ``

    to put both single and double quotes inside:

let backtickStr = `Preyum's favorite subject is "Computer Science".`;
  • To put backslash itself we use two backslashes:
let path = "C:\Users\PreyumKumar\Documents";

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]);
}
  • 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++;
}
  • 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);
  • 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.");
}

Array Methods (map, filter, reduce)

  • 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]
  • 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]
  • 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

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"));
  • 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;
};
  • 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"
});

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

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"
  • 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);
}

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"]

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

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"
  • Destructuring nested object for assignment:
let expertUser = {
    profile: {
        firstName: "Preyum",
        expertise: "Computer Vision"
    }
};

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

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"]]

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
  • 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)

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);
  • 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."

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"

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

The setTimeout() Function

  • setTimeout() is a method that calls a function or evaluates an expression after a specified number of milliseconds.
console.log("Waiting for my Apple...");
setTimeout(() => {
    console.log("Apple is served!");
}, 2000); // Delays execution by 2000 milliseconds (2 seconds)

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); 
  • 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!");
            });
        });
    });
});

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.
  • 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).");
    });

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!
*/

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.
  • 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
  • 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 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!"

Stay tuned for my next blog, where I'll dive into React! In the meantime, if you feel I missed anything here, drop a comment below and I'll make sure to add it.

Total
0
Shares
Leave a Reply

Your email address will not be published. Required fields are marked *

Previous Post

Sam Altman-backed fusion startup Helion in talks to sell power to OpenAI

Next Post

Terms of reference vs project charter: Which do you need?

Related Posts