TypeScript (TS)

TypeScript (TS) is a superset of JavaScript that adds static typing and other features to help catch errors early, improve code maintainability, and enable better tooling (like autocomplete). It compiles to plain JavaScript.

Here are the most important concepts in TypeScript, each with a brief explanation and a small example.

1. Basic Types TypeScript provides primitive types to explicitly define variable types.

typescript

let name: string = "Alice";
let age: number = 30;
let isActive: boolean = true;
let nothing: null = null;
let undefinedValue: undefined = undefined;

// Arrays
let numbers: number[] = [1, 2, 3];
let strings: Array<string> = ["a", "b"];

2. Type InferenceTypeScript infers types when not explicitly annotated, reducing boilerplate.

typescript

let city = "New York";  // Inferred as string
city = 123;             // Error: Type 'number' is not assignable to 'string'

3. Any, Unknown, Never, Void

  • any: Disables type checking (avoid when possible).
  • unknown: Safer alternative; requires narrowing before use.
  • void: For functions returning nothing.
  • never: For values that never occur (e.g., endless loops).

typescript

let flexible: any = 5;
flexible = "hello";     // OK

let safe: unknown = 10;
if (typeof safe === "number") {
  console.log(safe + 5);  // OK after narrowing
}

function throwError(): never {
  throw new Error("Oops");
}

function log(): void {
  console.log("No return");
}

4. Union and Intersection Types

  • Union (|): Value can be one of several types.
  • Intersection (&): Combines multiple types.

typescript

let id: string | number = "abc";
id = 123;  // OK

type Admin = { name: string; privileges: string[] };
type Guest = { name: string; readonly: boolean };
type ElevatedUser = Admin & Guest;  // Has all properties

5. Type Aliases and InterfacesDefine custom types. Interfaces are more extensible (can be merged).

typescript

type Point = { x: number; y: number };  // Type alias

interface Shape {
  color: string;
}
interface Circle extends Shape {
  radius: number;
}

let circle: Circle = { color: "red", radius: 10 };

6. Functions and OverloadsTyped parameters, returns, and optional/default params. Overloads allow multiple signatures.

typescript

function add(a: number, b: number): number;
function add(a: string, b: string): string;
function add(a: any, b: any): any {
  return a + b;
}

add(1, 2);      // number
add("hello", "world");  // string

7. GenericsCreate reusable components that work with multiple types.

typescript

function identity<T>(arg: T): T {
  return arg;
}

let output = identity<string>("hello");  // T is string
let num = identity(42);                 // Inferred as number

interface Box<T> {
  value: T;
}
let stringBox: Box<string> = { value: "test" };

8. EnumsNamed constants for sets of values.

typescript

enum Direction {
  Up = 1,
  Down,
  Left,
  Right
}

let move: Direction = Direction.Up;  // 1

9. TuplesFixed-length arrays with typed positions.

typescript

let pair: [string, number] = ["age", 30];
pair[0] = "name";  // OK if string
pair[2] = true;   // Error: Tuple type has only 2 elements

10. Type Narrowing and GuardsNarrow types using checks (typeof, instanceof, in, custom guards).

typescript

function printLength(x: string | number) {
  if (typeof x === "string") {
    console.log(x.length);  // x narrowed to string
  } else {
    console.log(x.toFixed());  // x narrowed to number
  }
}

function isString(val: any): val is string {  // Type predicate
  return typeof val === "string";
}

11. Classes and OOP FeaturesSupport for classes with access modifiers, inheritance, abstract classes.

typescript

class Animal {
  protected name: string;  // Accessible in subclasses
  constructor(name: string) {
    this.name = name;
  }
  move(distance: number = 0) {
    console.log(`${this.name} moved ${distance}m`);
  }
}

class Dog extends Animal {
  bark() {
    console.log("Woof!");
  }
}

const dog = new Dog("Buddy");
dog.move(10);
dog.bark();

12. Utility TypesBuilt-in types for transforming others (e.g., Partial, Required, Pick, Omit, Readonly).

typescript

interface User {
  id: number;
  name: string;
  email?: string;
}

type PartialUser = Partial<User>;  // All properties optional
type RequiredUser = Required<User>;  // All required
type NameOnly = Pick<User, "name">;
type NoId = Omit<User, "id">;
type Frozen = Readonly<User>;  // Can't modify properties

13. Advanced Type Manipulation

  • Conditional Types: Type logic like ternaries.
  • Mapped Types: Transform object keys.
  • Template Literal Types: String patterns.

typescript

type IsString<T> = T extends string ? "yes" : "no";
type Test = IsString<"hello">;  // "yes"

type Flags<T> = { [K in keyof T]: boolean };  // Map to boolean properties

type EventName = `on${"click" | "hover"}`;  // "onclick" | "onhover"

These cover the core of TypeScript. For the latest features (as of 2026), check the official docs for improvements like better inference and stricter checks, but the fundamentals remain the same. Practice by adding types to existing JavaScript code!

Modern Data and event sharing

TechnologyDescriptionUse Cases
WebSocketA protocol providing full-duplex communication channels over a single TCP connection, enabling bidirectional real-time data transfer between client and server.Real-time applications like chat apps, online gaming, collaborative editing, live sports updates, or stock trading platforms where low-latency two-way interaction is needed.
Server-Sent Events (SSE)A standard allowing servers to push updates to the client over a single, long-lived HTTP connection, supporting unidirectional streaming from server to client.Scenarios requiring server-initiated updates like live news feeds, social media notifications, real-time monitoring dashboards, or progress indicators for long-running tasks.
Web WorkersJavaScript scripts that run in background threads separate from the main browser thread, allowing concurrent execution without blocking the UI.Heavy computations such as data processing, image manipulation, complex calculations, or parsing large files in web apps to keep the interface responsive.
Service WorkersScripts that run in the background, acting as a proxy between the web app, browser, and network, enabling features like offline access and caching.Progressive Web Apps (PWAs) for offline functionality, push notifications, background syncing, or intercepting network requests to improve performance and reliability.
Shared WorkersSimilar to Web Workers but can be shared across multiple browser contexts (e.g., tabs or windows) of the same origin, allowing inter-tab communication.Applications needing shared state or communication between multiple instances, like coordinating data across open tabs in a web app or multiplayer games.
Broadcast Channel APIAn API for broadcasting messages between different browsing contexts (tabs, iframes, workers) on the same origin without needing a central hub.Syncing state across multiple tabs, such as updating user preferences or session data in real-time across open windows of the same site.
Long PollingA technique where the client sends a request to the server and keeps it open until new data is available, then responds and repeats, simulating real-time updates.Legacy real-time communication in environments where WebSockets or SSE aren’t supported, like older browsers or simple notification systems.
WebRTCA framework for real-time communication directly between browsers, supporting video, audio, and data channels without intermediaries.Video conferencing, peer-to-peer file sharing, live streaming, or collaborative tools requiring direct browser-to-browser connections.
Web Push APIAn API used with Service Workers to receive and display push notifications from a server, even when the web app is not open.Sending timely updates like news alerts, email notifications, or reminders in web apps to re-engage users.
WebTransportA modern API providing low-level access to bidirectional, multiplexed transport over HTTP/3 or other protocols, for efficient data streaming.High-performance applications needing reliable, ordered delivery or raw datagrams, such as gaming, media streaming, or large file transfers.
Background Sync APIAn extension for Service Workers allowing deferred actions to run in the background when network connectivity is restored.Ensuring data submission or updates in PWAs during intermittent connectivity, like syncing form data or emails offline.

WebSocket

WebSockets provide a persistent, full-duplex communication channel over a single TCP connection, allowing real-time bidirectional data exchange between a client (typically a browser) and a server.

Unlike traditional HTTP requests, which are stateless and require a new connection for each interaction, WebSockets maintain an open connection, enabling low-latency updates without the overhead of repeated handshakes.

How It Works

The process starts with an HTTP upgrade request from the client, including headers like Upgrade: websocket, Sec-WebSocket-Key, and Sec-WebSocket-Version. The server responds with a 101 Switching Protocols status and a Sec-WebSocket-Accept header if it accepts the upgrade.

Once established, data is sent in frames, supporting text (UTF-8) or binary formats. The connection stays open until explicitly closed by either party or due to an error. Events like open, message, close, and error handle the lifecycle. For advanced use, the non-standard WebSocketStream API offers promise-based handling with backpressure to manage data flow and prevent buffering issues.

developer.mozilla.org

Key Features

  • Full-duplex communication for simultaneous sending and receiving.
  • Low latency due to persistent connections.
  • Support for subprotocols (e.g., for custom message formats).
  • Automatic reconnection handling in some libraries.
  • Backpressure management in experimental APIs like WebSocketStream.
  • Broad browser support, but closing connections is recommended to allow browser caching (bfcache).

Use Cases

WebSockets are ideal for applications needing instant updates, such as live chat systems (e.g., Slack), online multiplayer games (e.g., real-time player movements in a browser-based game), collaborative editing tools (e.g., Google Docs), stock trading platforms (e.g., live price feeds), or IoT dashboards (e.g., real-time sensor data).

They shine in scenarios where polling would be inefficient, but for unidirectional server pushes, alternatives like SSE might suffice.

Code Examples

Client-side (JavaScript in Browser):

javascript

const socket = new WebSocket('wss://example.com/chat');

socket.onopen = () => {
  console.log('Connection opened');
  socket.send('Hello, server!');
};

socket.onmessage = (event) => {
  console.log('Received:', event.data);
};

socket.onclose = (event) => {
  console.log('Connection closed', event.reason);
};

socket.onerror = (error) => {
  console.error('Error:', error);
};

// To close: socket.close();

Server-side (Node.js with ‘ws’ library):

javascript

const WebSocket = require('ws');
const wss = new WebSocket.Server({ port: 8080 });

wss.on('connection', (ws) => {
  console.log('Client connected');
  ws.send('Welcome!');

  ws.on('message', (message) => {
    console.log('Received:', message);
    ws.send(`Echo: ${message}`);
  });

  ws.on('close', () => {
    console.log('Client disconnected');
  });
});

