setTimeout Function in JavaScript

  • The setTimeout function in JavaScript is used to execute a function or a specified piece of code after a certain delay (measured in milliseconds). It is part of the browser's Web APIs, specifically the WindowOrWorkerGlobalScope mixin.
Here's a detailed explanation:
  • Syntax


    let timeoutID = setTimeout(function, delay, [arg1, arg2, ...]);

  • function: The function to be executed after the delay.
  • delay: The time, in milliseconds, to wait before executing the function. (1 second = 1000 milliseconds)
  • [arg1, arg2, ...]: Optional arguments to pass to the function when it is executed.
  • Basic Example


    setTimeout(() => {
      console.log("This message is displayed after 3 seconds.");
    }, 3000);

  • In this example, the message "This message is displayed after 3 seconds." will be logged to the console after 3 seconds.
Variations and Use Cases

1. Passing Arguments to the Callback Function


    function greet(name) {
      console.log(`Hello, ${name}!`);
    }

    setTimeout(greet, 2000, 'Gagan');

  • Here, the `greet` function is called with the argument 'Gagan' after 2 seconds.
2. Cancelling a Timeout
  • The `setTimeout` function returns a unique identifier (ID) for the timeout, which can be used to cancel the timeout using the `clearTimeout` function.


    let timeoutID = setTimeout(() => {
      console.log("This will not be displayed.");
    }, 5000);

    // Cancel the timeout
    clearTimeout(timeoutID);

  • In this case, the timeout is cancelled before it can execute, so the message will not be displayed.
3. Using Anonymous Functions
  • You can use anonymous functions directly within `setTimeout`.


    setTimeout(function () {
      console.log("This is an anonymous function.");
    }, 1000);


4. Chaining Timeouts
  • You can chain timeouts to create a sequence of actions.

    setTimeout(() => {
      console.log("First message");
      setTimeout(() => {
        console.log("Second message");
      }, 2000);
    }, 1000);

  • This will log "First message" after 1 second and "Second message" 2 seconds after the first message.
Interview Scenarios and Questions

1. Explain how `setTimeout` works.
  • setTimeout schedules a function to run after a specified delay. It does not block the main thread; instead, it puts the function in the event queue after the delay.
2. What happens if the delay is set to 0?
  • Setting the delay to 0 means the function will be executed as soon as possible but still after the current execution context completes. It is often used to defer execution until the call stack is clear.

    setTimeout(() => {
        console.log("Executed after current stack.");
    }, 0);
    console.log("Executed first.");

  • Output:
Executed first.
Executed after current stack.

3. How to create a recurring action using `setTimeout`?
  • To create a recurring action, you can recursively call `setTimeout` inside the callback function.

    function recurringAction() {
        console.log("This action repeats every second.");
        setTimeout(recurringAction, 1000);
    }

    setTimeout(recurringAction, 1000);


4. Differences between `setTimeout` and `setInterval`.

  • setInterval repeatedly calls a function with a fixed time delay between each call, whereas setTimeout calls a function once after a specified delay.

    // Using setInterval
    setInterval(() => {
      console.log("This message repeats every 2 seconds.");
    }, 2000);

    // Equivalent using setTimeout
    function repeatAction() {
      console.log("This message repeats every 2 seconds.");
      setTimeout(repeatAction, 2000);
    }

    setTimeout(repeatAction, 2000);


5. How to handle `this` context in `setTimeout`?

  • When using `setTimeout` inside an object method, `this` may not refer to the object as expected. Use arrow functions or `bind` to preserve the `this` context.

    const obj = {
      name: 'Gagan',
      greet: function () {
        setTimeout(() => {
          console.log(`Hello, ${this.name}`);
        }, 1000);
      }
    };

    obj.greet(); // Output: Hello, Gagan


Conclusion

  • Understanding `setTimeout` and its variations is essential for handling asynchronous operations in JavaScript. It helps in scheduling tasks, creating delays, and managing timed events, which are crucial for building responsive and performant web applications.

