› Module 10: Modern JavaScript (ES6+) › 10.4 JSON & the fetch API
M O D U L E

Module 10: Modern JavaScript (ES6+)

Features that make JS shorter, safer, and easier to maintain

10.1 Template Literals

Template literals (delimited by backticks `) revolutionized string formatting in JavaScript by supporting inline variable and expression interpolation via ${expression} alongside native multi-line formatting without awkward newline concatenation.

Figure 10.1: Template Literals & Tagged Template Functions Processing Engine Template Input sanitize`Hello ${user}, alert ${cnt}!` AST Decomposition strings: ["Hello ", ", ..."] Static string fragments values: [user, cnt] Evaluated expressions Tagged Output Sanitized & Safe String XSS Escaped!

Figure 10.1: Template literals evaluate expressions inline; tagged templates pass string chunks and evaluated values to a processing function.

1. Interpolation & Expression Evaluation

Inside ${...}, any valid JavaScript expression can be evaluated — including arithmetic, ternary operators, function calls, and nested template literals:

2. Tagged Template Literals (Advanced Production Pattern)

A tagged template is a function call where the template literal is placed immediately following the function name without parentheses: tagFunction`string`. The function receives:

  1. An array of raw string literals (split wherever an expression occurs).
  2. The evaluated values of each ${expression} as remaining arguments.

Tagged templates power leading libraries: React's styled-components, GraphQL query definitions, and SQL query sanitizers preventing SQL injections.

// 1. Multi-line and expression evaluation
const user = { name: "Sam", role: "admin", points: 1250 };

const card = "User: " + user.name.toUpperCase() + "\n" +
  "Status: " + (user.role === "admin" ? "Superuser" : "Standard") + "\n" +
  "Tier: " + (user.points > 1000 ? "Gold" : "Bronze");
console.log(card);

// 2. Custom Tagged Template for XSS Sanitization:
function safeHtml(strings, ...values) {
  return strings.reduce((result, str, i) => {
    const val = values[i] !== undefined ? String(values[i]) : "";
    const sanitizedVal = val
      .replace(/&/g, "&")
      .replace(//g, ">")
      .replace(/"/g, """);
    return result + str + sanitizedVal;
  }, "");
}

const maliciousInput = '<script>alert("XSS Attack!");</script>';
const cleanOutput = safeHtml`<div>Welcome, ${maliciousInput}</div>`;
console.log(cleanOutput);
// Escapes HTML tags safely into <div>Welcome, &lt;script&gt;...</div>

10.2 Modules (import / export)

ES Modules (ESM) are the standardized JavaScript mechanism for splitting large codebases across isolated, maintainable files. Modules prevent global scope pollution, clarify explicit dependencies, and enable modern bundlers (Vite, Webpack, Rollup) to perform dead-code elimination (tree-shaking).

Figure 10.2: ES Module Dependency Graph, Static Analysis & Tree-Shaking app.js (Entry) import { add } from './math.js' Imports only what it needs math.js (Module) export function add() ✓ BUNDLED (Referenced) export function cube() ✗ TREE-SHAKEN (Eliminated!) Production Bundle Optimized JS Dead code pruned Fast load times

Figure 10.2: Static ESM imports allow bundlers to construct an exact dependency graph and eliminate unreferenced exports via tree-shaking.

1. Named vs. Default Exports

  • Named Exports: Can have multiple per file. Must be imported using exact matching names enclosed in curly braces import { add, PI } (or aliased with as).
  • Default Export: Only one permitted per file (export default function ...). Can be imported with any arbitrary identifier without braces (import multiply from ...).

2. Dynamic Imports for Route Lazy-Loading

Standard import statements must reside at the top level of a file. When code should only load conditionally (such as opening a modal or switching routes), dynamic import (import('./heavyModule.js')) returns a Promise, enabling seamless code-splitting.

3. ESM vs. CommonJS: The Vital Differences

  1. Static vs. Dynamic: ESM is parsed and validated statically at compile time; CommonJS (require()) runs dynamically at runtime.
  2. Live Bindings: ESM named exports are live read-only references; CommonJS copies primitive values upon export.
  3. Environment: Modules always execute in 'use strict' mode, and top-level this is undefined (not global or window).
// File: utils/math.js
export const PI = 3.14159;

export function add(a, b) {
  return a + b;
}

// Default export
export default function multiply(a, b) {
  return a * b;
}

// File: main.js
import multiply, { add, PI as CirclePI } from "./utils/math.js";

console.log(add(2, 3));         // 5
console.log(multiply(4, 5));    // 20
console.log(CirclePI);          // 3.14159

// Dynamic import() for Lazy Loading:
async function loadChartLibrary() {
  const { renderChart } = await import("./heavyChart.js");
  renderChart("#chart-container");
}

10.3 Default & Rest Parameters

ES6 introduced native language support for default parameter values and rest parameters, eliminating brittle defensive idioms like param = param || defaultValue and clumsy manipulations of the legacy arguments object.

Figure 10.3: Parameter Resolution Order, Default Fallbacks & Rest Array Gathering Function Call sendReport( "Sales", undefined, 10, 20, 30) Parameter Resolution Engine title = "Sales" Explicit arg used "Sales" format = "PDF" undefined passed → Defaults to "PDF" ...metrics Gathers into Array [10, 20, 30]

Figure 10.3: Explicit arguments override defaults; undefined activates default expressions; rest gathers unbounded tail arguments into a true Array.

1. Default Parameters: Call-Time Evaluation & undefined

Default parameters in JavaScript evaluate at call time, not function definition time. Each time the function runs without that argument, the default expression re-evaluates fresh.

Crucial Rule: Only passing undefined (or omitting the argument) triggers default assignment. Passing null, false, 0, or "" does NOT activate the default value because they are valid defined values!

2. Rest Parameters vs. The arguments Object

  • True Array: Rest parameters (...args) produce a genuine JavaScript Array instance, providing direct access to .map(), .filter(), .reduce(), and .forEach().
  • Arrow Function Support: Arrow functions do NOT have their own arguments object. Rest parameters are the standard, universal way to capture variadic arguments.
  • Position Constraint: The rest parameter must be the last parameter in the function definition (e.g. (a, ...rest) is valid; (...rest, b) causes a SyntaxError).
// 1. Dynamic default parameter evaluation
function logEvent(event, timestamp = new Date().toISOString()) {
  console.log("[" + timestamp + "] " + event);
}

logEvent("User Login"); // Generates fresh timestamp at call time!

// 2. undefined vs null distinction (Interview favorite!):
function configure(timeout = 5000) {
  return timeout;
}
console.log(configure(undefined)); // 5000 (Default activated)
console.log(configure(null));      // null (null is preserved!)

// 3. Rest Parameters with Array transformations:
function calculateTotal(taxRate, discount = 0, ...prices) {
  const subtotal = prices.reduce((sum, p) => sum + p, 0);
  const total = (subtotal - discount) * (1 + taxRate);
  return total.toFixed(2);
}

console.log(calculateTotal(0.08, 5, 20, 50, 30)); // Tax: 8%, Discount: $5, Items: [20, 50, 30]

10.4 JSON & the fetch API

JSON (JavaScript Object Notation) is the universal, language-agnostic text serialization format for transmitting structured data across the web. The browser's native Fetch API provides a modern, Promise-based interface for executing asynchronous HTTP requests over the network without third-party dependencies.

Figure 10.4: Fetch API Two-Stage Promise Lifecycle & Stream Resolution 1. fetch(url, options) method: 'POST' body: JSON.stringify() Returns Promise<Response> 2. Response Headers Arrive res.status: 200 OK res.ok: true First Promise Resolves (Body is still streaming!) 3. await res.json() Streams body text Parses JSON to Object Second Promise Resolves Full JavaScript Data Ready

Figure 10.4: Fetch executes in two asynchronous phases: receiving response headers (Promise 1), followed by reading and deserializing the body stream (Promise 2).

1. The Golden Rule of fetch() Error Handling

A primary technical screening question: fetch() does NOT reject on HTTP 404 or 500 status codes. The Promise returned by fetch() only rejects if the request fails due to network disconnection, DNS failure, or CORS security blocks. To detect server error status codes, developers must explicitly check if (!response.ok) (which tests if status is in the 200–299 range).

2. JSON Serialization & Parsing

  • JSON.stringify(data, replacer, space): Converts in-memory JavaScript objects into a JSON text string. Keys must be double-quoted. Note: functions, undefined, and Symbols are automatically omitted!
  • JSON.parse(text, reviver): Deserializes a valid JSON string back into a live JavaScript object graph.

3. Request Cancellation with AbortController

Modern production apps should never allow network requests to hang indefinitely. Using AbortController or AbortSignal.timeout(ms) (ES2022) enables automatic request cancellation if the server fails to respond within a timeout window.

// Production-Grade Fetch with Error Handling & Timeout
async function postUserData(endpoint, payload) {
  // 1. Configure AbortController timeout (5 seconds)
  const controller = new AbortController();
  const timeoutId = setTimeout(() => controller.abort(), 5000);

  try {
    const response = await fetch(endpoint, {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
        "Accept": "application/json"
      },
      body: JSON.stringify(payload), // Object -> JSON String
      signal: controller.signal
    });

    clearTimeout(timeoutId);

    // 2. Mandatory HTTP status validation:
    if (!response.ok) {
      throw new Error("Server returned HTTP " + response.status + ": " + response.statusText);
    }

    // 3. Second Promise: Read and deserialize JSON stream:
    const data = await response.json();
    return data;
  } catch (err) {
    if (err.name === "AbortError") {
      console.error("Network request timed out after 5000ms!");
    } else {
      console.error("Fetch failure:", err.message);
    }
    throw err;
  }
}

// Example invocation:
postUserData("https://api.readytogo.dev/users", { name: "Satish", role: "Dev" })
  .then(res => console.log("Success:", res))
  .catch(err => console.log("Handled in UI:", err.message));
MODULE COMPREHENSIVE QUIZ

Module 10 Final Quiz

Test your understanding across all 4 topics of this module with interview-grade questions, tricky edge cases, and code output predictions.

Take Module 10 Final Quiz →
Yukti Mascot