This setup creates a simple echo server for chat-like interactions.

Server-Sent Events (SSE)

Server-Sent Events (SSE) allow a server to push updates to a client over a single, persistent HTTP connection, enabling unidirectional real-time streaming from server to browser.

developer.mozilla.org It’s simpler than WebSockets for one-way communication and uses standard HTTP.

How It Works

The client initiates the connection using the EventSource API, specifying a URL that returns text/event-stream content-type. The server keeps the connection open, sending events as plain text lines prefixed with fields like data:, event:, id:, or retry:. Events are delimited by double newlines.

The browser automatically reconnects on drops, with customizable retry intervals. Data is UTF-8 encoded, and comments (starting with : ) can act as keep-alives to prevent timeouts.

Key Features

  • Unidirectional (server to client only).
  • Automatic reconnection with last-event-ID tracking.
  • Support for custom event types.
  • CORS compatibility with proper headers.
  • No client-to-server data sending on the same channel.
  • Works over HTTP/2 for multiplexing.

Use Cases

SSE is used for server-initiated updates like live news tickers (e.g., CNN real-time headlines), social media notifications (e.g., Twitter updates), monitoring dashboards (e.g., server logs or metrics), progress bars for long tasks (e.g., file uploads), or stock price feeds.

It’s not for bidirectional needs, where WebSockets are better.

Code Examples

Client-side (JavaScript):

javascript

const eventSource = new EventSource('/events');

eventSource.onmessage = (event) => {
  console.log('Message:', event.data);
  // Update UI, e.g., append to a list
};

eventSource.addEventListener('ping', (event) => {
  const data = JSON.parse(event.data);
  console.log('Ping:', data.time);
});

eventSource.onerror = (error) => {
  console.error('Error:', error);
};

// Close: eventSource.close();

Server-side (PHP):

php

header('Content-Type: text/event-stream');
header('Cache-Control: no-cache');

while (true) {
  if (connection_aborted()) break;

  $data = json_encode(['time' => date('c')]);
  echo "event: ping\n";
  echo "data: $data\n\n";
  flush();

  sleep(1);
}

This sends periodic pings.

Web Workers

Web Workers run JavaScript in background threads, separate from the main UI thread, to perform heavy computations without freezing the interface.

They enable concurrency in single-threaded JavaScript environments.

How It Works

A worker is created from a separate JS file using new Worker(‘worker.js’). Communication uses postMessage() to send data (copied, not shared) and onmessage to receive it.

Workers can’t access the DOM or window object but can use APIs like fetch() or XMLHttpRequest. They run in a WorkerGlobalScope and can spawn sub-workers.

Key Features

  • Non-blocking UI during intensive tasks.
  • Message-based communication.
  • Restricted access (no DOM manipulation).
  • Network requests support.
  • Types: Dedicated (single script), Shared (multi-context), Service (proxying).

Use Cases

Used for data processing (e.g., sorting large arrays in a spreadsheet app), image manipulation (e.g., filters in a photo editor), complex calculations (e.g., simulations in educational tools), or parsing big files (e.g., JSON in analytics dashboards).

Code Examples

Main Thread:

javascript

const worker = new Worker('worker.js');
worker.postMessage('Process this');
worker.onmessage = (event) => console.log('Result:', event.data);
worker.terminate(); // When done

Worker Script (worker.js):

javascript

self.onmessage = (event) => {
  const result = event.data.toUpperCase(); // Heavy computation here
  self.postMessage(result);
};
``` [](grok_render_citation_card_json={"cardIds":["89a8f2"]})

### Service Workers

Service Workers act as network proxies in the browser, intercepting requests to enable offline access, caching, and background features. [](grok_render_citation_card_json={"cardIds":["13a21b"]}) They run in a separate thread and require HTTPS.

#### How It Works
Registered via `navigator.serviceWorker.register('/sw.js')`, they have a lifecycle: install (cache assets), activate (clean up), and handle events like `fetch` (intercept requests). [](grok_render_citation_card_json={"cardIds":["c5e3e7"]}) Use `caches` API for storage and promises for async ops.

#### Key Features
- Request interception and modification.
- Offline caching.
- Push notifications and background sync.
- Event-driven (install, activate, fetch).
- Secure context only.

#### Use Cases
Progressive Web Apps (PWAs) for offline modes (e.g., Google Maps caching tiles), push alerts (e.g., news apps), API mocking in dev, or prefetching (e.g., gallery images). [](grok_render_citation_card_json={"cardIds":["225d9a"]})

#### Code Examples
**Registration:**
```javascript
if ('serviceWorker' in navigator) {
  navigator.serviceWorker.register('/sw.js').then(reg => console.log('Registered'));
}

Service Worker (sw.js):

javascript

self.addEventListener('install', (event) => {
  event.waitUntil(caches.open('cache-v1').then(cache => cache.addAll(['/'])));
});

self.addEventListener('fetch', (event) => {
  event.respondWith(caches.match(event.request).then(res => res || fetch(event.request)));
});
``` [](grok_render_citation_card_json={"cardIds":["cd320e"]})

### Shared Workers

Shared Workers are web workers accessible by multiple browsing contexts (tabs, iframes) on the same origin, allowing shared state and communication. [](grok_render_citation_card_json={"cardIds":["5c9a28"]})

#### How It Works
Created with `new SharedWorker('worker.js')`, they use `MessagePort` for communication via `port.postMessage()` and `port.onmessage`. [](grok_render_citation_card_json={"cardIds":["5ae880"]}) The worker handles connections with `onconnect`.

#### Key Features
- Shared across contexts.
- Port-based messaging.
- Event-driven connections.
- Terminates when no references remain.

#### Use Cases
Coordinating data across tabs (e.g., shared calculator in multi-window app) or cross-iframe sync (e.g., game state). [](grok_render_citation_card_json={"cardIds":["05d676"]})

#### Code Examples
**Main Script:**
```javascript
const worker = new SharedWorker('worker.js');
worker.port.start();
worker.port.postMessage([2, 3]);
worker.port.onmessage = (e) => console.log('Result:', e.data);

Worker (worker.js):

javascript

onconnect = (e) => {
  const port = e.ports[0];
  port.onmessage = (e) => port.postMessage(e.data[0] * e.data[1]);
};
``` [](grok_render_citation_card_json={"cardIds":["311ec8"]})

### Broadcast Channel API

The Broadcast Channel API allows messaging between browsing contexts and workers on the same origin via a named channel. [](grok_render_citation_card_json={"cardIds":["fb66a0"]})

#### How It Works
Create with `new BroadcastChannel('channel')`, send via `postMessage()`, receive with `onmessage`. [](grok_render_citation_card_json={"cardIds":["9a5f6a"]}) Data is cloned; no direct references needed.

#### Key Features
- Cross-context broadcasting.
- No reference management.
- Structured cloning for complex data.
- Close with `close()`.

#### Use Cases
Syncing state across tabs (e.g., login status) or iframes (e.g., UI updates). [](grok_render_citation_card_json={"cardIds":["b6065d"]})

#### Code Examples
```javascript
const bc = new BroadcastChannel('test');
bc.postMessage('Hello');
bc.onmessage = (e) => console.log('Received:', e.data);
bc.close();
``` [](grok_render_citation_card_json={"cardIds":["b416c8"]})

### Long Polling

Long Polling simulates real-time updates by keeping HTTP requests open until new data arrives, then responding and repeating. [](grok_render_citation_card_json={"cardIds":["26926f"]})

#### How It Works
Client sends request; server holds until data, responds, closes. Client immediately re-requests. [](grok_render_citation_card_json={"cardIds":["116398"]}) Handles errors with retries.

#### Key Features
- No special protocols.
- Low delay for infrequent messages.
- Simple HTTP-based.
- Graceful reconnection.

#### Use Cases
Notifications in legacy systems (e.g., chat with low traffic) or where WebSockets aren't supported. [](grok_render_citation_card_json={"cardIds":["1ef1ee"]})

#### Code Examples
**Client:**
```javascript
async function subscribe() {
  try {
    const res = await fetch('/subscribe');
    if (res.ok) {
      console.log(await res.text());
      subscribe();
    }
  } catch {
    setTimeout(subscribe, 1000);
  }
}
subscribe();

Server (Node.js):

javascript