Event Loop in JavaScript

  • The event loop is a fundamental part of JavaScript's runtime that allows it to handle asynchronous operations (like fetching data from a server or reading a file) without blocking the main thread of execution.
How It Works
  • Call Stack: This is where your code gets executed. When you call a function, it's added to the call stack. When the function returns, it's removed from the call stack.
  • Web APIs: These are provided by the browser (or Node.js) and include things like `setTimeout`, `fetch`, DOM events, etc. When you call these functions, they run in the background.
  • Callback Queue: Once an asynchronous operation completes (like `setTimeout`), its callback function is added to the callback queue.
  • Event Loop: This constantly checks the call stack and the callback queue. If the call stack is empty, it takes the first function from the callback queue and pushes it onto the call stack, allowing it to be executed.
Example
  • Let's go through an example to see how this works.


  console.log('Start');

  setTimeout(() => {
    console.log('Timeout callback');
  }, 2000);

  console.log('End');

  • Step-by-Step Execution:
1. Start
  • console.log('Start') is called and added to the call stack.
  • It prints "Start" to the console and is then removed from the call stack.
2. setTimeout
  • setTimeout is called and added to the call stack.
  • setTimeout is a Web API, so it sets a timer for 2000 milliseconds (2 seconds) and then its callback function is added to the callback queue.
  • setTimeout is then removed from the call stack.
3. End
  • console.log('End') is called and added to the call stack.
  • It prints "End" to the console and is then removed from the call stack.
4. Event Loop
  • The event loop checks if the call stack is empty (which it is).
  • After 2 seconds, the `setTimeout` callback function (`console.log('Timeout callback')`) is added to the callback queue.
5. Executing Callback:
  • The event loop moves the `setTimeout` callback from the callback queue to the call stack.
  • The callback function (`console.log('Timeout callback')`) is executed, printing "Timeout callback" to the console.
  • The callback function is then removed from the call stack.
Visualization
  • Here's a simplified visualization of the process:
1. Initial State
  • Call Stack: []
  • Web APIs: []
  • Callback Queue: []
2. After `console.log('Start')`:
  • Call Stack: [console.log]
  • Web APIs: []
  • Callback Queue: []
3. After `setTimeout`:
  • Call Stack: []
  • Web APIs: [setTimeout]
  • Callback Queue: []
4. After `console.log('End')`:
  • Call Stack: [console.log]
  • Web APIs: [setTimeout]
  • Callback Queue: []
5. 2 Seconds Later:
  • Call Stack: []
  • Web APIs: []
  • Callback Queue: [setTimeout callback]
6. Callback Execution:
  • Call Stack: [setTimeout callback]
  • Web APIs: []
  • Callback Queue: []
Conclusion
  • The event loop ensures that JavaScript can handle asynchronous operations efficiently. By understanding the call stack, web APIs, and the callback queue, you can grasp how JavaScript manages to execute asynchronous code without blocking the main thread, making your applications more responsive and efficient.

Javascript Program for masking email and phone number

  • Here is the two different function that receive email and phone and return masked value.


    export const maskEmail = (email) => {
        if (email) {

            // Split the email into local and domain parts
            const atIndex = email.indexOf('@');
            if (atIndex === -1) {
                // If no '@' symbol is found, return the original email
                return email;
            }

            const localPart = email.substring(0, atIndex);
            const domainPart = email.substring(atIndex);

            // Determine how many characters to mask in the local part
            let maskedPartLength = localPart.length - 3;
            if (maskedPartLength < 1) {
                // If the local part is too short, adjust the number of visible characters
                maskedPartLength = localPart.length > 1 ? 1 : 0;
            }

            // Create the masked part
            const maskedPart = '*'.repeat(maskedPartLength);
            const visiblePart = localPart.substring(0, localPart.length - maskedPartLength);

            // Combine the visible part, masked part, and domain part
            return `${visiblePart}${maskedPart}${domainPart}`;
        } else {
            return "****@****.com"
        }
    }

    export const maskPhoneNumber = (phoneNumber) => {
        if (phoneNumber) {

            // Check if the phone number has less than 4 digits
            if (phoneNumber.length <= 4) {
                return phoneNumber; // Return the original if too short to mask
            }

            // Mask all but the last four digits
            const visiblePart = phoneNumber.slice(-4);
            const maskedPartLength = phoneNumber.length - 4;
            const maskedPart = '*'.repeat(maskedPartLength);

            // Combine the masked part and the visible part
            return `${maskedPart}${visiblePart}`;
        } else {
            return "**** **** ****"
        }
    }


Explain timestamp and unix timestamp

Timestamp
  • A timestamp is a sequence of characters or encoded information identifying when a certain event occurred, typically giving date and time of day, sometimes accurate to a small fraction of a second. Timestamps are used in various fields such as computing, networking, data analysis, and record-keeping to track and log events.
Key Aspects of Timestamps:
1. Format:
  • Timestamps often follow a specific format to ensure consistency and readability. A common format is the ISO 8601 standard, which represents dates and times in an unambiguous way. For example, "2024-07-18T12:34:56Z" represents the 18th of July, 2024, at 12:34:56 UTC.
  • Other formats can include UNIX timestamps, which count the number of seconds since the "epoch" (00:00:00 UTC on 1 January 1970).
2. Components:
  • Date: The calendar date, often formatted as YYYY-MM-DD.
  • Time: The time of day, usually in 24-hour format, often formatted as HH:MM:SS.
  • Time Zone Information: Timestamps can include time zone offsets or indicate UTC (Coordinated Universal Time) with a "Z" suffix.
3. Uses and Applications:
  • Database Management: Timestamps are critical for tracking when data records are created, modified, or deleted.
  • Log Files: System logs, application logs, and event logs all use timestamps to record the exact time events occur, aiding in troubleshooting and auditing.
  • Version Control Systems: In software development, timestamps help manage different versions of files and track changes over time.
  • Network Protocols: Timestamps are used in protocols like NTP (Network Time Protocol) to synchronize clocks across networked devices.
  • Data Analysis: In time series analysis, timestamps allow for the tracking of changes and trends over time.
4. Precision and Accuracy:
  • Timestamps can vary in precision, from seconds to milliseconds, microseconds, or even nanoseconds, depending on the requirements.
  • Accurate timestamps are crucial in contexts where precise timing is important, such as in financial transactions, scientific experiments, or telecommunications.
5. Storage and Representation:
  • In databases, timestamps are often stored as specific data types like `DATETIME`, `TIMESTAMP`, or `DATE`.
  • In programming, timestamps can be represented as strings, integers (e.g., UNIX time), or specialized date/time objects provided by libraries or frameworks.
Example:
  • Consider a log entry with the following timestamp: "2024-07-18T15:45:30.123+05:30".
    • 2024-07-18: This represents the date (18th July 2024).
    • 15:45:30.123: This represents the time (3:45 PM and 30.123 seconds).
    • +05:30: This indicates the time zone offset (5 hours and 30 minutes ahead of UTC).
  • This timestamp provides a precise and unambiguous record of when the log entry was created, facilitating accurate tracking and analysis.
Unix Timestamp
  • Unix time, also known as Unix epoch time or POSIX time, is a system for tracking time that counts the number of seconds elapsed since the Unix epoch. The Unix epoch is set at 00:00:00 UTC on January 1, 1970. Unix time is widely used in computing systems, particularly in Unix-like operating systems, for representing points in time.
Key Aspects of Unix Time:
1. Definition and Origin:
  • Unix time starts from the Unix epoch: 00:00:00 UTC on January 1, 1970. This moment is designated as "zero time."
  • Time is represented as the number of seconds that have passed since this epoch. For instance, a Unix timestamp of 1625078400 corresponds to the number of seconds between January 1, 1970, and July 1, 2021.