const http = require('http');
const subscribers = {};
http.createServer((req, res) => {
  if (req.url === '/subscribe') {
    const id = Math.random();
    subscribers[id] = res;
    req.on('close', () => delete subscribers[id]);
  }
}).listen(8080);
``` [](grok_render_citation_card_json={"cardIds":["ba35a2"]})

### WebRTC

WebRTC enables peer-to-peer real-time communication for audio, video, and data without intermediaries. [](grok_render_citation_card_json={"cardIds":["dde31f"]})

#### How It Works
Uses `RTCPeerConnection` for connections, exchanging offers/answers and ICE candidates via signaling. Adds streams (`MediaStream`) or channels (`RTCDataChannel`). [](grok_render_citation_card_json={"cardIds":["817048"]})

#### Key Features
- P2P media and data.
- Encryption (DTLS/SRTP).
- ICE for NAT traversal.
- DTMF for telephony.

#### Use Cases
Video calls (e.g., Zoom-like apps), file sharing, screen sharing, or gaming. [](grok_render_citation_card_json={"cardIds":["cb657c"]})

#### Code Examples
```javascript
const pc = new RTCPeerConnection();
navigator.mediaDevices.getUserMedia({ video: true }).then(stream => {
  stream.getTracks().forEach(track => pc.addTrack(track, stream));
});
pc.ontrack = (e) => document.getElementById('video').srcObject = e.streams[0];
``` [](grok_render_citation_card_json={"cardIds":["98ecb3"]})

### Web Push API

The Web Push API delivers server-pushed notifications via service workers, even when the app isn't open. [](grok_render_citation_card_json={"cardIds":["9dae9d"]})

#### How It Works
Subscribe with `PushManager.subscribe()`, get endpoint and keys. Server sends to endpoint; service worker handles `push` event. [](grok_render_citation_card_json={"cardIds":["499295"]})

#### Key Features
- Background delivery.
- Unique subscriptions.
- Encryption keys.
- `push` and `pushsubscriptionchange` events.

#### Use Cases
News alerts, chat notifications, or e-commerce updates. [](grok_render_citation_card_json={"cardIds":["ba5bc3"]})

#### Code Examples
(Refer to MDN's ServiceWorker Cookbook for full implementations, as direct snippets focus on events like `onpush` in service workers.) [](grok_render_citation_card_json={"cardIds":["9aa2ca"]})

### WebTransport

WebTransport provides low-level access to HTTP/3 for bidirectional streams and datagrams. [](grok_render_citation_card_json={"cardIds":["418178"]})

#### How It Works
Connect with `new WebTransport(url)`, await `ready`. Use streams for reliable data or datagrams for unreliable. [](grok_render_citation_card_json={"cardIds":["7e938a"]})

#### Key Features
- HTTP/3/QUIC-based.
- Bi/uni-directional streams.
- Datagram support.
- Congestion control options.

#### Use Cases
Gaming (low-latency), streaming, or large transfers. [](grok_render_citation_card_json={"cardIds":["2a3c45"]})

#### Code Examples
```javascript
const transport = new WebTransport('https://example.com:443');
await transport.ready;
const stream = await transport.createBidirectionalStream();
``` [](grok_render_citation_card_json={"cardIds":["842db4"]})

### Background Sync API

Background Sync defers tasks in service workers until network is available. [](grok_render_citation_card_json={"cardIds":["6116b3"]})

#### How It Works
Register via `sync.register(tag)`, handle `sync` event in worker when online. [](grok_render_citation_card_json={"cardIds":["a0fa8e"]})

#### Key Features
- Deferred network ops.
- Tag-based tasks.
- `sync` event.

#### Use Cases
Offline email sending or form submissions. [](grok_render_citation_card_json={"cardIds":["8c8823"]})

#### Code Examples
**Registration:**
```javascript
navigator.serviceWorker.ready.then(reg => reg.sync.register('sync-tag'));

Worker:

javascript