2. Representation:
  • Unix time is typically stored as a 32-bit or 64-bit integer. In a 32-bit system, the range of Unix time is from January 1, 1970, to January 19, 2038 (the Year 2038 problem). A 64-bit system extends this range significantly.
  • Unix time does not account for leap seconds, which means there can be minor discrepancies compared to UTC.
3. Formats:
  • Binary: As an integer, Unix time is a simple, efficient way to store time values.
  • Human-readable: Unix timestamps can be converted to human-readable formats using various programming languages and tools.
4. Usage:
  • File Systems: Unix time is used in file systems to record the creation, modification, and access times of files.
  • Databases: Timestamps in databases often use Unix time for consistency and ease of calculation.
  • Programming: Many programming languages and frameworks provide built-in functions to work with Unix time. For example, Python has the `time` module, and JavaScript has the `Date` object.
5. Advantages:
  • Simplicity: Representing time as a single integer makes calculations and comparisons straightforward.
  • Portability: Unix time is a cross-platform standard, ensuring compatibility between different systems and programming languages.
  • Consistency: It provides a uniform way of representing time across different systems and applications.
6. Conversion:
  • From Unix time to Human-readable format: Programming languages provide functions to convert Unix timestamps to date and time strings. For example, in Python, you can use `datetime.fromtimestamp()`.
  • From Human-readable format to Unix time: Similarly, you can convert human-readable date and time strings to Unix timestamps. In Python, `time.mktime()` can be used for this purpose.
  • Unix time remains a fundamental concept in computing, providing a reliable and efficient way to handle time representation and calculations across various applications and systems.

jsonwebtoken (JWT) npm package

  • The jsonwebtoken package in npm (Node Package Manager) is a library that allows you to work with JSON Web Tokens (JWTs) in Node.js. JWTs are a compact, URL-safe means of representing claims between two parties. They are commonly used for authentication and information exchange.
Key Features of jsonwebtoken
  • Signing Tokens: You can sign tokens with a secret or a private key to ensure the integrity and authenticity of the token.
  • Verifying Tokens: You can verify tokens to ensure they have been signed correctly and have not been tampered with.
  • Decoding Tokens: You can decode tokens to read the payload without verifying their signature.
Installation
  • To install jsonwebtoken, you use npm:


    npm install jsonwebtoken

1. Signing a Token

  • To create a token, you use the `sign` method. You pass a payload (which is the data you want to store in the token), a secret or private key, and optionally some options like the token's expiration time.


    const jwt = require('jsonwebtoken');

    const payload = {
        userId: '123456',
        username: 'john_doe',
        role: 'admin'
    };

    const secretKey = 'your-256-bit-secret';

    const token = jwt.sign(payload, secretKey, { expiresIn: '1h' });

    console.log(token);

2. Verifying a Token

  • To verify a token, you use the `verify` method. You pass the token, the secret or public key, and a callback function.


    jwt.verify(token, secretKey, (err, decoded) => {
        if (err) {
            console.log('Token verification failed:', err);
        } else {
            console.log('Token is valid:', decoded);
        }
    });

3. Decoding a Token

  • To decode a token without verifying its signature, you use the `decode` method. This can be useful for extracting information from a token without checking its integrity.


    const decoded = jwt.decode(token);
    console.log(decoded);

Example Usage in Express.js

  • Here's a basic example of how you might use `jsonwebtoken` in an Express.js application for user authentication.


    const express = require('express');
    const jwt = require('jsonwebtoken');

    const app = express();
    const port = 3000;

    const secretKey = 'your-256-bit-secret';

    // Middleware to check the token
    const authenticateJWT = (req, res, next) => {
        const token = req.header('Authorization');

        if (token) {
            jwt.verify(token, secretKey, (err, decoded) => {
                if (err) {
                    return res.status(403).send('Forbidden');
                }
                req.user = decoded;
                next();
            });
        } else {
            res.status(401).send('Unauthorized');
        }
    };

    // Login route
    app.post('/login', (req, res) => {
        // In a real application, you'd verify the user's credentials
        const user = {
            id: 1,
            username: 'john_doe',
            role: 'admin'
        };

        const token = jwt.sign(user, secretKey, { expiresIn: '1h' });
        res.json({ token });
    });

    // Protected route
    app.get('/protected', authenticateJWT, (req, res) => {
        res.send('This is a protected route. Your user info: ' + JSON.stringify(req.user));
    });

    app.listen(port, () => {
        console.log(`Server running at http://localhost:${port}/`);
    });

In this example:

  • The `/login` route simulates a login, signing a token with user information.
  • The `/protected` route is protected by the `authenticateJWT` middleware, which verifies the token and grants access if valid.
Conclusion
  • The `jsonwebtoken` package is a powerful tool for handling JWTs in a Node.js environment. It allows you to securely transmit information between parties, ensuring that the data is verifiable and trustworthy. With easy-to-use methods for signing, verifying, and decoding tokens, it integrates well with authentication systems in web applications.

What is JWT (JSON Web Token)

JSON Web Token (JWT) Detailed Explanation

  • A JSON Web Token (JWT) is an open standard (RFC 7519) for securely transmitting information between parties as a JSON object. This information can be verified and trusted because it is digitally signed.
Structure of a JWT
  • A JWT is composed of three parts separated by dots ('.'):
    • 1. Header
    • 2. Payload
    • 3. Signature
  • Each part is Base64Url encoded.
  • 1. Header: The header typically consists of two parts:
    • The type of token, which is JWT.
    • The signing algorithm being used, such as HMAC SHA256 or RSA.
  • Example:

    {
        "alg": "HS256",
        "typ": "JWT"
    }

  • 2. Payload: The payload contains the claims. Claims are statements about an entity (typically, the user) and additional data. There are three types of claims:
    • Registered claims: These are a set of predefined claims which are not mandatory but recommended, to provide a set of useful, interoperable claims. Some of them are `iss` (issuer), `exp` (expiration time), `sub` (subject), and `aud` (audience).
    • Public claims: These can be defined at will by those using JWTs. But to avoid collisions, they should be defined in the IANA JSON Web Token Registry or be defined as a URI that contains a collision-resistant namespace.
    • Private claims: These are the custom claims created to share information between parties that agree on using them and are neither registered nor public.
  • Example:

    {
        "sub": "1234567890",
        "name": "John Doe",
        "admin": true
    }

  • 3. Signature: To create the signature part, you have to take the encoded header, the encoded payload, a secret, the algorithm specified in the header, and sign that.
  • Example:

    HMACSHA256(
        base64UrlEncode(header) + "." +
        base64UrlEncode(payload),
        secret
    )

  • Putting all together, you get a JWT that looks like this:

    eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.
    eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.
    SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c


How JWT Works

  • Authentication: When the user logs in, the server verifies the credentials. If valid, the server generates a JWT and sends it to the user.
  • Client-side storage: The client stores the JWT (usually in local storage or a cookie).
  • Authorization: Every time the client makes a request to a protected route or resource, it sends the JWT along with the request. This can be done using an HTTP header like `Authorization: Bearer <token>`.
  • Server-side verification: The server verifies the token's signature and checks the claims (e.g., expiration date, issuer) to determine if the request is valid.
Advantages of JWT
  • Compact: Due to their size, JWTs can be sent through a URL, POST parameter, or inside an HTTP header, and they are easy to use in mobile devices.
  • Self-contained: The payload contains all the information needed about the user, reducing the need to query the database.
  • Secure: The signature is calculated using the header, payload, and a secret or private key, ensuring the integrity and authenticity of the token.
Use Cases
  • Authentication: Commonly used for user authentication.
  • Information Exchange: Securely transmitting information between parties.
  • Authorization: Verifying if the user has the correct permissions to access a resource.
Security Considerations
  • Always use HTTPS to ensure the JWT is sent securely.
  • Keep your secret or private key secure.
  • Use a strong signing algorithm (e.g., RSA or HMAC with SHA-256).
  • Validate the token’s claims, such as expiration time (`exp`), issuer (`iss`), and audience (`aud`).
  • JWTs are widely used due to their simplicity and the security they provide, making them an excellent choice for modern web applications.