self.addEventListener('sync', (event) => {
  if (event.tag === 'sync-tag') event.waitUntil(fetch('/api'));
});
``` [](grok_render_citation_card_json={"cardIds":["ec7a12"]})

What Are Progressive Web Apps (PWAs)?

Progressive Web Apps (PWAs) are web applications that use modern web technologies to deliver an experience similar to native mobile apps. They combine the reach and accessibility of websites with app-like features such as offline functionality, push notifications, and home screen installation.Coined in 2015 by Google engineers, PWAs have become a standard for building fast, reliable, and engaging experiences across devices. As of 2025, they are widely adopted, with the global PWA market projected to grow significantly due to their cost-effectiveness and performance advantages.PWAs load quickly, work offline or on slow networks, and feel immersive—all from a single codebase using HTML, CSS, and JavaScript.

Core Technologies Behind PWAs

PWAs rely on a few key web APIs:

  • Service Workers — Background scripts that act as a proxy between the app and the network. They enable caching for offline access, background syncing, and push notifications.
  • Web App Manifest — A JSON file that provides metadata (name, icons, theme colors, display mode) so the browser can treat the site like an installable app.
  • HTTPS — Required for security, as service workers have powerful capabilities.
  • Other supporting features: Cache API, Push API, Background Sync API.

These allow PWAs to be reliable (load fast/offline), installable (add to home screen), and engaging (push notifications).

Key Features and Benefits (as of 2025)

FeatureDescriptionBenefit
Offline FunctionalityService workers cache assets, allowing use without internet.Users in low-connectivity areas stay engaged; e.g., view cached content.
Fast LoadingInstant loads via caching and optimized delivery.Lower bounce rates, better SEO (Google favors fast sites).
Installable“Add to Home Screen” prompt; launches fullscreen without browser UI.Feels like a native app; no app store needed.
Push NotificationsRe-engage users even when the app isn’t open.Higher retention and conversions.
Cross-PlatformOne codebase works on Android, iOS, desktop.Cheaper development/maintenance than separate native apps.
Responsive DesignAdapts to any screen size.Seamless on phones, tablets, laptops.
Automatic UpdatesUpdates in the background; always current.No manual downloads required.

Pros:

  • Cost-effective (single codebase).
  • Discoverable via search engines.
  • Improved engagement (many brands report 50-100%+ increases).

Cons:

  • Limited access to some native features (e.g., advanced Bluetooth on iOS).
  • iOS support is improving but still lags behind Android.
  • User adoption: Some prefer traditional app stores.

Popular PWA Examples in 2025

Many major brands use PWAs with impressive results:

  • Starbucks → Doubled daily users; customers browse/menu offline and order seamlessly.
  • Twitter/X Lite → Reduced data usage, faster loads; 20% lower bounce rates.
  • Pinterest → 60% increase in core engagements; higher ad revenue.
  • Uber → Works in low-connectivity areas; quick ride requests.
  • AliExpress → 104% increase in conversions for new users.
  • Flipkart → 70% higher conversions; 3x more time spent on site.
  • Tinder → Faster swiping/loading; better engagement.
  • Others: Forbes, Washington Post, Spotify, BMW.

PWAs represent the future of web development in 2025—blurring the line between web and native apps while offering broader reach and lower costs. If you’re building a site or app, starting with PWA principles (like adding a manifest and service worker) is highly recommended. Tools like Google’s Lighthouse can audit your site for PWA readiness.

ES6 Features

Here’s a detailed, practical explanation of every major ES6 (ECMAScript 2015) feature, with real-world examples, common pitfalls, and when to use each one.

1. let and const

  • let: Block-scoped, mutable, no hoisting to the top (Temporal Dead Zone)
  • const: Block-scoped, immutable binding (but the object/array itself can change)
// Good
let count = 0;
const API_URL = "https://api.example.com";

// const objects are mutable
const user = { name: "John" };
user.name = "Jane";     // OK
user.age = 30;          // OK
// user = {}            // TypeError!

// Temporal Dead Zone
console.log(x); // ReferenceError
let x = 10;

Best Practice: Default to const, use let only when reassignment is needed. Never use var.

2. Arrow Functions

  • Concise syntax
  • Lexical this (inherits from surrounding scope)
// Syntax variations
param => expression
(param1, param2) => expression
(param) => { statements }
() => { return value }

// Real-world example: this binding
class Timer {
  start() {
    setInterval(() => {
      console.log(this); // Timer instance, not global!
    }, 1000);
  }
}

// No own arguments object
const fn = (...args) => console.log(arguments); // Error!

When NOT to use arrows:

  • Object methods (breaks this)
  • Prototype methods
  • Functions that need arguments or new

3. Template Literals

const name = "Sara", age = 25;

`Hello ${name}, you are ${age + 1} next year!`

// Tagged templates
function highlight(strings, ...values) {
  return strings.reduce((result, str, i) => 
    `${result}${str}<b>${values[i] || ''}</b>`, '');
}

const result = highlight`Hello ${name}, you have ${age} years`;

4. Destructuring Assignment

// Arrays
const [first, , third] = [1, 2, 3]; // third = 3
const [a, ...rest] = [1, 2, 3, 4];  // rest = [2,3,4]

// Objects
const {name, age, address: {city} = {}} = user;

// Default values
const {settings = {theme: 'dark'}} = config;

// Function parameters
function draw({x, y, color = 'red'}) { ... }

5. Default, Rest, and Spread

// Default parameters
function ajax(url, method = 'GET', data = null) { ... }

// Rest parameter (replaces arguments)
function sum(...numbers) {
  return numbers.reduce((a,b) => a + b, 0);
}

// Spread
Math.max(...[1, 5, 3])           // 5
const arr = [...arr1, 10, ...arr2]
const obj = { ...obj1, age: 30, ...obj2 }  // later properties win

6. Classes (Syntactic Sugar)

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

  static fromJSON(json) {           // static method
    return new this(JSON.parse(json));
  }

  speak() {                         // goes on prototype
    console.log(`${this.name} makes noise`);
  }
}

class Dog extends Animal {
  speak() {
    super.speak();
    console.log("Woof!");
  }
}

// Private fields (ES2022, but often grouped with class features)
class Counter {
  #count = 0;                       // truly private
  increment() { this.#count++; }
}

7. Modules (import / export)

// Named exports
export const PI = 3.14;
export function add(a, b) { return a + b; }

// Default export
export default class Calculator { ... }

// Importing
import Calculator from './calc.js';
import { PI, add } from './math.js';
import * as math from './math.js';
import { add as plus } from './math.js';

8. Promises

// Creation
const p = new Promise((resolve, reject) => {
  setTimeout(() => resolve("Done!"), 1000);
});

// Chaining
fetch('/users')
  .then(res => res.json())
  .then(users => console.log(users))
  .catch(err => console.error(err))
  .finally(() => console.log("Finished"));

// Promise.all, race, allSettled, any
Promise.all([p1, p2, p3]).then(values => ...);

9. Enhanced Object Literals

const name = "API", version = 2;

const api = {
  name,                          // same as name: name
  [`${name}_v${version}`]: true,
  get latest() { return version; },
  fetch() { ... }                // method shorthand
};

10. Symbol

const id = Symbol('id');
const secret = Symbol.for('secret'); // global registry

obj[id] = 123;                     // truly unique key
Symbol.keyFor(secret);             // "secret"

Use case: Adding non-enumerable, collision-safe properties.

11. for…of and Iterables

for (let char of "hello") { ... }
for (let key of map.keys()) { ... }
for (let [key, value] of map.entries()) { ... }

// Custom iterable
class Range {
  constructor(start, end) {
    this.start = start;
    this.end = end;
  }
  [Symbol.iterator]() {
    let i = this.start;
    return {
      next: () => ({
        value: i,
        done: i++ >= this.end
      })
    };
  }
}
for (let n of new Range(1, 5)) console.log(n);

12. Generators

function* generateIds() {
  let i = 1;
  while (true) yield i++;
}

const gen = generateIds();
gen.next().value; // 1, 2, 3...

// Delegation
function* gen2() {
  yield 1;
  yield* [10, 20, 30];   // delegate to array
  yield 4;
}

Great for: infinite sequences, async flows (before async/await), custom iteration.

13. Map and Set

const map = new Map();
map.set(obj, "data");      // object keys OK!
map.set("key", "value");

const set = new Set();
set.add(1).add(2).add(1);  // size === 2

14. WeakMap and WeakSet

  • Keys are weakly referenced → no memory leaks
  • No iteration, no clear(), no size
  • Perfect for private data, caching, DOM metadata
const privateData = new WeakMap();
class User {
  constructor(id) {
    privateData.set(this, {id});
  }
  getId() { return privateData.get(this).id; }
}

15. Proxy

const handler = {
  get(target, prop) {
    if (!(prop in target)) {
      console.warn(`Property ${prop} does not exist`);
    }
    return target[prop];
  },
  set(target, prop, value) {
    if (prop === 'age' && value < 0) {
      throw new Error("Age can't be negative");
    }
    target[prop] = value;
    return true;
  }
};

const proxy = new Proxy({}, handler);

Use cases: validation, logging, virtual properties, revocable references.

16. Reflect API

  • Cleaner alternative to old Object operations
  • Works perfectly with Proxy traps
Reflect.defineProperty(obj, 'name', {value: 'John'});
Reflect.get(target, prop, receiver); // matches Proxy get trap

17–19. New Built-in Methods (Quick List)

// Array
Array.from(document.querySelectorAll('div'));
[1,2,3].find(x => x > 1);           // 2
[1,2,3].includes(2);                // true

// String
"hello".startsWith("he");           // true
"⭐".repeat(5);

// Number & Math
Number.isInteger(42);               // true
Math.trunc(4.9);                    // 4
Math.sign(-5);                      // -1

Bonus: Most Important ES6 Features in Daily Use (2025)

  1. const / let
  2. Arrow functions
  3. Destructuring + rest/spread
  4. Template literals
  5. Classes + extends
  6. import / export
  7. Promises + async/await (await came in ES2017 but built on ES6)
  8. Map / Set / WeakMap

Master these 8 and you’re writing modern JavaScript like a pro.

ES6 (ECMAScript 2015) and TypeScript

Here’s a clear, practical comparison between ES6 (ECMAScript 2015) and TypeScript — what they actually add to JavaScript, and how they differ in purpose and features.

FeatureES6 (Vanilla JavaScript)TypeScript (Superset of JS)Winner / When to Use
Core PurposeModernizes JavaScript syntax and behaviorAdds static typing + modern features on top of JS
Runs directly in browsers?Yes (since ~2016–2017)No → must be compiled to JS (usually ES6 or ES5)ES6
Type SystemNone (dynamic typing only)Full static typing (interfaces, generics, enums, etc.)TypeScript
let / constYesYes (identical)Tie
Arrow FunctionsYesYes (identical)Tie
ClassesYes (syntactic sugar)Yes + access modifiers (public, private, protected) + #private fieldsTypeScript
DestructuringYesYes + type annotations in destructuringTypeScript
Spread / RestYesYes + typed spreadsTypeScript
Template LiteralsYesYesTie
Default / Rest ParametersYesYes + can type each parameterTypeScript
PromisesYesYes + can type what a Promise resolves toTypeScript
async / awaitES2017 (but built on ES6 Promises)Yes + full typing (async function(): Promise<User[]>)TypeScript
Modules (import/export)Yes (native ES modules)Yes + type-only imports/exports, .d.ts filesTypeScript
DecoratorsNo (only experimental stage 2–3 for years)Yes (widely used: @Component, @Injectable, etc.)TypeScript
GenericsNoYes (Array<T>, Promise<T>, custom generics)TypeScript
Interfaces & TypesNoYes (interface User { name: string }, type ID = string | number)TypeScript
EnumsNo (people fake them with objects)Yes (enum Direction { Up, Down })TypeScript
Union & Intersection TypesNoYes (string | number, TypeA & TypeB)TypeScript
Tuple TypesVery limited (no label, no enforcement)Full support [string, number] with labelsTypeScript
Null / Undefined handlingLoose (null == undefined)Strict null checks (huge bug prevention)TypeScript
Parameter PropertiesNoYes
constructor(public name: string) {}
TypeScript
Definite Assignment AssertionNoYes (name!: string)TypeScript
Const AssertionsNoYes (as const → literal types)TypeScript
Mapped & Conditional TypesNoYes (very powerful for type-level programming)TypeScript
Optional Chaining (?.)ES2020Yes (since TS 3.7) + full type narrowingTie (now both)
Nullish Coalescing (??)ES2020Yes + type safeTie
Private Fields (#field)ES2022Yes (via # or private keyword)Tie
Top-level awaitES2022 (only in modules)Yes (in modules)Tie
Project-wide type safetyNoYes (catches bugs at compile time)TypeScript
IDE Autocomplete / RefactoringBasicExcellent (thanks to types)TypeScript
Learning CurveLow to mediumMedium to high (especially advanced types)ES6 easier
Build Step RequiredNo (if targeting modern browsers)Yes (tsc or Babel + plugin)ES6 (no build)

Real-World Summary (2025)

ScenarioBest ChoiceWhy
Small script, learning JS, CodePenPure ES6No build, instant run
Large application, team > 3 peopleTypeScriptFewer runtime bugs, better refactoring
React / Angular / NestJS projectTypeScript (required or strongly recommended)Official support, amazing DX
Library you want everyone to use easilyPublish in JS + provide .d.ts (TypeScript declarations)Users get types without writing TS
Need decorators (@Component, @Injectable)TypeScriptStable decorators
Want maximum type-level magic (Zod, tRPC, etc.)TypeScriptOnly possible here
Full-stack with Node.js + Express/FastifyTypeScriptEnd-to-end type safety (especially with Prisma, tRPC)

Bottom Line

  • ES6 gave us modern JavaScript syntax (arrows, classes, modules, etc.).
  • TypeScript takes ES6 (and newer) syntax + adds a world-class static type system on top.

Today (2025) in professional development:

  • Almost all new enterprise/frontend/backend codebases use TypeScript.
  • You still need to know ES6+ features — TypeScript is 99% just typed JavaScript.

Recommendation:
Learn ES6+ first → then immediately learn TypeScript.
You’ll write ES6-style code every day, but with TypeScript’s safety net.

Share Events and Data Between Micro Frontends (MFEs)


Micro Frontends (MFEs) are an architectural approach where a frontend application is broken down into smaller, independent parts that can be developed, deployed, and maintained separately. Communication between these MFEs is crucial to ensure seamless functionality and user experience. Below are common strategies for enabling communication between MFEs in a React-based application, along with examples:


1. Custom Events (Event Bus)

MFEs can communicate by emitting and listening to custom browser events. This is a loosely coupled approach, allowing MFEs to interact without direct dependencies.

How it works:

  • One MFE dispatches a custom event with data.
  • Other MFEs listen for this event and react to the data.

Example:

// MFE 1: Emitting an event
const sendMessage = (message) => {
  const event = new CustomEvent('mfeMessage', { detail: { message } });
  window.dispatchEvent(event);
};

// Button in MFE 1
<button onClick={() => sendMessage('Hello from MFE 1')}>
  Send Message
</button>

// MFE 2: Listening for the event
useEffect(() => {
  const handleMessage = (event) => {
    console.log('Received in MFE 2:', event.detail.message);
    // Update state or UI based on event.detail.message
  };

  window.addEventListener('mfeMessage', handleMessage);

  return () => {
    window.removeEventListener('mfeMessage', handleMessage);
  };
}, []);

Pros:

  • Decoupled communication.
  • Works across different frameworks (not React-specific).
  • Simple to implement for basic use cases.

Cons:

  • Event names can collide if not namespaced properly.
  • Debugging can be challenging with many events.
  • No strong typing or contract enforcement.

2. Shared State Management (e.g., Redux, Zustand)

A centralized state management library can be shared across MFEs to store and manage shared state.

How it works:

  • A shared state library is exposed globally (e.g., via a window object or a shared module).
  • Each MFE can read from or dispatch actions to update the shared state.

Example with Zustand:

// Shared state module (shared-store.js)
import create from 'zustand';

export const useSharedStore = create((set) => ({
  sharedData: '',
  setSharedData: (data) => set({ sharedData: data }),
}));

// MFE 1: Update shared state
import { useSharedStore } from './shared-store';

const MFE1Component = () => {
  const { setSharedData } = useSharedStore();

  return (
    <button onClick={() => setSharedData('Data from MFE 1')}>
      Update Shared State
    </button>
  );
};

// MFE 2: Read shared state
import { useSharedStore } from './shared-store';

const MFE2Component = () => {
  const { sharedData } = useSharedStore();

  return <div>Received: {sharedData}</div>;
};

Setup:

  • The shared store can be bundled as a separate module and imported by each MFE.
  • Alternatively, expose it via window.sharedStore for MFEs to access.

Pros:

  • Structured and predictable state management.
  • Easy to scale for complex applications.
  • Type-safe with TypeScript.

Cons:

  • Requires additional setup for sharing the store.
  • Tight coupling if the store schema is shared across MFEs.
  • Overhead of maintaining a state management library.

3. Props Passing via a Host Application

A host or shell application can orchestrate communication by passing props to MFEs, treating them as components.

How it works:

  • The host application renders MFEs and passes callbacks or data as props.
  • MFEs communicate by invoking these callbacks, which the host handles.

Example:

// Host App
import MFE1 from 'mfe1/RemoteComponent';
import MFE2 from 'mfe2/RemoteComponent';

const HostApp = () => {
  const [sharedData, setSharedData] = useState('');

  return (
    <div>
      <MFE1 onDataChange={setSharedData} />
      <MFE2 sharedData={sharedData} />
    </div>
  );
};

// MFE 1: Send data via callback
const MFE1Component = ({ onDataChange }) => {
  return (
    <button onClick={() => onDataChange('Data from MFE 1')}>
      Send Data
    </button>
  );
};

// MFE 2: Receive data via props
const MFE2Component = ({ sharedData }) => {
  return <div>Received: {sharedData}</div>;
};

Setup:

  • Use a module federation tool like Webpack Module Federation to load MFEs dynamically.
  • The host app exposes a contract for props that MFEs must adhere to.

Pros:

  • Simple and explicit communication.
  • Leverages React’s component model.
  • Easy to debug and test.

Cons:

  • Tightly couples MFEs to the host’s interface.
  • Less flexible for dynamic or runtime-loaded MFEs.
  • Requires a clear contract for props.

4. URL-based Communication

MFEs can communicate by updating and reading the browser’s URL (e.g., query parameters or hash).

How it works:

  • One MFE updates the URL with data (e.g., query params).
  • Other MFEs listen for URL changes and extract the data.

Example:

// MFE 1: Update URL
import { useHistory } from 'react-router-dom';

const MFE1Component = () => {
  const history = useHistory();

  const sendData = () => {
    history.push({
      pathname: '/mfe1',
      search: `?data=${encodeURIComponent('Hello from MFE 1')}`,
    });
  };

  return <button onClick={sendData}>Send Data via URL</button>;
};

// MFE 2: Read URL
import { useLocation } from 'react-router-dom';

const MFE2Component = () => {
  const location = useLocation();
  const queryParams = new URLSearchParams(location.search);
  const data = queryParams.get('data');

  return <div>Received: {data || 'No data'}</div>;
};

Pros:

  • No direct dependency between MFEs.
  • Persists state in the URL, enabling deep linking.
  • Works well for navigation-driven communication.

Cons:

  • Limited to small amounts of data (URL length restrictions).
  • Requires careful encoding/decoding of data.
  • Can clutter the URL if overused.

5. Window.postMessage

This approach uses the browser’s postMessage API for cross-origin or cross-window communication, ideal for MFEs hosted in iframes or different domains.

How it works:

  • One MFE sends a message to another MFE or the host using window.postMessage.
  • The receiver listens for messages and processes them.

Example:

// MFE 1: Send message
const sendMessage = () => {
  window.postMessage({ type: 'MFE_MESSAGE', payload: 'Hello from MFE 1' }, '*');
};

return <button onClick={sendMessage}>Send Message</button>;

// MFE 2: Receive message
useEffect(() => {
  const handleMessage = (event) => {
    if (event.data.type === 'MFE_MESSAGE') {
      console.log('Received in MFE 2:', event.data.payload);
      // Update state or UI
    }
  };

  window.addEventListener('message', handleMessage);

  return () => {
    window.removeEventListener('message', handleMessage);
  };
}, []);

Pros:

  • Works across different origins (e.g., iframes, different domains).
  • Flexible for complex scenarios.
  • Secure if origin checks are implemented.

Cons:

  • Requires careful origin validation to prevent security issues.
  • More complex than custom events for same-origin MFEs.
  • Message handling can become messy if not structured.

6. Shared Storage (e.g., localStorage, sessionStorage)

MFEs can use browser storage mechanisms like localStorage or sessionStorage to share data.

How it works:

  • One MFE writes data to localStorage.
  • Other MFEs listen for storage events or poll localStorage for updates.

Example:

// MFE 1: Write to localStorage
const sendData = () => {
  localStorage.setItem('mfeData', JSON.stringify({ message: 'Hello from MFE 1' }));
};

// MFE 2: Listen for storage changes
useEffect(() => {
  const handleStorageChange = (event) => {
    if (event.key === 'mfeData') {
      const data = JSON.parse(event.newValue);
      console.log('Received in MFE 2:', data.message);
    }
  };

  window.addEventListener('storage', handleStorageChange);

  return () => {
    window.removeEventListener('storage', handleStorageChange);
  };
}, []);

Pros:

  • Simple to implement.
  • Persists data across page reloads (localStorage).
  • Works across different origins if same storage is accessible.

Cons:

  • Limited storage size (5-10 MB for localStorage).
  • Performance issues with frequent writes/reads.
  • Requires manual serialization/deserialization.

7. Pub/Sub Libraries (e.g., PubSubJS)

Use a lightweight publish-subscribe library like PubSubJS to manage communication between MFEs.

How it works:

  • MFEs publish messages to specific topics.
  • Other MFEs subscribe to these topics to receive messages.

Example:

import PubSub from 'pubsub-js';

// MFE 1: Publish message
const sendMessage = () => {
  PubSub.publish('MFE_MESSAGE', { message: 'Hello from MFE 1' });
};

// MFE 2: Subscribe to message
useEffect(() => {
  const subscription = PubSub.subscribe('MFE_MESSAGE', (msg, data) => {
    console.log('Received in MFE 2:', data.message);
  });

  return () => {
    PubSub.unsubscribe(subscription);
  };
}, []);

Pros:

  • Structured and decoupled.
  • Easy to manage multiple topics.
  • Works across frameworks.

Cons:

  • Adds a dependency to the project.
  • Requires careful topic naming to avoid conflicts.
  • Overhead of managing subscriptions.

Recommendations

  • For simple communication: Use Custom Events or Props Passing for quick, lightweight solutions.
  • For complex state management: Use Shared State Management (e.g., Zustand, Redux) for scalability and structure.
  • For cross-origin scenarios: Use Window.postMessage with proper origin validation.
  • For navigation-driven apps: Use URL-based Communication to leverage browser history.
  • For decoupled systems: Consider Pub/Sub Libraries or Shared Storage for flexibility.

Best Practices

  • Namespace events/topics: Prevent conflicts by using unique prefixes (e.g., mfe1.eventName).
  • Define contracts: Clearly document the data structure for communication to avoid errors.
  • Handle errors gracefully: Add error boundaries and validation for incoming data.
  • Use TypeScript: Enforce types for shared data to improve maintainability.
  • Avoid tight coupling: Prefer loosely coupled methods like events or Pub/Sub over direct prop passing when possible.

Below, I’ll provide detailed step-by-step guides for implementing each of the seven communication methods for Micro Frontends (MFEs) in a React-based application. Each method will include setup instructions, code examples, and considerations for using Webpack Module Federation (a common tool for MFEs). The examples assume you’re using React with Webpack Module Federation for MFE integration, but the communication patterns are adaptable to other setups.


How it works

Prerequisites

  • Node.js and npm/yarn installed.
  • Two or more React MFEs and a host/shell application.
  • Webpack Module Federation configured for loading MFEs.
  • Basic knowledge of React, Webpack, and JavaScript/TypeScript.

Webpack Module Federation Setup (Common for All Methods)

Before diving into communication methods, ensure your MFEs are set up with Webpack Module Federation. Here’s a basic setup for a host and two MFEs:

  1. Host Application (host-app)

Directory: host-app

Install dependencies:

npm init -y
npm install react react-dom webpack webpack-cli webpack-dev-server html-webpack-plugin @module-federation/nextjs-mf --save-dev

Webpack config (host-app/webpack.config.js):

const HtmlWebpackPlugin = require('html-webpack-plugin');
const { ModuleFederationPlugin } = require('webpack').container;

module.exports = {
  mode: 'development',
  entry: './src/index.js',
  output: {
    publicPath: 'http://localhost:3000/',
  },
  devServer: {
    port: 3000,
  },
  module: {
    rules: [
      {
        test: /\.jsx?$/,
        loader: 'babel-loader',
        exclude: /node_modules/,
        options: { presets: ['@babel/preset-react'] },
      },
    ],
  },
  plugins: [
    new HtmlWebpackPlugin({ template: './public/index.html' }),
    new ModuleFederationPlugin({
      name: 'host',
      remotes: {
        mfe1: 'mfe1@http://localhost:3001/remoteEntry.js',
        mfe2: 'mfe2@http://localhost:3002/remoteEntry.js',
      },
      shared: { react: { singleton: true }, 'react-dom': { singleton: true } },
    }),
  ],
};

Entry point (host-app/src/index.js):

import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';

ReactDOM.render(<App />, document.getElementById('root'));

Host app (host-app/src/App.jsx):

import React from 'react';
import MFE1 from 'mfe1/RemoteComponent';
import MFE2 from 'mfe2/RemoteComponent';

const App = () => (
  <div>
    <h1>Host Application</h1>
    <MFE1 />
    <MFE2 />
  </div>
);
export default App;
  1. MFE 1 (mfe1)

Directory: mfe1

Install dependencies (same as host).

Webpack config (mfe1/webpack.config.js):

const HtmlWebpackPlugin = require('html-webpack-plugin');
const { ModuleFederationPlugin } = require('webpack').container;

module.exports = {
  mode: 'development',
  entry: './src/index.js',
  output: {
    publicPath: 'http://localhost:3001/',
  },
  devServer: {
    port: 3001,
  },
  module: {
    rules: [
      {
        test: /\.jsx?$/,
        loader: 'babel-loader',
        exclude: /node_modules/,
        options: { presets: ['@babel/preset-react'] },
      },
    ],
  },
  plugins: [
    new HtmlWebpackPlugin({ template: './public/index.html' }),
    new ModuleFederationPlugin({
      name: 'mfe1',
      filename: 'remoteEntry.js',
      exposes: {
        './RemoteComponent': './src/RemoteComponent',
      },
      shared: { react: { singleton: true }, 'react-dom': { singleton: true } },
    }),
  ],
};

Remote component (mfe1/src/RemoteComponent.jsx):

import React from 'react';

const RemoteComponent = () => <div>MFE 1 Component</div>;
export default RemoteComponent;

MFE 2 (mfe2)

Similar setup to MFE 1, but use port 3002 and expose a different component.

Run the applications:

   # In host-app directory
   npm start
   # In mfe1 directory
   npm start
   # In mfe2 directory
   npm start
  • Host runs on http://localhost:3000, MFE1 on http://localhost:3001, MFE2 on http://localhost:3002.

Now, let’s implement each communication method.


1. Custom Events (Event Bus)

Steps

  1. Define an event name: Use a unique, namespaced event name (e.g., mfe1.message).
  2. Emit event in MFE 1: Dispatch a custom event with data using window.dispatchEvent.
  3. Listen for event in MFE 2: Add an event listener in MFE 2 to handle the event.
  4. Clean up: Remove event listeners on component unmount to prevent memory leaks.
  5. Test: Verify the event is received and data is processed.

Example

  • MFE 1 (mfe1/src/RemoteComponent.jsx):
  import React from 'react';

  const RemoteComponent = () => {
    const sendMessage = (message) => {
      const event = new CustomEvent('mfe1.message', { detail: { message } });
      window.dispatchEvent(event);
    };

    return (
      <div>
        <h2>MFE 1</h2>
        <button onClick={() => sendMessage('Hello from MFE 1')}>
          Send Message
        </button>
      </div>
    );
  };

  export default RemoteComponent;
  • MFE 2 (mfe2/src/RemoteComponent.jsx):
  import React, { useEffect, useState } from 'react';

  const RemoteComponent = () => {
    const [message, setMessage] = useState('');

    useEffect(() => {
      const handleMessage = (event) => {
        setMessage(event.detail.message);
      };

      window.addEventListener('mfe1.message', handleMessage);

      return () => {
        window.removeEventListener('mfe1.message', handleMessage);
      };
    }, []);

    return (
      <div>
        <h2>MFE 2</h2>
        <p>Received: {message || 'No message'}</p>
      </div>
    );
  };

  export default RemoteComponent;
  • Host App (host-app/src/App.jsx):
  • No changes needed; just render MFE1 and MFE2 as shown in the setup.

Considerations

  • Namespace events (e.g., mfe1.message) to avoid conflicts.
  • Use TypeScript for type-safe event data.
  • Avoid overuse, as debugging many events can be complex.

2. Shared State Management (Zustand)

Steps

  1. Create a shared store: Define a Zustand store in a shared module.
  2. Expose the store: Bundle the store as a shared module or attach it to window.
  3. Configure Module Federation: Expose the store from a shared module or one MFE.
  4. Use in MFEs: Import and use the store in MFE1 and MFE2.
  5. Test: Verify state updates propagate across MFEs.

Example

  • Shared Store (shared-store/index.js):
  import create from 'zustand';

  export const useSharedStore = create((set) => ({
    sharedData: '',
    setSharedData: (data) => set({ sharedData: data }),
  }));

MFE 1 Webpack Config (mfe1/webpack.config.js):

Add the shared store as an exposed module:

plugins: [
  new ModuleFederationPlugin({
    name: 'mfe1',
    filename: 'remoteEntry.js',
    exposes: {
      './RemoteComponent': './src/RemoteComponent',
      './SharedStore': '../shared-store/index.js', // Expose shared store
    },
    shared: { react: { singleton: true }, 'react-dom': { singleton: true }, zustand: { singleton: true } },
  }),
],

MFE 1 (mfe1/src/RemoteComponent.jsx):

  import React from 'react';
  import { useSharedStore } from 'mfe1/SharedStore';

  const RemoteComponent = () => {
    const { setSharedData } = useSharedStore();

    return (
      <div>
        <h2>MFE 1</h2>
        <button onClick={() => setSharedData('Data from MFE 1')}>
          Update Shared State
        </button>
      </div>
    );
  };

  export default RemoteComponent;
  • MFE 2 (mfe2/src/RemoteComponent.jsx):
  import React from 'react';
  import { useSharedStore } from 'mfe1/SharedStore';

  const RemoteComponent = () => {
    const { sharedData } = useSharedStore();

    return (
      <div>
        <h2>MFE 2</h2>
        <p>Received: {sharedData || 'No data'}</p>
      </div>
    );
  };

  export default RemoteComponent;

Host App Webpack Config:
Update to include the shared store:

remotes: {
  mfe1: 'mfe1@http://localhost:3001/remoteEntry.js',
  mfe2: 'mfe2@http://localhost:3002/remoteEntry.js',
},
shared: { react: { singleton: true }, 'react-dom': { singleton: true }, zustand: { singleton: true } },

Considerations

  • Use a singleton for the store to avoid multiple instances.
  • Ensure all MFEs share the same version of Zustand.
  • Consider TypeScript for type-safe state management.

3. Props Passing via Host Application

Steps

  1. Define props in Host: Create state and callbacks in the host app.
  2. Pass props to MFEs: Pass data and callbacks to MFE components.
  3. Handle props in MFEs: Use the passed props to send/receive data.
  4. Configure Module Federation: Ensure MFEs are loaded as components.
  5. Test: Verify props are passed and callbacks work.

Example

  • Host App (host-app/src/App.jsx):
  import React, { useState } from 'react';
  import MFE1 from 'mfe1/RemoteComponent';
  import MFE2 from 'mfe2/RemoteComponent';

  const App = () => {
    const [sharedData, setSharedData] = useState('');

    return (
      <div>
        <h1>Host Application</h1>
        <MFE1 onDataChange={setSharedData} />
        <MFE2 sharedData={sharedData} />
      </div>
    );
  };

  export default App;
  • MFE 1 (mfe1/src/RemoteComponent.jsx):
  import React from 'react';

  const RemoteComponent = ({ onDataChange }) => {
    return (
      <div>
        <h2>MFE 1</h2>
        <button onClick={() => onDataChange('Data from MFE 1')}>
          Send Data
        </button>
      </div>
    );
  };

  export default RemoteComponent;
  • MFE 2 (mfe2/src/RemoteComponent.jsx):
  import React from 'react';

  const RemoteComponent = ({ sharedData }) => {
    return (
      <div>
        <h2>MFE 2</h2>
        <p>Received: {sharedData || 'No data'}</p>
      </div>
    );
  };

  export default RemoteComponent;

Considerations

  • Define a clear prop contract to ensure compatibility.
  • Avoid passing complex objects to minimize serialization issues.
  • Suitable for tightly integrated MFEs but less flexible for dynamic loading.

4. URL-based Communication

Steps

  1. Install React Router: Add react-router-dom to all MFEs and the host.
  2. Update URL in MFE 1: Use useHistory to update query parameters.
  3. Read URL in MFE 2: Use useLocation to read query parameters.
  4. Synchronize routing: Ensure the host and MFEs share the same routing context.
  5. Test: Verify URL updates and data extraction.

Example

  • Install Dependencies:
  npm install react-router-dom
  • Host App (host-app/src/App.jsx):
  import React from 'react';
  import { BrowserRouter } from 'react-router-dom';
  import MFE1 from 'mfe1/RemoteComponent';
  import MFE2 from 'mfe2/RemoteComponent';

  const App = () => (
    <BrowserRouter>
      <h1>Host Application</h1>
      <MFE1 />
      <MFE2 />
    </BrowserRouter>
  );

  export default App;
  • MFE 1 (mfe1/src/RemoteComponent.jsx):
  import React from 'react';
  import { useHistory } from 'react-router-dom';

  const RemoteComponent = () => {
    const history = useHistory();

    const sendData = () => {
      history.push({
        pathname: '/mfe1',
        search: `?data=${encodeURIComponent('Hello from MFE 1')}`,
      });
    };

    return (
      <div>
        <h2>MFE 1</h2>
        <button onClick={sendData}>Send Data via URL</button>
      </div>
    );
  };

  export default RemoteComponent;
  • MFE 2 (mfe2/src/RemoteComponent.jsx):
  import React from 'react';
  import { useLocation } from 'react-router-dom';

  const RemoteComponent = () => {
    const location = useLocation();
    const queryParams = new URLSearchParams(location.search);
    const data = queryParams.get('data');

    return (
      <div>
        <h2>MFE 2</h2>
        <p>Received: {data || 'No data'}</p>
      </div>
    );
  };

  export default RemoteComponent;

Webpack Config (Shared Dependencies):

Add react-router-dom to shared dependencies in all Webpack configs:

shared: { react: { singleton: true }, 'react-dom': { singleton: true }, 'react-router-dom': { singleton: true } },

Considerations

  • Use a shared routing context to avoid conflicts.
  • Encode data to prevent URL injection issues.
  • Limit data size due to URL length restrictions.

5. Window.postMessage

Steps

  1. Send message in MFE 1: Use window.postMessage to send data.
  2. Listen for message in MFE 2: Add a message event listener.
  3. Validate origin: Check the message’s origin for security.
  4. Clean up: Remove event listeners on unmount.
  5. Test: Verify messages are sent and received.

Example

  • MFE 1 (mfe1/src/RemoteComponent.jsx):
  import React from 'react';

  const RemoteComponent = () => {
    const sendMessage = () => {
      window.postMessage(
        { type: 'MFE_MESSAGE', payload: 'Hello from MFE 1' },
        'http://localhost:3000' // Host origin
      );
    };

    return (
      <div>
        <h2>MFE 1</h2>
        <button onClick={sendMessage}>Send Message</button>
      </div>
    );
  };

  export default RemoteComponent;
  • MFE 2 (mfe2/src/RemoteComponent.jsx):
  import React, { useEffect, useState } from 'react';

  const RemoteComponent = () => {
    const [message, setMessage] = useState('');

    useEffect(() => {
      const handleMessage = (event) => {
        if (event.origin !== 'http://localhost:3000') return; // Validate origin
        if (event.data.type === 'MFE_MESSAGE') {
          setMessage(event.data.payload);
        }
      };

      window.addEventListener('message', handleMessage);

      return () => {
        window.removeEventListener('message', handleMessage);
      };
    }, []);

    return (
      <div>
        <h2>MFE 2</h2>
        <p>Received: {message || 'No message'}</p>
      </div>
    );
  };

  export default RemoteComponent;

Considerations

  • Always validate the event.origin to prevent security risks.
  • Use a clear message format (e.g., { type, payload }).
  • Suitable for cross-origin or iframe-based MFEs.

6. Shared Storage (localStorage)

Steps

  1. Write to storage in MFE 1: Use localStorage.setItem to store data.
  2. Listen for storage events in MFE 2: Add a storage event listener.
  3. Read storage directly (optional): Poll localStorage if needed.
  4. Clean up: Remove event listeners on unmount.
  5. Test: Verify data is written and read correctly.

Example

  • MFE 1 (mfe1/src/RemoteComponent.jsx):
  import React from 'react';

  const RemoteComponent = () => {
    const sendData = () => {
      localStorage.setItem('mfeData', JSON.stringify({ message: 'Hello from MFE 1' }));
    };

    return (
      <div>
        <h2>MFE 1</h2>
        <button onClick={sendData}>Send Data</button>
      </div>
    );
  };

  export default RemoteComponent;
  • MFE 2 (mfe2/src/RemoteComponent.jsx):
  import React, { useEffect, useState } from 'react';

  const RemoteComponent = () => {
    const [message, setMessage] = useState('');

    useEffect(() => {
      const handleStorageChange = (event) => {
        if (event.key === 'mfeData') {
          const data = JSON.parse(event.newValue);
          setMessage(data.message);
        }
      };

      window.addEventListener('storage', handleStorageChange);

      // Initial read
      const storedData = localStorage.getItem('mfeData');
      if (storedData) {
        setMessage(JSON.parse(storedData).message);
      }

      return () => {
        window.removeEventListener('storage', handleStorageChange);
      };
    }, []);

    return (
      <div>
        <h2>MFE 2</h2>
        <p>Received: {message || 'No data'}</p>
      </div>
    );
  };

  export default RemoteComponent;

Considerations

  • Use sessionStorage for session-specific data.
  • Serialize/deserialize data carefully.
  • Be aware of storage size limits (5-10 MB).

7. Pub/Sub Libraries (PubSubJS)

Steps

  1. Install PubSubJS: Add pubsub-js to all MFEs.
  2. Publish in MFE 1: Use PubSub.publish to send messages.
  3. Subscribe in MFE 2: Use PubSub.subscribe to receive messages.
  4. Share PubSubJS: Configure Module Federation to share the library.
  5. Test: Verify publish/subscribe works across MFEs.

Example

  • Install Dependencies:
  npm install pubsub-js
  • MFE 1 (mfe1/src/RemoteComponent.jsx):
  import React from 'react';
  import PubSub from 'pubsub-js';

  const RemoteComponent = () => {
    const sendMessage = () => {
      PubSub.publish('MFE_MESSAGE', { message: 'Hello from MFE 1' });
    };

    return (
      <div>
        <h2>MFE 1</h2>
        <button onClick={sendMessage}>Send Message</button>
      </div>
    );
  };

  export default RemoteComponent;
  • MFE 2 (mfe2/src/RemoteComponent.jsx):
  import React, { useEffect, useState } from 'react';
  import PubSub from 'pubsub-js';

  const RemoteComponent = () => {
    const [message, setMessage] = useState('');

    useEffect(() => {
      const subscription = PubSub.subscribe('MFE_MESSAGE', (msg, data) => {
        setMessage(data.message);
      });

      return () => {
        PubSub.unsubscribe(subscription);
      };
    }, []);

    return (
      <div>
        <h2>MFE 2</h2>
        <p>Received: {message || 'No message'}</p>
      </div>
    );
  };

  export default RemoteComponent;
  • Webpack Config (Shared Dependencies):
  • Add pubsub-js to shared dependencies:
    javascript shared: { react: { singleton: true }, 'react-dom': { singleton: true }, 'pubsub-js': { singleton: true } },

Considerations

  • Use unique topic names to avoid conflicts.
  • Manage subscriptions to prevent memory leaks.
  • Consider TypeScript for typed messages.

Testing and Verification

For each method:

  1. Run all apps: Start the host (npm start on port 3000), MFE1 (port 3001), and MFE2 (port 3002).
  2. Interact with MFE 1: Trigger the action (e.g., click a button) to send data.
  3. Verify in MFE 2: Check that MFE 2 displays or logs the received data.
  4. Debug: Use browser DevTools to inspect events, storage, or network calls, Add console logs to trace data flow.

Best Practices (Across All Methods)

  • Namespace events/topics: Use prefixes like mfe1. to avoid collisions.
  • Type safety: Use TypeScript to define data contracts.
  • Error handling: Add try-catch blocks and validation for incoming data.
  • Clean up: Remove event listeners/subscriptions on unmount.
  • Module Federation: Share dependencies like React, Zustand, or PubSubJS as singletons to avoid version conflicts.
  • Documentation: Define clear communication contracts for each MFE.

If you need help with a specific method, troubleshooting, or integrating with a different setup (e.g., single-spa, iframe-based MFEs), let me know!

Object -JS

In JavaScript, an Object is a collection of properties, where each property is defined as a key-value pair. Objects are one of the fundamental data types and are used extensively in JavaScript.

Defining Objects

You can create an object in several ways:

  1. Object Literal Syntax:
   const person = {
     name: "John",
     age: 30,
     isEmployed: true
   };
  1. Using the new Object() Syntax:
   const person = new Object();
   person.name = "John";
   person.age = 30;
   person.isEmployed = true;

Accessing Object Properties

  • Dot Notation: Access properties using the dot . syntax.
  console.log(person.name); // "John"
  • Bracket Notation: Access properties using the bracket [] syntax, which is useful when dealing with dynamic property names or keys that are not valid identifiers.
  console.log(person["age"]); // 30

Common Object Methods

JavaScript provides several built-in methods for working with objects. Below are some key methods with examples:

1. Object.keys()

Returns an array of the object’s own enumerable property names (keys).

const person = {
  name: "John",
  age: 30,
  isEmployed: true
};

console.log(Object.keys(person)); // ["name", "age", "isEmployed"]

2. Object.values()

Returns an array of the object’s own enumerable property values.

console.log(Object.values(person)); // ["John", 30, true]

3. Object.entries()

Returns an array of the object’s own enumerable key-value pairs in the form of an array of arrays.

console.log(Object.entries(person));
// [["name", "John"], ["age", 30], ["isEmployed", true]]

4. Object.assign()

Copies the values of all enumerable own properties from one or more source objects to a target object.

const target = { a: 1 };
const source1 = { b: 2 };
const source2 = { c: 3 };

const result = Object.assign(target, source1, source2);
console.log(result); // { a: 1, b: 2, c: 3 }

5. Object.freeze()

Prevents modification (both adding and changing) of an object’s properties.

const frozenObject = Object.freeze({ name: "Alice", age: 25 });
frozenObject.age = 30; // Cannot change; strict mode will throw an error
console.log(frozenObject.age); // 25

6. Object.seal()

Prevents adding or removing properties from an object, but allows modification of existing properties.

const sealedObject = Object.seal({ name: "Alice", age: 25 });
sealedObject.age = 30; // Allowed
delete sealedObject.name; // Not allowed
console.log(sealedObject); // { name: "Alice", age: 30 }

7. Object.create()

Creates a new object, using an existing object as the prototype of the newly created object.

const parent = {
  sayHello() {
    console.log("Hello!");
  }
};

const child = Object.create(parent);
child.sayHello(); // "Hello!"

8. Object.hasOwnProperty()

Returns true if the object has the specified property as its own (not inherited) property.

console.log(person.hasOwnProperty("name")); // true
console.log(person.hasOwnProperty("toString")); // false (inherited from Object prototype)

9. Object.is()

Determines whether two values are the same value (like ===, but handles NaN and -0 cases differently).

console.log(Object.is(25, 25)); // true
console.log(Object.is(NaN, NaN)); // true
console.log(Object.is(-0, +0)); // false

10. Object.getOwnPropertyNames()

Returns an array of all properties (enumerable or not) found directly in a given object.

const obj = Object.create({}, {
  prop1: { value: 42, enumerable: true },
  prop2: { value: 36, enumerable: false }
});

console.log(Object.getOwnPropertyNames(obj)); // ["prop1", "prop2"]

11. Object.defineProperty()

Adds or modifies a property on an object and returns the object.

const person = {};
Object.defineProperty(person, 'name', {
  value: 'John',
  writable: false,  // Cannot be changed
  enumerable: true, // Will show up in Object.keys() or for...in loop
  configurable: false // Cannot be deleted or modified
});

console.log(person.name); // "John"
person.name = 'Doe'; // Won't change
console.log(person.name); // "John"

12. Object.fromEntries()

Creates an object from an array of key-value pairs (the reverse of Object.entries()).

const entries = [
  ['name', 'John'],
  ['age', 30]
];
const person = Object.fromEntries(entries);
console.log(person); // { name: "John", age: 30 }

Example: Combining Object Methods

Here’s a practical example that demonstrates how various object methods work together:

const user = {
  name: "Alice",
  age: 25,
  occupation: "Engineer"
};

// Log object keys
console.log("Keys:", Object.keys(user)); // ["name", "age", "occupation"]

// Log object values
console.log("Values:", Object.values(user)); // ["Alice", 25, "Engineer"]

// Log key-value pairs
console.log("Entries:", Object.entries(user)); // [["name", "Alice"], ["age", 25], ["occupation", "Engineer"]]

// Add new properties by cloning an object
const newUser = Object.assign({}, user, { location: "New York" });
console.log(newUser); // { name: "Alice", age: 25, occupation: "Engineer", location: "New York" }

// Seal the object, preventing adding/removing properties
Object.seal(newUser);
newUser.age = 26; // Allowed
delete newUser.location; // Not allowed (sealed)
console.log(newUser); // { name: "Alice", age: 26, occupation: "Engineer", location: "New York" }

Conclusion

JavaScript objects are powerful and flexible, offering a wide array of methods to create, manipulate, and protect data structures. By mastering these methods, you can handle objects efficiently and use them to structure your application’s data.

Array – JS

Arrays are a fundamental data structure in JavaScript used to store multiple values in a single variable. They come with a variety of built-in methods that allow you to manipulate and interact with the data. Here’s a detailed overview of JavaScript arrays and their important methods:

Creating Arrays

// Using array literal
let fruits = ["Apple", "Banana", "Mango"];

// Using the Array constructor
let numbers = new Array(1, 2, 3, 4, 5);

Accessing and Modifying Arrays

let fruits = ["Apple", "Banana", "Mango"];

// Accessing elements
console.log(fruits[0]); // "Apple"

// Modifying elements
fruits[1] = "Orange";
console.log(fruits); // ["Apple", "Orange", "Mango"]

// Length of array
console.log(fruits.length); // 3

Important Array Methods

  1. Adding and Removing Elements
    • push(): Adds one or more elements to the end of the array and returns the new length.fruits.push("Pineapple");
      console.log(fruits);
      // ["Apple", "Orange", "Mango", "Pineapple"]
    • pop(): Removes the last element from the array and returns that element.
      fruits.pop();
      console.log(fruits);
      // ["Apple", "Orange", "Mango"]
    • unshift(): Adds one or more elements to the beginning of the array and returns the new length.
      fruits.unshift("Strawberry");
      console.log(fruits);
      // ["Strawberry", "Apple", "Orange", "Mango"]
    • shift(): Removes the first element from the array and returns that element.
      fruits.shift();
      console.log(fruits);
      // ["Apple", "Orange", "Mango"]
  2. Combining and Slicing Arrays
    • concat(): Merges two or more arrays and returns a new array.
      let vegetables = ["Carrot", "Broccoli"];
      let allFood = fruits.concat(vegetables);
      console.log(allFood);
      // ["Apple", "Orange", "Mango", "Carrot", "Broccoli"]
    • slice(): Returns a shallow copy of a portion of an array into a new array object.
      let citrus = fruits.slice(1, 3);
      console.log(citrus);
      // ["Orange", "Mango"]
    • splice(): Changes the contents of an array by removing or replacing existing elements and/or adding new elements in place.
      fruits.splice(1, 1, "Grapes");
      console.log(fruits);
      // ["Apple", "Grapes", "Mango"]
  3. Searching and Sorting
    • indexOf(): Returns the first index at which a given element can be found in the array, or -1 if it is not present.
      console.log(fruits.indexOf("Mango"));
      // 2
    • lastIndexOf(): Returns the last index at which a given element can be found in the array, or -1 if it is not present.
      console.log(fruits.lastIndexOf("Grapes"));
      // 1
    • includes(): Determines whether an array includes a certain element, returning true or false.
      console.log(fruits.includes("Apple"));
      // true
    • find(): Returns the value of the first element in the array that satisfies the provided testing function.
      let numbers = [1, 2, 3, 4, 5];
      let found = numbers.find((element) => element > 3);
      console.log(found);
      // 4
    • findIndex(): Returns the index of the first element in the array that satisfies the provided testing function.
      let foundIndex = numbers.findIndex((element) => element > 3); console.log(foundIndex);
      // 3
    • sort(): Sorts the elements of an array in place and returns the sorted array.
      let unsortedNumbers = [3, 1, 4, 1, 5, 9];
      unsortedNumbers.sort((a, b) => a - b);
      console.log(unsortedNumbers);
      // [1, 1, 3, 4, 5, 9]
    • reverse(): Reverses the order of the elements in the array in place.
      unsortedNumbers.reverse();
      console.log(unsortedNumbers);
      // [9, 5, 4, 3, 1, 1]
  4. Iteration Methods
    • forEach(): Executes a provided function once for each array element.
      fruits.forEach((item) => console.log(item));
      // "Apple" // "Grapes" // "Mango"
    • map(): Creates a new array with the results of calling a provided function on every element in the array.
      let lengths = fruits.map((item) => item.length);
      console.log(lengths);
      // [5, 6, 5]
    • filter(): Creates a new array with all elements that pass the test implemented by the provided function.
      let longNames = fruits.filter((item) => item.length > 5); console.log(longNames);
      // ["Grapes"]
    • reduce(): Executes a reducer function (that you provide) on each element of the array, resulting in a single output value.
      let sum = numbers.reduce((accumulator, currentValue) => accumulator + currentValue, 0);
      console.log(sum);
      // 15
    • some(): Tests whether at least one element in the array passes the test implemented by the provided function.
      let hasLargeNumber = numbers.some((element) => element > 4); console.log(hasLargeNumber);
      // true
    • every(): Tests whether all elements in the array pass the test implemented by the provided function.
      let allPositive = numbers.every((element) => element > 0); console.log(allPositive);
      // true
  5. Other Useful Methods
    • join(): Joins all elements of an array into a string.
      let joinedFruits = fruits.join(", ");
      console.log(joinedFruits);
      // "Apple, Grapes, Mango"
    • toString(): Returns a string representing the specified array and its elements.
      console.log(fruits.toString());
      // "Apple,Grapes,Mango"
    • Array.isArray(): Checks if the value is an array.
      console.log(Array.isArray(fruits));
      // true

Conclusion

Arrays are versatile and essential in JavaScript, offering numerous methods to manipulate, iterate, and transform data. Understanding and effectively using these methods can significantly enhance your ability to write efficient and readable JavaScript code.

Promises – JS

In JavaScript, a Promise is an object representing the eventual completion (or failure) of an asynchronous operation and its resulting value. Promises are a powerful way to handle asynchronous operations in a more manageable and readable manner compared to traditional callback functions. Here’s an overview of Promises in JavaScript:

Basic Concepts

1. States of a Promise:

  • Pending: Initial state, neither fulfilled nor rejected.
  • Fulfilled: Operation completed successfully.
  • Rejected: Operation failed.

    2. Creating a Promise:

    let myPromise = new Promise((resolve, reject) => {
      // Asynchronous operation here
      let success = true; // Example condition
      if (success) {
        resolve("Operation was successful!");
      } else {
        reject("Operation failed.");
      }
    });

    3. Consuming a Promise:

    • then(): Invoked when the promise is fulfilled.

    • catch(): Invoked when the promise is rejected.

    • finally(): Invoked when the promise is settled (either fulfilled or rejected).

    myPromise
      .then((value) => {
        console.log(value); // "Operation was successful!"
      })
      .catch((error) => {
        console.error(error); // "Operation failed."
      })
      .finally(() => {
        console.log("Promise is settled.");
      });

    4. Chaining Promises:

      Promises can be chained to handle a sequence of asynchronous operations in a more readable manner.

      let promiseChain = new Promise((resolve, reject) => {
      resolve(1);
      });

      promiseChain
      .then((value) => {
      console.log(value); // 1
      return value * 2;
      })
      .then((value) => {
      console.log(value); // 2
      return value * 3;
      })
      .then((value) => {
      console.log(value); // 6
      })
      .catch((error) => {
      console.error(error);
      });

      Handling Multiple Promises

      1. Promise.all(): Waits for all promises to be fulfilled and returns an array of their results. If any promise is rejected, it returns the reason of the first promise that was rejected.

      let promise1 = Promise.resolve(3);
      let promise2 = 42;
      let promise3 = new Promise((resolve, reject) => {
        setTimeout(resolve, 100, 'foo');
      });
      
      Promise.all([promise1, promise2, promise3]).then((values) => {
        console.log(values); // [3, 42, "foo"]
      });

      2. Promise.race(): Returns the result of the first promise that settles (fulfills or rejects).

      let promise1 = new Promise((resolve, reject) => {
        setTimeout(resolve, 500, 'one');
      });
      
      let promise2 = new Promise((resolve, reject) => {
        setTimeout(resolve, 100, 'two');
      });
      
      Promise.race([promise1, promise2]).then((value) => {
        console.log(value); // "two"
      });

      3. Promise.allSettled(): Waits for all promises to settle (either fulfill or reject) and returns an array of objects describing the outcome of each promise.

      let promise1 = Promise.resolve(3);
      let promise2 = new Promise((resolve, reject) => setTimeout(reject, 100, 'foo'));
      let promise3 = 42;
      
      Promise.allSettled([promise1, promise2, promise3]).then((results) => results.forEach((result) => console.log(result.status)));
      // "fulfilled"
      // "rejected"
      // "fulfilled"

      4. Promise.any(): Returns the result of the first promise that fulfills. If all promises are rejected, it returns an AggregateError.

      let promise1 = Promise.reject(0);
      let promise2 = new Promise((resolve) => setTimeout(resolve, 100, 'quick'));
      let promise3 = new Promise((resolve) => setTimeout(resolve, 500, 'slow'));
      
      Promise.any([promise1, promise2, promise3]).then((value) => {
        console.log(value); // "quick"
      }).catch((error) => {
        console.log(error);
      });

        Async/Await

        Async/await is syntactic sugar built on top of promises, making asynchronous code look and behave more like synchronous code.

        1. Async Functions: Declared with the async keyword.
        2. Await Expressions: Used to pause the execution of an async function until the promise settles.
        async function asyncFunction() {
        try {
        let result1 = await new Promise((resolve) => setTimeout(resolve, 100, 'first'));
        console.log(result1); // "first"
        let result2 = await new Promise((resolve) => setTimeout(resolve, 100, 'second'));
        console.log(result2); // "second"
        } catch (error) {
        console.error(error);
        }
        }

        asyncFunction();

        Error Handling

        Proper error handling in promises is crucial. Use .catch() in promise chains and try...catch blocks in async functions to manage errors.

        // Promise chain
        myPromise
        .then((value) => {
        throw new Error("Something went wrong!");
        })
        .catch((error) => {
        console.error(error); // "Something went wrong!"
        });

        // Async/await
        async function asyncFunction() {
        try {
        let result = await myPromise;
        } catch (error) {
        console.error(error); // Handle the error
        }
        }

        Understanding promises and using them effectively can greatly enhance your ability to handle asynchronous operations in JavaScript. They provide a cleaner and more maintainable way to manage asynchronous code compared to traditional callback patterns.