bcryptjs npm package

  • The `bcryptjs` npm package is a library for hashing passwords using the bcrypt algorithm in JavaScript, particularly in Node.js environments. It is a pure JavaScript implementation of bcrypt, which makes it highly portable and easy to use without requiring native bindings or dependencies, unlike the `bcrypt` package that relies on C++ bindings.
Here is a detailed explanation of the `bcryptjs` package:
  • Installation: To install the `bcryptjs` package, you can use npm:


    npm install bcryptjs

  • Features: Here is the list of features.
  • No Dependencies: Pure JavaScript implementation, which means it doesn't need any native modules or compilation.
  • Cross-Platform: Works consistently across different platforms and environments.
  • Compatibility: Compatible with the bcrypt algorithm used in other libraries and platforms.
  • Security: Provides a secure way to hash and compare passwords.
Usage
  • Hashing Passwords: To hash a password, you can use the `hash` function. This function takes the password and a salt (or a number of rounds to generate a salt) and returns a hashed password.


    const bcrypt = require('bcryptjs');

    const password = 'myPassword123';
    const saltRounds = 10;

    bcrypt.hash(password, saltRounds, function (err, hash) {
        if (err) throw err;
        console.log('Hashed password:', hash);
    });

  • You can also use the synchronous version:


    const hash = bcrypt.hashSync(password, saltRounds);
    console.log('Hashed password:', hash);

  • Comparing Passwords: To compare a plaintext password with a hashed password, you can use the `compare` function. This function takes the plaintext password and the hashed password and returns a boolean indicating if they match.


    bcrypt.compare(password, hash, function (err, result) {
        if (err) throw err;
        console.log('Password matches:', result);
    });

  • The synchronous version:


    const result = bcrypt.compareSync(password, hash);
    console.log('Password matches:', result);

  • Salting: Salting is a process of adding a unique value (salt) to the password before hashing it. This makes it more secure by ensuring that even if two users have the same password, their hashes will be different.
  • Generating a Salt: You can generate a salt using the `genSalt` function. This function takes the number of rounds as an argument, which determines the computational complexity of generating the salt.


    bcrypt.genSalt(saltRounds, function (err, salt) {
        if (err) throw err;
        console.log('Generated salt:', salt);
    });  

  • The synchronous version:


    const salt = bcrypt.genSaltSync(saltRounds);
    console.log('Generated salt:', salt);

  • Best Practices:
  • Use a sufficient number of salt rounds: A higher number of rounds increases the security but also the computation time. 10-12 rounds are commonly used values.
  • Do not use synchronous functions in production: The synchronous functions block the event loop, which can be detrimental in a high-load environment.
  • Store the hashed password, not the plaintext: Always store the hashed version of passwords in your database, never the plaintext.
  • Example: Here is a complete example of hashing and verifying a password using bcryptjs:


    const bcrypt = require('bcryptjs');

    const password = 'myPassword123';
    const saltRounds = 10;

    // Hash the password
    bcrypt.hash(password, saltRounds, function (err, hash) {
        if (err) throw err;
        console.log('Hashed password:', hash);

        // Compare the password with the hash
        bcrypt.compare(password, hash, function (err, result) {
            if (err) throw err;
            console.log('Password matches:', result); // true
        });
    });

  • In this example, the password is first hashed and then verified to see if it matches the hashed version. This demonstrates the typical workflow when handling user passwords in an application.
  • Summary: The `bcryptjs` package is a robust and reliable solution for hashing and verifying passwords in JavaScript applications. Its pure JavaScript implementation ensures ease of use and compatibility across different environments, making it a popular choice for developers needing secure password hashing.

Phase 3 — Components Deep Dive

Chapter 1 — What We Are Going to Learn and Why In Phase 2 you learned what a component is and how to create one. You know that a component h...