Skip to content
Journey to Code

JavaScript Evolution: From Scripting Language to Modern Development Platform

Enhancing JavaScript's Async Capabilities

How ES7 and ES8 Improved the Web

· By · Updated · 5 min read

Cover reading "Enhancing JavaScript's Async Capabilities" with async/await code and an edition rail marking ES2016 and ES2017

JavaScript continues to evolve at a remarkable pace, bringing developers more powerful and elegant ways to write code. While ES6 (ECMAScript 2015) changed the language with arrow functions, classes, and promises, the subsequent releases—ES7 (2016) and ES8 (2017)—refined and enhanced these foundations with features that significantly improved how we handle asynchronous operations and manipulate data.

Code that waits for two things, like two network requests, used to need nested callbacks or a chain of .then() calls. With ES8 it reads top to bottom, and with one extra function the two waits overlap, so two 1-second jobs finish in about 1 second instead of 2.

In this article, we are going to measure that ourselves, after a look at the small ES2016 additions.

Quick recap if you are new here: a promise is an object standing for a result that has not arrived yet (ES6 added them), and a callback is a function you hand over to be called when the result arrives. ES8's async/await is a friendlier way to use promises.

The New Release Cycle: A More Agile JavaScript#

Before diving into the features, it's worth understanding how JavaScript's evolution changed. After the massive ES6 update, TC39 (the committee overseeing JavaScript's development) shifted to yearly, incremental releases. Each feature now progresses through four stages before becoming part of the standard:

  1. Stage 1: Proposal and discussion

  2. Stage 2: Draft specification

  3. Stage 3: Implementation and feedback

  4. Stage 4: Ready for inclusion

This approach allows for more agile language development, benefiting developers with regular, manageable improvements rather than waiting years for massive updates.

ES7/ES2016: Small but Mighty Updates#

ES7 might seem underwhelming at first glance, with just two notable additions—but don't be fooled. These seemingly minor features significantly improved everyday JavaScript coding.

Array.prototype.includes()#

Before ES7, checking if an array contained a specific value required using indexOf():

const myArray = ["a", "b", "c"];
const value = "b";

// Pre-ES7
if (myArray.indexOf(value) !== -1) {
  console.log("found"); // found
}

With includes(), the code becomes more intuitive:

// ES7
if (myArray.includes(value)) {
  console.log("found"); // found
}

One more difference: includes() can find NaN, while indexOf() cannot.

console.log([NaN].indexOf(NaN));  // -1 (not found)
console.log([NaN].includes(NaN)); // true
What does [NaN].indexOf(NaN) return?
includes() vs. indexOf() Comparison
---html
<h1>Array.includes() vs. indexOf()</h1>
<p>ES7 introduced the <code>includes()</code> method, which provides a more intuitive way to check if an array contains a specific value compared to the traditional <code>indexOf()</code> method.</p>

<div class="container">
	<div class="card">
		<h2>Test Array</h2>
		<div class="input-group">
			<label for="arrayInput">Enter array elements (comma-separated):</label>
			<input type="text" id="arrayInput" value="1, 2, 3, hello, world, NaN">
		</div>
		<div class="input-group">
			<label for="searchValue">Value to search for:</label>
			<input type="text" id="searchValue" placeholder="Enter a value">
		</div>
		<button id="checkButton">Check Both Methods</button>
	</div>

	<div class="card">
		<h2>Results</h2>
		<div class="results" id="results">
			<p>Enter a value and click "Check Both Methods" to see the comparison.</p>
		</div>
	</div>
</div>

<div class="container">
	<div class="card">
		<h2>Performance Test</h2>
		<p>Run a performance comparison between both methods:</p>
		<div class="input-group">
			<label for="arraySize">Array size:</label>
			<input type="number" id="arraySize" value="10000" min="1000" max="1000000">
		</div>
		<div class="input-group">
			<label for="iterations">Iterations:</label>
			<input type="number" id="iterations" value="1000" min="100" max="10000">
		</div>
		<button id="performanceButton">Run Performance Test</button>
	</div>

	<div class="card">
		<h2>Special Case: NaN</h2>
		<p><code>indexOf()</code> can't find <code>NaN</code>, but <code>includes()</code> can.</p>
		<button id="nanButton">Test NaN Handling</button>
		<div class="results" id="nanResults"></div>
	</div>
</div>
---css
/* Dark mode awareness */
:root {
	--bg-color: #fff;
	--text-color: #333;
	--card-bg: #f0f0f0;
	--highlight: #4a90e2;
	--success: #2ecc71;
	--error: #e74c3c;
	--border: #ddd;
}

@media (prefers-color-scheme: dark) {
	:root {
		--bg-color: #1a1a1a;
		--text-color: #e0e0e0;
		--card-bg: #2a2a2a;
		--highlight: #5a9cf0;
		--success: #3ee887;
		--error: #ff6b6b;
		--border: #444;
	}
}

body {
	font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica,
		Arial, sans-serif;
	background-color: var(--bg-color);
	color: var(--text-color);
	max-width: 800px;
	margin: 0 auto;
	padding: 20px;
	transition: background-color 0.3s, color 0.3s;
}

h1,
h2 {
	color: var(--highlight);
}

.container {
	display: grid;
	grid-template-columns: 1fr 1fr;
	gap: 20px;
	margin-top: 20px;
}

.card {
	background-color: var(--card-bg);
	border-radius: 8px;
	padding: 20px;
	box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}

.input-group {
	margin-bottom: 15px;
}

label {
	display: block;
	margin-bottom: 5px;
}

input,
button {
	padding: 8px 12px;
	border-radius: 4px;
	border: 1px solid var(--border);
	background-color: var(--bg-color);
	color: var(--text-color);
	width: 100%;
	box-sizing: border-box;
}

button {
	background-color: var(--highlight);
	color: white;
	border: none;
	cursor: pointer;
	margin-top: 10px;
	transition: opacity 0.2s;
}

button:hover {
	opacity: 0.9;
}

.results {
	margin-top: 20px;
	border-top: 1px solid var(--border);
	padding-top: 15px;
}

.result-item {
	padding: 8px;
	margin-bottom: 5px;
	border-radius: 4px;
}

.success {
	background-color: var(--success);
	color: white;
}

.error {
	background-color: var(--error);
	color: white;
}

.performance {
	font-weight: bold;
	margin-top: 10px;
}

code {
	background-color: rgba(0, 0, 0, 0.1);
	padding: 2px 4px;
	border-radius: 3px;
	font-family: monospace;
}

@media (max-width: 600px) {
	.container {
		grid-template-columns: 1fr;
	}
}

.toggle-mode {
	position: absolute;
	top: 20px;
	right: 20px;
	background: none;
	border: 2px solid var(--highlight);
	color: var(--highlight);
	width: auto;
	font-size: 14px;
}
---js
// Check for system preference and set initial mode
if (
window.matchMedia &&
window.matchMedia("(prefers-color-scheme: dark)").matches)
{
  document.documentElement.setAttribute("data-theme", "dark");
}

// Main functionality
const arrayInput = document.getElementById("arrayInput");
const searchValue = document.getElementById("searchValue");
const checkButton = document.getElementById("checkButton");
const results = document.getElementById("results");

// Parse array input
function parseArray(input) {
  const array = input.split(",").map(item => {
    item = item.trim();
    // Handle special values
    if (item === "NaN") return NaN;
    if (item === "null") return null;
    if (item === "undefined") return undefined;
    if (item === "true") return true;
    if (item === "false") return false;

    // Try to convert to number if possible
    const num = Number(item);
    return isNaN(num) ? item : num;
  });
  return array;
}

// Check value with both methods
checkButton.addEventListener("click", () => {
  const array = parseArray(arrayInput.value);
  let value = searchValue.value.trim();

  // Handle special values in search input
  if (value === "NaN") value = NaN;else
  if (value === "null") value = null;else
  if (value === "undefined") value = undefined;else
  if (value === "true") value = true;else
  if (value === "false") value = false;else
  {
    // Try to convert to number if possible
    const num = Number(value);
    if (!isNaN(num)) value = num;
  }

  const indexOfResult = array.indexOf(value) !== -1;
  const includesResult = array.includes(value);

  // Time both methods (simple example, not scientific)
  const startIndexOf = performance.now();
  for (let i = 0; i < 10000; i++) {
    array.indexOf(value) !== -1;
  }
  const endIndexOf = performance.now();

  const startIncludes = performance.now();
  for (let i = 0; i < 10000; i++) {
    array.includes(value);
  }
  const endIncludes = performance.now();

  results.innerHTML = `
        <div class="result-item ${indexOfResult ? "success" : "error"}">
          <strong>indexOf:</strong> ${
  indexOfResult ? "Found" : "Not found"
  } (index: ${array.indexOf(value)})
        </div>
        <div class="result-item ${includesResult ? "success" : "error"}">
          <strong>includes:</strong> ${includesResult ? "Found" : "Not found"}
        </div>
        <div class="performance">
          <p>indexOf time: ${(endIndexOf - startIndexOf).toFixed(2)}ms</p>
          <p>includes time: ${(endIncludes - startIncludes).toFixed(2)}ms</p>
        </div>
        ${
  indexOfResult !== includesResult ?
  "<p><strong>Note:</strong> Results differ! This often happens with <code>NaN</code> values.</p>" :
  ""
  }
      `;
});

// Performance test
const performanceButton = document.getElementById("performanceButton");
const arraySize = document.getElementById("arraySize");
const iterations = document.getElementById("iterations");

performanceButton.addEventListener("click", () => {
  const size = parseInt(arraySize.value);
  const iters = parseInt(iterations.value);

  // Create large array
  const largeArray = [];
  for (let i = 0; i < size; i++) {
    largeArray.push(i);
  }

  // Value that doesn't exist (worst case)
const searchVal = size + 1;

  // Test indexOf
  const startIndexOf = performance.now();
  for (let i = 0; i < iters; i++) {
    largeArray.indexOf(searchVal) !== -1;
  }
  const endIndexOf = performance.now();

  // Test includes
  const startIncludes = performance.now();
  for (let i = 0; i < iters; i++) {
    largeArray.includes(searchVal);
  }
  const endIncludes = performance.now();

  const indexOfTime = endIndexOf - startIndexOf;
  const includesTime = endIncludes - startIncludes;

  results.innerHTML = `
        <h3>Performance Test Results</h3>
        <p>Array size: ${size.toLocaleString()}, Iterations: ${iters.toLocaleString()}</p>
        <div class="performance">
          <p>indexOf time: ${indexOfTime.toFixed(2)}ms</p>
          <p>includes time: ${includesTime.toFixed(2)}ms</p>
          <p>Difference: ${Math.abs(indexOfTime - includesTime).toFixed(
  2)
  }ms</p>
          <p>${
  indexOfTime < includesTime ?
  "indexOf was faster" :
  "includes was faster"
  } by ${Math.abs((indexOfTime / includesTime - 1) * 100).toFixed(
  2)
  }%</p>
        </div>
      `;
});

// NaN handling test
const nanButton = document.getElementById("nanButton");
const nanResults = document.getElementById("nanResults");

nanButton.addEventListener("click", () => {
  const arrayWithNaN = [1, 2, NaN, 4, 5];

  const indexOfResult = arrayWithNaN.indexOf(NaN) !== -1;
  const includesResult = arrayWithNaN.includes(NaN);

  nanResults.innerHTML = `
        <div class="result-item ${indexOfResult ? "success" : "error"}">
          <strong>indexOf(NaN):</strong> ${
  indexOfResult ? "Found" : "Not found"
  } (index: ${arrayWithNaN.indexOf(NaN)})
        </div>
        <div class="result-item ${includesResult ? "success" : "error"}">
          <strong>includes(NaN):</strong> ${
  includesResult ? "Found" : "Not found"
  }
        </div>
        <p><strong>Why?</strong> <code>indexOf</code> uses strict equality (===) which always returns <code>false</code> for <code>NaN === NaN</code>. <code>includes()</code> uses a different algorithm that can correctly identify <code>NaN</code> values.</p>
      `;
});

Exponentiation Operator (**)#

ES7 introduced a dedicated operator for exponential calculations:

// Pre-ES7
const oldResult = Math.pow(2, 10);
console.log(oldResult); // 1024

// ES7
const result = 2 ** 10;
console.log(result); // 1024

// With assignment
let power = 2;
power **= 10;
console.log(power); // 1024

This isn't just a shortcut (what developers call syntactic sugar)—it's more readable and integrates better with JavaScript's other operators.

Exponentiation Operator Demo
---html
<h1>Exponentiation Operator (**)</h1>
<p>ES7 introduced a dedicated operator for exponential calculations, replacing the more verbose <code>Math.pow()</code> function.</p>

<div class="container">
	<div class="card">
		<h2>Interactive Calculator</h2>
		<div class="slider-container">
			<label for="baseSlider">Base value:</label>
			<div class="slider-with-value">
				<input type="range" id="baseSlider" min="1" max="20" value="2" step="0.1">
				<span class="slider-value" id="baseValue">2</span>
			</div>
		</div>

		<div class="slider-container">
			<label for="exponentSlider">Exponent value:</label>
			<div class="slider-with-value">
				<input type="range" id="exponentSlider" min="1" max="10" value="3" step="0.1">
				<span class="slider-value" id="exponentValue">3</span>
			</div>
		</div>

		<div class="result" id="result">Result: 8</div>

		<div class="code-display" id="codeDisplay">
			const result = 2 ** 3;
			// Equivalent to: Math.pow(2, 3);</div>
	</div>

	<div class="card">
		<h2>Performance Comparison</h2>
		<p>Compare the performance of the exponentiation operator (**) with Math.pow()</p>

		<div class="slider-container">
			<label for="iterationsSlider">Test iterations:</label>
			<div class="slider-with-value">
				<input type="range" id="iterationsSlider" min="10000" max="10000000" value="1000000" step="10000">
				<span class="slider-value" id="iterationsValue">1,000,000</span>
			</div>
		</div>

		<button id="runTestButton">Run Performance Test</button>

		<div id="performanceChart">
			<div class="chart-grid" style="bottom: 25%;"></div>
			<div class="chart-grid" style="bottom: 50%;"></div>
			<div class="chart-grid" style="bottom: 75%;"></div>

			<div class="chart-bar chart-bar-mathpow" id="mathPowBar"></div>
			<div class="chart-value chart-value-mathpow" id="mathPowValue">0ms</div>
			<div class="chart-label chart-label-mathpow">Math.pow()</div>

			<div class="chart-bar chart-bar-operator" id="operatorBar"></div>
			<div class="chart-value chart-value-operator" id="operatorValue">0ms</div>
			<div class="chart-label chart-label-operator">** Operator</div>
		</div>
	</div>
</div>

<div class="container">
	<div class="card">
		<h2>Assignment Operator (**=)</h2>
		<p>Like other operators, the exponentiation operator has an assignment version:</p>

		<div class="code-display">
			let value = 2;
			value **= 3;
			// value is now 8</div>

		<div class="slider-container">
			<label for="assignBaseSlider">Starting value:</label>
			<div class="slider-with-value">
				<input type="range" id="assignBaseSlider" min="1" max="10" value="2" step="0.1">
				<span class="slider-value" id="assignBaseValue">2</span>
			</div>
		</div>

		<button id="applyExponentButton">Apply Exponent</button>

		<div class="result" id="assignmentResult">Current value: 2</div>
		<div id="assignmentCode" class="code-display">// Click "Apply Exponent" to see the code</div>
	</div>

	<div class="card">
		<h2>Real-World Applications</h2>
		<p>The exponentiation operator is useful for:</p>
		<ul>
			<li>Scientific calculations (area, volume, growth)</li>
			<li>Financial calculations (compound interest)</li>
			<li>Graphics and animations (scaling, zooming)</li>
			<li>Algorithm implementations (binary exponentiation)</li>
		</ul>

		<h3>Compound Interest Example</h3>
		<div class="slider-container">
			<label for="principalSlider">Principal ($):</label>
			<div class="slider-with-value">
				<input type="range" id="principalSlider" min="1000" max="10000" value="5000" step="100">
				<span class="slider-value" id="principalValue">$5,000</span>
			</div>
		</div>

		<div class="slider-container">
			<label for="rateSlider">Annual Interest Rate (%):</label>
			<div class="slider-with-value">
				<input type="range" id="rateSlider" min="1" max="10" value="5" step="0.1">
				<span class="slider-value" id="rateValue">5%</span>
			</div>
		</div>

		<div class="slider-container">
			<label for="yearsSlider">Years:</label>
			<div class="slider-with-value">
				<input type="range" id="yearsSlider" min="1" max="30" value="10" step="1">
				<span class="slider-value" id="yearsValue">10</span>
			</div>
		</div>

		<div class="result" id="interestResult">Future Value: $8,144.47</div>
		<div class="code-display" id="interestCode">
			const futureValue = 5000 * (1 + 0.05) ** 10;
			// Equivalent to: 5000 * Math.pow(1 + 0.05, 10);</div>
	</div>
</div>
---css
/* Dark mode awareness */
:root {
	--bg-color: #fff;
	--text-color: #333;
	--card-bg: #f0f0f0;
	--highlight: #4a90e2;
	--secondary: #9b59b6;
	--success: #2ecc71;
	--border: #ddd;
	--slider-bg: #d1d1d1;
	--chart-grid: #ccc;
	--chart-bar1: rgba(74, 144, 226, 0.7);
	--chart-bar2: rgba(155, 89, 182, 0.7);
}

@media (prefers-color-scheme: dark) {
	:root {
		--bg-color: #1a1a1a;
		--text-color: #e0e0e0;
		--card-bg: #2a2a2a;
		--highlight: #5a9cf0;
		--secondary: #bb6aef;
		--success: #3ee887;
		--border: #444;
		--slider-bg: #444;
		--chart-grid: #555;
		--chart-bar1: rgba(90, 156, 240, 0.8);
		--chart-bar2: rgba(187, 106, 239, 0.8);
	}
}

body {
	font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica,
		Arial, sans-serif;
	background-color: var(--bg-color);
	color: var(--text-color);
	max-width: 800px;
	margin: 0 auto;
	padding: 20px;
	transition: background-color 0.3s, color 0.3s;
}

h1,
h2 {
	color: var(--highlight);
}

.container {
	display: grid;
	grid-template-columns: 1fr 1fr;
	gap: 20px;
	margin-top: 20px;
}

.card {
	background-color: var(--card-bg);
	border-radius: 8px;
	padding: 20px;
	box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}

.slider-container {
	margin-bottom: 20px;
}

label {
	display: block;
	margin-bottom: 5px;
}

.slider-with-value {
	display: flex;
	align-items: center;
	gap: 10px;
}

.slider-value {
	min-width: 40px;
	text-align: center;
	font-weight: bold;
}

input[type="range"] {
	flex-grow: 1;
	height: 10px;
	-webkit-appearance: none;
	background: var(--slider-bg);
	border-radius: 5px;
	outline: none;
}

input[type="range"]::-webkit-slider-thumb {
	-webkit-appearance: none;
	width: 20px;
	height: 20px;
	border-radius: 50%;
	background: var(--highlight);
	cursor: pointer;
}

input[type="range"]::-moz-range-thumb {
	width: 20px;
	height: 20px;
	border-radius: 50%;
	background: var(--highlight);
	cursor: pointer;
	border: none;
}

.result {
	font-size: 24px;
	font-weight: bold;
	margin: 20px 0;
	padding: 10px;
	border-radius: 4px;
	background-color: rgba(0, 0, 0, 0.05);
	text-align: center;
}

.code-display {
	background-color: rgba(0, 0, 0, 0.1);
	padding: 10px;
	border-radius: 4px;
	margin-bottom: 10px;
	font-family: monospace;
	white-space: pre;
}

button {
	background-color: var(--highlight);
	color: white;
	border: none;
	border-radius: 4px;
	padding: 8px 12px;
	cursor: pointer;
	margin-top: 10px;
	transition: opacity 0.2s;
}

button:hover {
	opacity: 0.9;
}

#performanceChart {
	width: 100%;
	height: 200px;
	position: relative;
	margin-top: 20px;
}

.chart-bar {
	position: absolute;
	bottom: 0;
	width: 40%;
	transition: height 0.5s ease;
	border-top-left-radius: 3px;
	border-top-right-radius: 3px;
}

.chart-bar-mathpow {
	left: 10%;
	background-color: var(--chart-bar1);
}

.chart-bar-operator {
	right: 10%;
	background-color: var(--chart-bar2);
}

.chart-label {
	position: absolute;
	bottom: -25px;
	text-align: center;
	width: 40%;
}

.chart-label-mathpow {
	left: 10%;
}

.chart-label-operator {
	right: 10%;
}

.chart-value {
	position: absolute;
	width: 40%;
	text-align: center;
}

.chart-value-mathpow {
	left: 10%;
	top: -25px;
}

.chart-value-operator {
	right: 10%;
	top: -25px;
}

.chart-grid {
	position: absolute;
	width: 100%;
	border-top: 1px dashed var(--chart-grid);
}

@media (max-width: 600px) {
	.container {
		grid-template-columns: 1fr;
	}
}

.toggle-mode {
	position: absolute;
	top: 20px;
	right: 20px;
	background: none;
	border: 2px solid var(--highlight);
	color: var(--highlight);
	font-size: 14px;
}
---js
// Check for system preference and set initial mode
if (
window.matchMedia &&
window.matchMedia("(prefers-color-scheme: dark)").matches)
{
  document.documentElement.setAttribute("data-theme", "dark");
}

// Format numbers for display
function formatNumber(num) {
  return num.toLocaleString(undefined, { maximumFractionDigits: 2 });
}

// Interactive calculator
const baseSlider = document.getElementById("baseSlider");
const exponentSlider = document.getElementById("exponentSlider");
const baseValue = document.getElementById("baseValue");
const exponentValue = document.getElementById("exponentValue");
const result = document.getElementById("result");
const codeDisplay = document.getElementById("codeDisplay");

function updateCalculation() {
  const base = parseFloat(baseSlider.value);
  const exponent = parseFloat(exponentSlider.value);
  const calculatedResult = base ** exponent;

  baseValue.textContent = base.toFixed(1);
  exponentValue.textContent = exponent.toFixed(1);
  result.textContent = `Result: ${formatNumber(calculatedResult)}`;
  codeDisplay.textContent = `const result = ${base} ** ${exponent};
// Equivalent to: Math.pow(${base}, ${exponent});`;
}

baseSlider.addEventListener("input", updateCalculation);
exponentSlider.addEventListener("input", updateCalculation);

// Performance comparison
const iterationsSlider = document.getElementById("iterationsSlider");
const iterationsValue = document.getElementById("iterationsValue");
const runTestButton = document.getElementById("runTestButton");
const mathPowBar = document.getElementById("mathPowBar");
const operatorBar = document.getElementById("operatorBar");
const mathPowValue = document.getElementById("mathPowValue");
const operatorValue = document.getElementById("operatorValue");

iterationsSlider.addEventListener("input", () => {
  const iterations = parseInt(iterationsSlider.value);
  iterationsValue.textContent = iterations.toLocaleString();
});

runTestButton.addEventListener("click", () => {
  const iterations = parseInt(iterationsSlider.value);
  const base = parseFloat(baseSlider.value);
  const exponent = parseFloat(exponentSlider.value);

  // Reset chart
  mathPowBar.style.height = "0";
  operatorBar.style.height = "0";
  mathPowValue.textContent = "0ms";
  operatorValue.textContent = "0ms";

  // Prevent UI freeze
  setTimeout(() => {
    // Test Math.pow
    let result;
    const mathPowStart = performance.now();
    for (let i = 0; i < iterations; i++) {
      result = Math.pow(base, exponent);
    }
    const mathPowEnd = performance.now();
    const mathPowTime = mathPowEnd - mathPowStart;

    // Test ** operator
    const operatorStart = performance.now();
    for (let i = 0; i < iterations; i++) {
      result = base ** exponent;
    }
    const operatorEnd = performance.now();
    const operatorTime = operatorEnd - operatorStart;

    // Update chart
    const maxTime = Math.max(mathPowTime, operatorTime);
    const mathPowHeight = mathPowTime / maxTime * 100;
    const operatorHeight = operatorTime / maxTime * 100;

    mathPowBar.style.height = `${mathPowHeight}%`;
    operatorBar.style.height = `${operatorHeight}%`;
    mathPowValue.textContent = `${mathPowTime.toFixed(2)}ms`;
    operatorValue.textContent = `${operatorTime.toFixed(2)}ms`;
  }, 50);
});

// Assignment operator
const assignBaseSlider = document.getElementById("assignBaseSlider");
const assignBaseValue = document.getElementById("assignBaseValue");
const applyExponentButton = document.getElementById("applyExponentButton");
const assignmentResult = document.getElementById("assignmentResult");
const assignmentCode = document.getElementById("assignmentCode");

let currentValue = 2;

assignBaseSlider.addEventListener("input", () => {
  currentValue = parseFloat(assignBaseSlider.value);
  assignBaseValue.textContent = currentValue.toFixed(1);
  assignmentResult.textContent = `Current value: ${currentValue}`;
  assignmentCode.textContent = `// Click "Apply Exponent" to see the code`;
});

applyExponentButton.addEventListener("click", () => {
  const exponent = parseFloat(exponentSlider.value);
  const oldValue = currentValue;
  currentValue **= exponent;
  assignmentResult.textContent = `Current value: ${formatNumber(currentValue)}`;
  assignmentCode.textContent = `let value = ${oldValue};
value **= ${exponent};
// value is now ${formatNumber(currentValue)}`;
});

// Compound interest calculator
const principalSlider = document.getElementById("principalSlider");
const rateSlider = document.getElementById("rateSlider");
const yearsSlider = document.getElementById("yearsSlider");
const principalValue = document.getElementById("principalValue");
const rateValue = document.getElementById("rateValue");
const yearsValue = document.getElementById("yearsValue");
const interestResult = document.getElementById("interestResult");
const interestCode = document.getElementById("interestCode");

function updateInterestCalculation() {
  const principal = parseFloat(principalSlider.value);
  const rate = parseFloat(rateSlider.value) / 100;
  const years = parseInt(yearsSlider.value);

  const futureValue = principal * (1 + rate) ** years;

  principalValue.textContent = `$${principal.toLocaleString()}`;
  rateValue.textContent = `${rate * 100}%`;
  yearsValue.textContent = years;
  interestResult.textContent = `Future Value: $${futureValue.toLocaleString(
  undefined,
  { minimumFractionDigits: 2, maximumFractionDigits: 2 })
  }`;
  interestCode.textContent = `const futureValue = ${principal} * (1 + ${rate}) ** ${years};
// Equivalent to: ${principal} * Math.pow(1 + ${rate}, ${years});`;
}

principalSlider.addEventListener("input", updateInterestCalculation);
rateSlider.addEventListener("input", updateInterestCalculation);
yearsSlider.addEventListener("input", updateInterestCalculation);

// Initialize
updateCalculation();
updateInterestCalculation();

ES8/ES2017: The Async Revolution#

If ES7 was an appetizer, ES8 is the main course—particularly for handling asynchronous operations, which have long been one of JavaScript's most challenging aspects.

The Evolution of Async JavaScript#

To appreciate async/await, we need to understand how asynchronous JavaScript has evolved. In these three sketches, getUser, getUserPosts, getPostComments, userId and handleError are placeholders for your own functions: the first sketch's functions take callbacks, the other two's return promises.

// The callback era (pre-ES6)
getUser(userId, function(user) {
  getUserPosts(user.id, function(posts) {
    getPostComments(posts[0].id, function(comments) {
      // Welcome to callback hell
      console.log(comments);
    }, handleError);
  }, handleError);
}, handleError);
// The promise era (ES6)
getUser(userId)
  .then(user => getUserPosts(user.id))
  .then(posts => getPostComments(posts[0].id))
  .then(comments => console.log(comments))
  .catch(error => handleError(error));
// The async/await era (ES8)
async function showComments(userId) {
  try {
    const user = await getUser(userId);
    const posts = await getUserPosts(user.id);
    const comments = await getPostComments(posts[0].id);
    console.log(comments);
  } catch (error) {
    handleError(error);
  }
}
showComments(userId);

Notice the async in front of function. await is only allowed inside an async function (or at the top level of a module), and an async function always returns a promise. Each await pauses that function until the promise settles; the rest of the page keeps running.

Async Evolution Playground
---html
<h1>Async Evolution Playground</h1>
  <p>Experience the evolution of asynchronous JavaScript through three distinct eras: Callbacks, Promises, and Async/Await.</p>
  
  <div class="timeline-container">
    <div class="timeline-item">
      <div class="timeline-dot" data-era="callback"></div>
      <div class="timeline-label" data-era="callback">Callback Era<br><small>(Pre-ES6)</small></div>
    </div>
    <div class="timeline-item">
      <div class="timeline-dot" data-era="promise"></div>
      <div class="timeline-label" data-era="promise">Promise Era<br><small>(ES6, 2015)</small></div>
    </div>
    <div class="timeline-item">
      <div class="timeline-dot" data-era="async-await"></div>
      <div class="timeline-label" data-era="async-await">Async/Await Era<br><small>(ES8, 2017)</small></div>
    </div>
  </div>
  
  <div class="container">
    <div class="card">
      <h2 id="currentEraTitle">Callback Era (Pre-ES6)</h2>
      
      <div class="code-tabs">
        <div class="code-tab active" data-tab="code">Code</div>
        <div class="code-tab" data-tab="explanation">Explanation</div>
        <div class="code-tab" data-tab="pros-cons">Pros & Cons</div>
      </div>
      
      <div class="code-content active" data-content="code">
        <div class="code-display" id="codeDisplay">
// Simulating asynchronous API calls with callbacks
function getUser(userId, successCallback, errorCallback) {
  setTimeout(() => {
    if (userId > 0) {
      successCallback({ id: userId, name: "User " + userId });
    } else {
      errorCallback(new Error("Invalid user ID"));
    }
  }, 1000);
}

function getUserPosts(userId, successCallback, errorCallback) {
  setTimeout(() => {
    if (userId > 0) {
      successCallback([
        { id: 1, title: "Post 1" },
        { id: 2, title: "Post 2" }
      ]);
    } else {
      errorCallback(new Error("Cannot fetch posts"));
    }
  }, 1000);
}

function getPostComments(postId, successCallback, errorCallback) {
  setTimeout(() => {
    if (postId > 0) {
      successCallback([
        { id: 1, text: "Great post!" },
        { id: 2, text: "I learned a lot" }
      ]);
    } else {
      errorCallback(new Error("Cannot fetch comments"));
    }
  }, 1000);
}

// Using the callback-based API
function fetchUserData(userId) {
  getUser(userId, 
    function(user) {
      console.log("User:", user);
      
      getUserPosts(user.id, 
        function(posts) {
          console.log("Posts:", posts);
          
          getPostComments(posts[0].id, 
            function(comments) {
              console.log("Comments:", comments);
              console.log("Operation complete!");
            },
            function(error) {
              console.error("Comments error:", error);
            }
          );
        },
        function(error) {
          console.error("Posts error:", error);
        }
      );
    },
    function(error) {
      console.error("User error:", error);
    }
  );
}</div>
      </div>
      
      <div class="code-content" data-content="explanation">
        <h3>The Callback Pattern</h3>
        <p>In the callback era, asynchronous operations were managed by passing functions as arguments that would be called once the operation completed.</p>
        <p>For each operation, you needed to provide:</p>
        <ul>
          <li>A success callback function</li>
          <li>An error callback function</li>
        </ul>
        <p>This approach led to deeply nested callbacks for sequential async operations, creating what became known as "callback hell" or the "pyramid of doom" due to its indentation pattern.</p>
        <p>Error handling was particularly challenging, as each callback needed its own error handler, making it difficult to manage errors consistently across a chain of operations.</p>
      </div>
      
      <div class="code-content" data-content="pros-cons">
        <h3>Pros</h3>
        <ul>
          <li>Simple concept to understand</li>
          <li>Universally supported, even in older browsers</li>
          <li>No additional language features required</li>
          <li>Fine-grained control over each step</li>
        </ul>
        
        <h3>Cons</h3>
        <ul>
          <li>Leads to deeply nested code ("callback hell")</li>
          <li>Error handling is complex and repetitive</li>
          <li>Difficult to reason about the flow of execution</li>
          <li>Sequential operations create deeply nested structures</li>
          <li>Parallelizing operations is cumbersome</li>
          <li>Code becomes difficult to maintain as complexity grows</li>
        </ul>
      </div>
      
      <div class="controls">
        <button id="runCodeButton">Run Code</button>
        <button id="clearConsoleButton" class="secondary">Clear Console</button>
        <button id="toggleErrorButton" class="error-toggle">Toggle Error Mode</button>
      </div>
      
      <h3>Console Output</h3>
      <div class="console" id="consoleOutput"></div>
    </div>
  </div>
  
  <div class="container">
    <div class="card">
      <h2>Visual Execution Flow</h2>
      <div class="visual-flow" id="visualFlow">
        <div class="flow-item" data-step="getUser">Get User</div>
        <div class="flow-item" data-step="getPosts">Get Posts</div>
        <div class="flow-item" data-step="getComments">Get Comments</div>
        <div class="flow-item" data-step="complete">Complete</div>
      </div>
      
      <div class="compare-container">
        <div>
          <h3>Readability Comparison</h3>
          <div class="code-display">
// Callback approach (nested)
getUser(userId, user => {
  getUserPosts(user.id, posts => {
    getComments(posts[0].id, comments => {
      // Deep nesting required
    });
  });
});

// Promise approach (chained)
getUser(userId)
  .then(user => getUserPosts(user.id))
  .then(posts => getComments(posts[0].id))
  .then(comments => {
    // Flat structure
  });

// Async/await approach (sequential)
async function getData() {
  const user = await getUser(userId);
  const posts = await getUserPosts(user.id);
  const comments = await getComments(posts[0].id);
  // Looks synchronous
}</div>
        </div>
        
        <div>
          <h3>Error Handling Comparison</h3>
          <div class="code-display">
// Callback error handling (repetitive)
getUser(id, 
  user => {
    // Success
  },
  error => {
    // Error handler 1
  }
);

// Promise error handling (centralized)
getUser(id)
  .then(user => {
    // Success
  })
  .catch(error => {
    // One error handler for the chain
  });

// Async/await error handling (try/catch)
async function getData() {
  try {
    const user = await getUser(id);
    // Success
  } catch (error) {
    // One try/catch block
  }
}</div>
        </div>
      </div>
      
      <div class="performance-metric">
        <h3>Code Complexity</h3>
        <div class="performance-value" id="complexityValue">-</div>
        <p>Lines of code required for the same operation across different eras</p>
      </div>
    </div>
  </div>
---css
/* Dark mode awareness */
:root {
	--bg-color: #fff;
	--text-color: #333;
	--card-bg: #f0f0f0;
	--highlight: #4a90e2;
	--secondary: #9b59b6;
	--success: #2ecc71;
	--error: #e74c3c;
	--warning: #f39c12;
	--info: #3498db;
	--border: #ddd;
	--timeline-bg: #e0e0e0;
	--timeline-connector: #bbb;
	--code-bg: rgba(0, 0, 0, 0.06);
	--active-tab: rgba(74, 144, 226, 0.1);
}

@media (prefers-color-scheme: dark) {
	:root {
		--bg-color: #1a1a1a;
		--text-color: #e0e0e0;
		--card-bg: #2a2a2a;
		--highlight: #5a9cf0;
		--secondary: #bb6aef;
		--success: #3ee887;
		--error: #ff6b6b;
		--warning: #ffb142;
		--info: #54a0ff;
		--border: #444;
		--timeline-bg: #333;
		--timeline-connector: #555;
		--code-bg: rgba(255, 255, 255, 0.06);
		--active-tab: rgba(90, 156, 240, 0.2);
	}
}

body {
	font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica,
		Arial, sans-serif;
	background-color: var(--bg-color);
	color: var(--text-color);
	max-width: 900px;
	margin: 0 auto;
	padding: 20px;
	transition: background-color 0.3s, color 0.3s;
}

h1,
h2,
h3 {
	color: var(--highlight);
}

.container {
	display: grid;
	grid-template-columns: 1fr;
	gap: 20px;
	margin-top: 20px;
}

.card {
	background-color: var(--card-bg);
	border-radius: 8px;
	padding: 20px;
	box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}

.timeline-container {
	display: flex;
	margin: 40px 0;
	position: relative;
}

.timeline-container::before {
	content: "";
	position: absolute;
	top: 30px;
	left: 0;
	right: 0;
	height: 4px;
	background-color: var(--timeline-connector);
	z-index: 1;
}

.timeline-item {
	flex: 1;
	text-align: center;
	padding-top: 20px;
	position: relative;
	z-index: 2;
}

.timeline-dot {
	width: 20px;
	height: 20px;
	border-radius: 50%;
	background-color: var(--timeline-bg);
	border: 4px solid var(--highlight);
	display: inline-block;
	margin-bottom: 10px;
	cursor: pointer;
	transition: transform 0.2s;
}

.timeline-dot:hover {
	transform: scale(1.1);
}

.timeline-dot.active {
	background-color: var(--highlight);
}

.timeline-label {
	font-weight: bold;
	margin-top: 15px;
	cursor: pointer;
}

.code-tabs {
	display: flex;
	border-bottom: 1px solid var(--border);
	margin-bottom: 20px;
}

.code-tab {
	padding: 10px 20px;
	cursor: pointer;
	border-bottom: 3px solid transparent;
	transition: background-color 0.2s;
}

.code-tab:hover {
	background-color: rgba(0, 0, 0, 0.05);
}

.code-tab.active {
	border-bottom-color: var(--highlight);
	color: var(--highlight);
	background-color: var(--active-tab);
}

.code-content {
	display: none;
}

.code-content.active {
	display: block;
}

.code-display {
	font-family: "Courier New", monospace;
	background-color: var(--code-bg);
	padding: 15px;
	border-radius: 4px;
	overflow-x: auto;
	white-space: pre;
	line-height: 1.5;
}

.controls {
	margin: 20px 0;
	display: flex;
	flex-wrap: wrap;
	gap: 10px;
}

button {
	background-color: var(--highlight);
	color: white;
	border: none;
	border-radius: 4px;
	padding: 8px 16px;
	cursor: pointer;
	transition: opacity 0.2s;
}

button:hover {
	opacity: 0.9;
}

button.secondary {
	background-color: var(--secondary);
}

.error-toggle {
	background-color: var(--error);
}

.console {
	background-color: var(--code-bg);
	padding: 15px;
	border-radius: 4px;
	margin-top: 20px;
	font-family: "Courier New", monospace;
	min-height: 150px;
	max-height: 300px;
	overflow-y: auto;
}

.console-line {
	margin-bottom: 5px;
	padding: 3px 5px;
	border-radius: 2px;
}

.console-line.success {
	color: var(--success);
}

.console-line.error {
	color: var(--error);
}

.console-line.info {
	color: var(--info);
}

.console-line.warning {
	color: var(--warning);
}

.loading-indicator {
	display: inline-block;
	width: 10px;
	height: 10px;
	border-radius: 50%;
	margin-right: 5px;
	animation: pulse 1.5s infinite;
}

@keyframes pulse {
	0% {
		opacity: 0.3;
	}
	50% {
		opacity: 1;
	}
	100% {
		opacity: 0.3;
	}
}

.visual-flow {
	display: flex;
	margin: 20px 0;
	position: relative;
	height: 100px;
	align-items: center;
}

.flow-item {
	height: 50px;
	display: flex;
	align-items: center;
	justify-content: center;
	flex: 1;
	background-color: var(--timeline-bg);
	margin: 0 10px;
	border-radius: 4px;
	position: relative;
	opacity: 0.7;
	transition: opacity 0.3s, background-color 0.3s;
}

.flow-item.active {
	opacity: 1;
	background-color: var(--highlight);
	color: white;
}

.flow-item::after {
	content: "→";
	position: absolute;
	right: -15px;
	font-size: 20px;
	color: var(--text-color);
	z-index: 2;
}

.flow-item:last-child::after {
	display: none;
}

.compare-container {
	display: grid;
	grid-template-columns: 1fr 1fr;
	gap: 20px;
	margin-top: 20px;
}

.performance-metric {
	text-align: center;
	padding: 10px;
	background-color: var(--code-bg);
	border-radius: 4px;
	margin-top: 10px;
}

.performance-value {
	font-size: 24px;
	font-weight: bold;
	margin: 10px 0;
}

@media (max-width: 768px) {
	.compare-container {
		grid-template-columns: 1fr;
	}

	.timeline-container {
		flex-direction: column;
		align-items: flex-start;
	}

	.timeline-container::before {
		top: 0;
		bottom: 0;
		left: 30px;
		right: auto;
		width: 4px;
		height: auto;
	}

	.timeline-item {
		display: flex;
		align-items: center;
		margin-bottom: 20px;
		padding-top: 0;
		padding-left: 50px;
		text-align: left;
	}

	.timeline-dot {
		position: absolute;
		left: 22px;
		margin-bottom: 0;
	}

	.timeline-label {
		margin-top: 0;
		margin-left: 10px;
	}
}

.toggle-mode {
	position: absolute;
	top: 20px;
	right: 20px;
	background: none;
	border: 2px solid var(--highlight);
	color: var(--highlight);
	font-size: 14px;
}
---js
// Check for system preference and set initial mode
if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) {
  document.documentElement.setAttribute('data-theme', 'dark');
}

// Tab handling
const codeTabs = document.querySelectorAll('.code-tab');
const codeContents = document.querySelectorAll('.code-content');

codeTabs.forEach(tab => {
  tab.addEventListener('click', () => {
    const tabName = tab.getAttribute('data-tab');

    // Update active tab
    codeTabs.forEach(t => t.classList.remove('active'));
    tab.classList.add('active');

    // Update active content
    codeContents.forEach(content => {
      if (content.getAttribute('data-content') === tabName) {
        content.classList.add('active');
      } else {
        content.classList.remove('active');
      }
    });
  });
});

// Timeline handling
const timelineDots = document.querySelectorAll('.timeline-dot');
const timelineLabels = document.querySelectorAll('.timeline-label');
const currentEraTitle = document.getElementById('currentEraTitle');
const codeDisplay = document.getElementById('codeDisplay');
const complexityValue = document.getElementById('complexityValue');

// Code for each era
const eraCode = {
  'callback': `// Simulating asynchronous API calls with callbacks
function getUser(userId, successCallback, errorCallback) {
  setTimeout(() => {
    if (userId > 0) {
      successCallback({ id: userId, name: "User " + userId });
    } else {
      errorCallback(new Error("Invalid user ID"));
    }
  }, 1000);
}

function getUserPosts(userId, successCallback, errorCallback) {
  setTimeout(() => {
    if (userId > 0) {
      successCallback([
        { id: 1, title: "Post 1" },
        { id: 2, title: "Post 2" }
      ]);
    } else {
      errorCallback(new Error("Cannot fetch posts"));
    }
  }, 1000);
}

function getPostComments(postId, successCallback, errorCallback) {
  setTimeout(() => {
    if (postId > 0) {
      successCallback([
        { id: 1, text: "Great post!" },
        { id: 2, text: "I learned a lot" }
      ]);
    } else {
      errorCallback(new Error("Cannot fetch comments"));
    }
  }, 1000);
}

// Using the callback-based API
function fetchUserData(userId) {
  getUser(userId, 
    function(user) {
      console.log("User:", user);
      
      getUserPosts(user.id, 
        function(posts) {
          console.log("Posts:", posts);
          
          getPostComments(posts[0].id, 
            function(comments) {
              console.log("Comments:", comments);
              console.log("Operation complete!");
            },
            function(error) {
              console.error("Comments error:", error);
            }
          );
        },
        function(error) {
          console.error("Posts error:", error);
        }
      );
    },
    function(error) {
      console.error("User error:", error);
    }
  );
}`,

  'promise': `// Simulating asynchronous API calls with promises
function getUser(userId) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      if (userId > 0) {
        resolve({ id: userId, name: "User " + userId });
      } else {
        reject(new Error("Invalid user ID"));
      }
    }, 1000);
  });
}

function getUserPosts(userId) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      if (userId > 0) {
        resolve([
          { id: 1, title: "Post 1" },
          { id: 2, title: "Post 2" }
        ]);
      } else {
        reject(new Error("Cannot fetch posts"));
      }
    }, 1000);
  });
}

function getPostComments(postId) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      if (postId > 0) {
        resolve([
          { id: 1, text: "Great post!" },
          { id: 2, text: "I learned a lot" }
        ]);
      } else {
        reject(new Error("Cannot fetch comments"));
      }
    }, 1000);
  });
}

// Using the promise-based API
function fetchUserData(userId) {
  return getUser(userId)
    .then(user => {
      console.log("User:", user);
      return getUserPosts(user.id);
    })
    .then(posts => {
      console.log("Posts:", posts);
      return getPostComments(posts[0].id);
    })
    .then(comments => {
      console.log("Comments:", comments);
      console.log("Operation complete!");
      return comments;
    })
    .catch(error => {
      console.error("Error:", error);
      throw error; // Re-throw to propagate the error
    });
}`,

  'async-await': `// Simulating asynchronous API calls with promises
function getUser(userId) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      if (userId > 0) {
        resolve({ id: userId, name: "User " + userId });
      } else {
        reject(new Error("Invalid user ID"));
      }
    }, 1000);
  });
}

function getUserPosts(userId) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      if (userId > 0) {
        resolve([
          { id: 1, title: "Post 1" },
          { id: 2, title: "Post 2" }
        ]);
      } else {
        reject(new Error("Cannot fetch posts"));
      }
    }, 1000);
  });
}

function getPostComments(postId) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      if (postId > 0) {
        resolve([
          { id: 1, text: "Great post!" },
          { id: 2, text: "I learned a lot" }
        ]);
      } else {
        reject(new Error("Cannot fetch comments"));
      }
    }, 1000);
  });
}

// Using async/await with the promise-based API
async function fetchUserData(userId) {
  try {
    const user = await getUser(userId);
    console.log("User:", user);
    
    const posts = await getUserPosts(user.id);
    console.log("Posts:", posts);
    
    const comments = await getPostComments(posts[0].id);
    console.log("Comments:", comments);
    console.log("Operation complete!");
    
    return comments;
  } catch (error) {
    console.error("Error:", error);
    throw error; // Re-throw to propagate the error
  }
}` };


// Explanation for each era
const eraExplanations = {
  'callback': `<h3>The Callback Pattern</h3>
<p>In the callback era, asynchronous operations were managed by passing functions as arguments that would be called once the operation completed.</p>
<p>For each operation, you needed to provide:</p>
<ul>
  <li>A success callback function</li>
  <li>An error callback function</li>
</ul>
<p>This approach led to deeply nested callbacks for sequential async operations, creating what became known as "callback hell" or the "pyramid of doom" due to its indentation pattern.</p>
<p>Error handling was particularly challenging, as each callback needed its own error handler, making it difficult to manage errors consistently across a chain of operations.</p>`,

  'promise': `<h3>The Promise Pattern</h3>
<p>ES6 introduced Promises, providing a more structured way to handle asynchronous operations.</p>
<p>A Promise represents a value that may not be available yet but will be resolved at some point in the future. Promises have three states:</p>
<ul>
  <li><strong>Pending</strong>: Initial state, neither fulfilled nor rejected</li>
  <li><strong>Fulfilled</strong>: The operation completed successfully</li>
  <li><strong>Rejected</strong>: The operation failed</li>
</ul>
<p>Promises allow for method chaining with <code>.then()</code> for success handling and <code>.catch()</code> for error handling. This creates a flat chain of operations rather than nested callbacks, significantly improving readability.</p>
<p>Error handling is also simplified, as a single <code>.catch()</code> at the end of a chain can handle errors from any step in the process.</p>`,

  'async-await': `<h3>The Async/Await Pattern</h3>
<p>ES8 introduced async/await, which is built on top of Promises but provides a syntax that makes asynchronous code look and feel like synchronous code.</p>
<p>Key components of async/await:</p>
<ul>
  <li><strong>async</strong>: A function declared with the <code>async</code> keyword always returns a Promise</li>
  <li><strong>await</strong>: The <code>await</code> keyword can only be used inside an async function and pauses execution until the Promise resolves</li>
</ul>
<p>This approach combines the non-blocking benefits of asynchronous code with the readability and structure of synchronous code. Error handling is also simplified further, as you can use familiar try/catch blocks just like in synchronous code.</p>
<p>Behind the scenes, async/await is still using Promises—it's "syntactic sugar" that makes working with Promises more intuitive and readable.</p>` };


// Pros and cons for each era
const eraProsAndCons = {
  'callback': `<h3>Pros</h3>
<ul>
  <li>Simple concept to understand</li>
  <li>Universally supported, even in older browsers</li>
  <li>No additional language features required</li>
  <li>Fine-grained control over each step</li>
</ul>

<h3>Cons</h3>
<ul>
  <li>Leads to deeply nested code ("callback hell")</li>
  <li>Error handling is complex and repetitive</li>
  <li>Difficult to reason about the flow of execution</li>
  <li>Sequential operations create deeply nested structures</li>
  <li>Parallelizing operations is cumbersome</li>
  <li>Code becomes difficult to maintain as complexity grows</li>
</ul>`,

  'promise': `<h3>Pros</h3>
<ul>
  <li>Flattens nested callback structures</li>
  <li>Provides a consistent error handling pattern</li>
  <li>Allows for method chaining</li>
  <li>Simplifies coordinating multiple async operations</li>
  <li>Standardized part of the language (ES6)</li>
  <li>Built-in methods like Promise.all() for parallelization</li>
</ul>

<h3>Cons</h3>
<ul>
  <li>Still requires chaining .then() calls for sequential operations</li>
  <li>Error handling can be forgotten if .catch() is omitted</li>
  <li>Slightly more complex conceptual model than callbacks</li>
  <li>Not as readable as synchronous-style code for complex sequences</li>
  <li>Debugging can be challenging due to complex stack traces</li>
</ul>`,

  'async-await': `<h3>Pros</h3>
<ul>
  <li>Makes asynchronous code look and feel synchronous</li>
  <li>Greatly improved readability for sequential operations</li>
  <li>Familiar try/catch error handling pattern</li>
  <li>Easier debugging with clearer stack traces</li>
  <li>Compatible with existing Promise-based APIs</li>
  <li>Can be mixed with Promise methods for complex scenarios</li>
</ul>

<h3>Cons</h3>
<ul>
  <li>Requires understanding of Promises underneath</li>
  <li>Can lead to inefficient code if not careful (sequential vs parallel)</li>
  <li>Must be inside an async function to use await</li>
  <li>Need to remember that async functions always return Promises</li>
  <li>Not supported in older browsers without transpilation</li>
</ul>` };


// Title for each era
const eraTitles = {
  'callback': 'Callback Era (Pre-ES6)',
  'promise': 'Promise Era (ES6, 2015)',
  'async-await': 'Async/Await Era (ES8, 2017)' };


// Complexity metrics
const complexityMetrics = {
  'callback': '24 lines',
  'promise': '16 lines',
  'async-await': '12 lines' };


// Update content based on selected era
function updateEraContent(era) {
  // Update timeline
  timelineDots.forEach(dot => {
    dot.classList.remove('active');
    if (dot.getAttribute('data-era') === era) {
      dot.classList.add('active');
    }
  });

  // Update title
  currentEraTitle.textContent = eraTitles[era];

  // Update code
  codeDisplay.textContent = eraCode[era];

  // Update explanations
  document.querySelector('.code-content[data-content="explanation"]').innerHTML = eraExplanations[era];
  document.querySelector('.code-content[data-content="pros-cons"]').innerHTML = eraProsAndCons[era];

  // Update complexity metric
  complexityValue.textContent = complexityMetrics[era];

  // Reset console
  consoleOutput.innerHTML = '';

  // Reset flow visualization
  document.querySelectorAll('.flow-item').forEach(item => {
    item.classList.remove('active');
  });

  // Reset error mode
  errorMode = false;
  toggleErrorButton.textContent = 'Toggle Error Mode (Off)';
}

// Add event listeners to timeline elements
timelineDots.forEach(dot => {
  const era = dot.getAttribute('data-era');
  dot.addEventListener('click', () => updateEraContent(era));
});

timelineLabels.forEach(label => {
  const era = label.getAttribute('data-era');
  label.addEventListener('click', () => updateEraContent(era));
});

// Console output handling
const consoleOutput = document.getElementById('consoleOutput');
const runCodeButton = document.getElementById('runCodeButton');
const clearConsoleButton = document.getElementById('clearConsoleButton');
const toggleErrorButton = document.getElementById('toggleErrorButton');
const visualFlow = document.getElementById('visualFlow');

let errorMode = false;

// Simulated console
const simulatedConsole = {
  log: function (...args) {
    const line = document.createElement('div');
    line.className = 'console-line';

    // Format arguments
    const formattedArgs = args.map(arg => {
      if (typeof arg === 'object') {
        return JSON.stringify(arg);
      }
      return String(arg);
    }).join(' ');

    line.textContent = `> ${formattedArgs}`;
    consoleOutput.appendChild(line);
    consoleOutput.scrollTop = consoleOutput.scrollHeight;

    // Update flow visualization
    if (formattedArgs.includes('User:')) {
      document.querySelector('.flow-item[data-step="getUser"]').classList.add('active');
    } else if (formattedArgs.includes('Posts:')) {
      document.querySelector('.flow-item[data-step="getPosts"]').classList.add('active');
    } else if (formattedArgs.includes('Comments:')) {
      document.querySelector('.flow-item[data-step="getComments"]').classList.add('active');
    } else if (formattedArgs.includes('Operation complete')) {
      document.querySelector('.flow-item[data-step="complete"]').classList.add('active');
    }
  },
  error: function (...args) {
    const line = document.createElement('div');
    line.className = 'console-line error';

    // Format arguments
    const formattedArgs = args.map(arg => {
      if (arg instanceof Error) {
        return arg.message;
      } else if (typeof arg === 'object') {
        return JSON.stringify(arg);
      }
      return String(arg);
    }).join(' ');

    line.textContent = `✖ ${formattedArgs}`;
    consoleOutput.appendChild(line);
    consoleOutput.scrollTop = consoleOutput.scrollHeight;
  },
  info: function (...args) {
    const line = document.createElement('div');
    line.className = 'console-line info';

    // Format arguments
    const formattedArgs = args.map(arg => {
      if (typeof arg === 'object') {
        return JSON.stringify(arg);
      }
      return String(arg);
    }).join(' ');

    line.textContent = `ℹ ${formattedArgs}`;
    consoleOutput.appendChild(line);
    consoleOutput.scrollTop = consoleOutput.scrollHeight;
  } };


// Run code button
runCodeButton.addEventListener('click', () => {
  // Clear previous output
  consoleOutput.innerHTML = '';

  // Reset flow visualization
  document.querySelectorAll('.flow-item').forEach(item => {
    item.classList.remove('active');
  });

  // Add loading indicator
  const loadingLine = document.createElement('div');
  loadingLine.className = 'console-line info';
  const loadingIndicator = document.createElement('span');
  loadingIndicator.className = 'loading-indicator';
  loadingLine.appendChild(loadingIndicator);
  loadingLine.appendChild(document.createTextNode(' Running...'));
  consoleOutput.appendChild(loadingLine);

  // Get current era and code
  const activeEra = Array.from(timelineDots).find(dot => dot.classList.contains('active'))?.getAttribute('data-era') || 'callback';
  const code = eraCode[activeEra];

  // Execute the code
  setTimeout(() => {
    consoleOutput.removeChild(loadingLine);

    try {
      // Override console methods
      const originalLog = console.log;
      const originalError = console.error;
      const originalInfo = console.info;

      console.log = simulatedConsole.log;
      console.error = simulatedConsole.error;
      console.info = simulatedConsole.info;

      // Create function from code
      const userId = errorMode ? -1 : 1;
      const executeCode = new Function(`
            ${code}
            return fetchUserData(${userId});
          `);

      // Execute and handle different result types
      const result = executeCode();

      if (result instanceof Promise) {
        result.catch(error => {
          // Promise error already handled in the code
        });
      }

      // Restore console methods
      setTimeout(() => {
        console.log = originalLog;
        console.error = originalError;
        console.info = originalInfo;
      }, 0);
    } catch (error) {
      simulatedConsole.error('Execution error:', error.message);
    }
  }, 500);
});

// Clear console button
clearConsoleButton.addEventListener('click', () => {
  consoleOutput.innerHTML = '';

  // Reset flow visualization
  document.querySelectorAll('.flow-item').forEach(item => {
    item.classList.remove('active');
  });
});

// Toggle error mode button
toggleErrorButton.addEventListener('click', () => {
  errorMode = !errorMode;
  toggleErrorButton.textContent = `Toggle Error Mode (${errorMode ? 'On' : 'Off'})`;
});

// Initialize with callback era
updateEraContent('callback');

Async/Await: Making Asynchronous Code Feel Synchronous#

The secret to async/await's power is how it transforms asynchronous operations into code that looks and feels synchronous while retaining all the non-blocking benefits.

async function fetchUserData() {
  try {
    // Each await pauses execution until the promise resolves
    const response = await fetch('/api/user');
    const userData = await response.json();
    return userData;
  } catch (error) {
    console.error('Failed to fetch user:', error);
    throw error; // Re-throw to allow further catch handling
  }
}

// Don't forget - async functions always return promises!
fetchUserData()
  .then(user => console.log(user))
  .catch(error => console.error('Outer error handler:', error));

Sequential vs. Parallel Execution#

While async/await excels at making code readable, you need to be careful about performance:

Promise.all takes a list of promises, starts them all right away, and gives back one promise that resolves with all the results once every one has finished. Here fetchUser, fetchPosts and fetchComments are placeholders for three independent requests:

async function loadSequential() {
  // Sequential execution (slower): each request waits for the one before it
  const userData = await fetchUser();
  const postData = await fetchPosts();
  const commentData = await fetchComments();
}
async function loadParallel() {
  // Parallel execution (faster when operations are independent)
  const [userData, postData, commentData] = await Promise.all([
    fetchUser(),
    fetchPosts(),
    fetchComments()
  ]);
}
Two independent tasks each take 1 second. About how long do they take with await one after the other, and with Promise.all?

Coding challenge: Race the clock

The starter times the sequential version with a fake 1-second task. Add the parallel version and compare.

Expected result: the console shows sequential: 2 s (about two seconds) and, once you finish the TODO, parallel: 1 s.

Sequential vs Promise.all: measure it
---js
const wait = (ms, value) =>
  new Promise((resolve) => setTimeout(() => resolve(value), ms));

async function main() {
  let start = Date.now();
  await wait(1000, "first");
  await wait(1000, "second");
  console.log("sequential:", Math.round((Date.now() - start) / 1000), "s");

  start = Date.now();
  // TODO: start both waits at once with Promise.all([...]) and await the result
  console.log("parallel:", Math.round((Date.now() - start) / 1000), "s");
}

main();
Sequential vs. Parallel Async Race
---html
<h1>Sequential vs. Parallel Async Operations</h1>
  <p>This visualization demonstrates the performance difference between running async operations sequentially versus in parallel using Promise.all().</p>
  
  <div class="container">
    <div class="card">
      <h2>Operation Settings</h2>
      
      <div class="settings">
        <div class="setting-group">
          <label for="requestCount">Number of API requests:</label>
          <div class="slider-with-value">
            <input type="range" id="requestCount" min="1" max="5" value="3" step="1">
            <span class="slider-value" id="requestCountValue">3</span>
          </div>
        </div>
        
        <div class="setting-group">
          <label for="requestTime">Request time (ms):</label>
          <div class="slider-with-value">
            <input type="range" id="requestTime" min="200" max="2000" value="1000" step="100">
            <span class="slider-value" id="requestTimeValue">1000 ms</span>
          </div>
        </div>
        
        <div class="setting-group">
          <label for="variability">Time variability (%):</label>
          <div class="slider-with-value">
            <input type="range" id="variability" min="0" max="50" value="20" step="5">
            <span class="slider-value" id="variabilityValue">20%</span>
          </div>
        </div>
      </div>
      
      <div class="controls">
        <button id="startRaceButton">Start Race</button>
        <button id="resetButton" class="secondary">Reset</button>
      </div>
      
      <div class="race-track">

        
        <div class="track">
          <div class="track-label">Sequential</div>
          <div class="progress-bar" id="parallelProgress"></div>
          <div class="racer parallel" id="parallelRacer">S</div>
          <div class="finish-line"></div>
          
          <div class="track-measures">
            <div class="track-measure">0s</div>
            <div class="track-measure">1s</div>
            <div class="track-measure">2s</div>
            <div class="track-measure">3s</div>
            <div class="track-measure">4s</div>
            <div class="track-measure">5s</div>
          </div>
        </div>
      </div>

      <div class="track">
        <div class="track-label">Parallel</div>
        <div class="progress-bar" id="sequentialProgress"></div>
        <div class="racer sequential" id="sequentialRacer">P</div>
        <div class="finish-line"></div>
        
        <div class="track-measures">
          <div class="track-measure">0s</div>
          <div class="track-measure">1s</div>
          <div class="track-measure">2s</div>
          <div class="track-measure">3s</div>
          <div class="track-measure">4s</div>
          <div class="track-measure">5s</div>
        </div>
      </div>
      
      <div class="results" id="results">
        <h3>Race Results</h3>
        <div class="result-row">
            <div>Parallel execution:</div>
            <div id="parallelTime">-</div>
          </div>
        <div class="result-row">
          <div>Sequential execution:</div>
          <div id="sequentialTime">-</div>
        </div>
        
        <div class="result-row">
          <div>Winner:</div>
          <div id="winner">-</div>
        </div>
        <div class="result-row">
          <div>Speed improvement:</div>
          <div id="improvement">-</div>
        </div>
      </div>
    </div>
    
    <div class="card">
      <h2>Code Implementation</h2>
      
      <div class="code-tabs">
        <div class="code-tab active" data-tab="sequential">Sequential</div>
        <div class="code-tab" data-tab="parallel">Parallel</div>
        <div class="code-tab" data-tab="comparison">Side-by-Side</div>
      </div>
      
      <div class="code-content active" data-content="sequential">
        <div class="code-display" id="sequentialCode">
// Sequential approach with async/await
async function fetchDataSequentially() {
  console.time('Sequential');
  
  // Individual requests run one after another
  const result1 = await fetchData('endpoint1');
  const result2 = await fetchData('endpoint2');
  const result3 = await fetchData('endpoint3');
  
  console.timeEnd('Sequential');
  return [result1, result2, result3];
}</div>
      </div>
      
      <div class="code-content" data-content="parallel">
        <div class="code-display" id="parallelCode">
// Parallel approach with Promise.all()
async function fetchDataInParallel() {
  console.time('Parallel');
  
  // All requests start at the same time
  const results = await Promise.all([
    fetchData('endpoint1'),
    fetchData('endpoint2'),
    fetchData('endpoint3')
  ]);
  
  console.timeEnd('Parallel');
  return results;
}</div>
      </div>
      
      <div class="code-content" data-content="comparison">
        <div class="code-display">
// Sequential vs Parallel Comparison

// Sequential: Total time = sum of all request times
async function fetchDataSequentially() {
  const result1 = await fetchData('endpoint1'); // Wait for 1st request
  const result2 = await fetchData('endpoint2'); // Then wait for 2nd request
  const result3 = await fetchData('endpoint3'); // Then wait for 3rd request
  
  return [result1, result2, result3];
}

// Parallel: Total time = duration of the longest request
async function fetchDataInParallel() {
  const results = await Promise.all([
    fetchData('endpoint1'), // All three requests
    fetchData('endpoint2'), // start at the same time
    fetchData('endpoint3')  // and run concurrently
  ]);
  
  return results;
}

// Real-world scenario where parallel is not always better:
async function fetchRelatedData() {
  // We need user data before we can fetch their posts
  const user = await fetchData(`/users/123`);
  
  // Now we can fetch posts and followers in parallel
  const [posts, followers] = await Promise.all([
    fetchData(`/users/${user.id}/posts`),
    fetchData(`/users/${user.id}/followers`)
  ]);
  
  return { user, posts, followers };
}</div>
      </div>
    </div>
  </div>
---css
/* Dark mode awareness */
    :root {
      --bg-color: #fff;
      --text-color: #333;
      --card-bg: #f0f0f0;
      --highlight: #4a90e2;
      --secondary: #9b59b6;
      --success: #2ecc71;
      --warning: #f39c12;
      --info: #3498db;
      --border: #ddd;
      --track-bg: #e8e8e8;
      --progress-bg: rgba(74, 144, 226, 0.3);
      --progress-text: #4a90e2;
      --racer-seq: #e74c3c;
      --racer-par: #2ecc71;
      --code-bg: rgba(0, 0, 0, 0.06);
    }
    
    @media (prefers-color-scheme: dark) {
      :root {
        --bg-color: #1a1a1a;
        --text-color: #e0e0e0;
        --card-bg: #2a2a2a;
        --highlight: #5a9cf0;
        --secondary: #bb6aef;
        --success: #3ee887;
        --warning: #ffb142;
        --info: #54a0ff;
        --border: #444;
        --track-bg: #333;
        --progress-bg: rgba(90, 156, 240, 0.2);
        --progress-text: #5a9cf0;
        --racer-seq: #ff6b6b;
        --racer-par: #3ee887;
        --code-bg: rgba(255, 255, 255, 0.06);
      }
    }
    
    body {
      font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
      background-color: var(--bg-color);
      color: var(--text-color);
      max-width: 900px;
      margin: 0 auto;
      padding: 20px;
      transition: background-color 0.3s, color 0.3s;
    }
    
    h1, h2, h3 {
      color: var(--highlight);
    }
    
    .container {
      display: grid;
      grid-template-columns: 1fr;
      gap: 20px;
      margin-top: 20px;
    }
    
    .card {
      background-color: var(--card-bg);
      border-radius: 8px;
      padding: 20px;
      box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
    }
    
    .race-track {
      margin: 30px 0;
      position: relative;
    }
    
    .track {
      height: 60px;
      background-color: var(--track-bg);
      border-radius: 4px;
      position: relative;
      margin-bottom: 30px;
      overflow: hidden;
    }
    
    .track-label {
      position: absolute;
      left: -120px;
      top: 20px;
      width: 100px;
      text-align: right;
      font-weight: bold;
    }
    
    .racer {
      position: absolute;
      width: 30px;
      height: 30px;
      border-radius: 50%;
      top: 15px;
      left: 10px;
      display: flex;
      align-items: center;
      justify-content: center;
      color: white;
      font-weight: bold;
      transition: left 0.5s ease;
      z-index: 2;
    }
    
    .racer.sequential {
      background-color: var(--racer-par);
    }
    
    .racer.parallel {
      background-color: var(--racer-seq);
    }
    
    .progress-bar {
      position: absolute;
      height: 100%;
      width: 0;
      background-color: var(--progress-bg);
      transition: width 0.5s ease;
      display: flex;
      align-items: center;
      justify-content: flex-end;
      padding-right: 10px;
      color: var(--progress-text);
      font-weight: bold;
      box-sizing: border-box;
    }
    
    .finish-line {
      position: absolute;
      right: 0;
      top: 0;
      bottom: 0;
      width: 5px;
      background: repeating-linear-gradient(
        45deg,
        #000,
        #000 5px,
        #fff 5px,
        #fff 10px
      );
      z-index: 2;
    }
    
    .request-label {
      position: absolute;
      top: -25px;
      display: inline-block;
      padding: 2px 8px;
      border-radius: 4px;
      font-size: 12px;
      white-space: nowrap;
    }
    
    .request-label.sequential {
      background-color: var(--racer-seq);
      color: white;
    }
    
    .request-label.parallel {
      background-color: var(--racer-par);
      color: white;
    }
    
    .track-measures {
      position: absolute;
      left: 0;
      right: 0;
      bottom: -20px;
      display: flex;
      justify-content: space-between;
      font-size: 12px;
      color: var(--text-color);
    }
    
    .track-measure {
      position: relative;
    }
    
    .track-measure::before {
      content: '';
      position: absolute;
      top: -5px;
      left: 50%;
      width: 1px;
      height: 5px;
      background-color: var(--border);
    }
    
    .controls {
      display: flex;
      flex-wrap: wrap;
      gap: 10px;
      margin: 20px 0;
    }
    
    button {
      background-color: var(--highlight);
      color: white;
      border: none;
      border-radius: 4px;
      padding: 8px 16px;
      cursor: pointer;
      transition: opacity 0.2s;
    }
    
    button:hover {
      opacity: 0.9;
    }
    
    button.secondary {
      background-color: var(--secondary);
    }
    
    .results {
      background-color: var(--progress-bg);
      padding: 15px;
      border-radius: 4px;
      margin-top: 30px;
      display: none;
    }
    
    .results.active {
      display: block;
    }
    
    .result-row {
      display: flex;
      justify-content: space-between;
      margin-bottom: 10px;
      padding-bottom: 10px;
      border-bottom: 1px solid var(--border);
    }
    
    .result-row:last-child {
      border-bottom: none;
      margin-bottom: 0;
      padding-bottom: 0;
    }
    
    .winner {
      font-weight: bold;
      color: var(--success);
    }
    
    .settings {
      margin-bottom: 20px;
    }
    
    .setting-group {
      margin-bottom: 15px;
    }
    
    label {
      display: block;
      margin-bottom: 5px;
    }
    
    .slider-with-value {
      display: flex;
      align-items: center;
      gap: 10px;
    }
    
    .slider-value {
      min-width: 60px;
      text-align: center;
      font-weight: bold;
    }
    
    input[type="range"] {
      flex-grow: 1;
      height: 8px;
      -webkit-appearance: none;
      background: var(--track-bg);
      border-radius: 4px;
      outline: none;
    }
    
    input[type="range"]::-webkit-slider-thumb {
      -webkit-appearance: none;
      width: 18px;
      height: 18px;
      border-radius: 50%;
      background: var(--highlight);
      cursor: pointer;
    }
    
    input[type="range"]::-moz-range-thumb {
      width: 18px;
      height: 18px;
      border-radius: 50%;
      background: var(--highlight);
      cursor: pointer;
      border: none;
    }
    
    .code-display {
      background-color: var(--code-bg);
      padding: 15px;
      border-radius: 4px;
      overflow-x: auto;
      white-space: pre;
      line-height: 1.5;
      font-family: 'Courier New', monospace;
      margin-top: 20px;
    }
    
    .code-tabs {
      display: flex;
      border-bottom: 1px solid var(--border);
      margin-bottom: 15px;
    }
    
    .code-tab {
      padding: 8px 15px;
      cursor: pointer;
      border-bottom: 3px solid transparent;
    }
    
    .code-tab.active {
      border-bottom-color: var(--highlight);
      color: var(--highlight);
      background-color: rgba(0, 0, 0, 0.05);
    }
    
    .code-content {
      display: none;
    }
    
    .code-content.active {
      display: block;
    }
    
    @media (max-width: 768px) {
      .track-label {
        position: relative;
        left: 0;
        top: 0;
        width: auto;
        text-align: left;
        margin-bottom: 5px;
      }
      
      .race-track {
        margin-left: 0;
      }
    }
    
    .toggle-mode {
      position: absolute;
      top: 20px;
      right: 20px;
      background: none;
      border: 2px solid var(--highlight);
      color: var(--highlight);
      font-size: 14px;
    }
---js
// Check for system preference and set initial mode
if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) {
  document.documentElement.setAttribute('data-theme', 'dark');
}

// Tab handling
const codeTabs = document.querySelectorAll('.code-tab');
const codeContents = document.querySelectorAll('.code-content');

codeTabs.forEach(tab => {
  tab.addEventListener('click', () => {
    const tabName = tab.getAttribute('data-tab');

    // Update active tab
    codeTabs.forEach(t => t.classList.remove('active'));
    tab.classList.add('active');

    // Update active content
    codeContents.forEach(content => {
      if (content.getAttribute('data-content') === tabName) {
        content.classList.add('active');
      } else {
        content.classList.remove('active');
      }
    });
  });
});

// Settings handling
const requestCountSlider = document.getElementById('requestCount');
const requestCountValue = document.getElementById('requestCountValue');
const requestTimeSlider = document.getElementById('requestTime');
const requestTimeValue = document.getElementById('requestTimeValue');
const variabilitySlider = document.getElementById('variability');
const variabilityValue = document.getElementById('variabilityValue');

// Update sequential code display based on settings
function updateSequentialCode() {
  const count = parseInt(requestCountSlider.value);
  let code = `// Sequential approach with async/await
async function fetchDataSequentially() {
  console.time('Sequential');
  
  // Individual requests run one after another`;

  for (let i = 1; i <= count; i++) {
    code += `\n  const result${i} = await fetchData('endpoint${i}');`;
  }

  code += `
  
  console.timeEnd('Sequential');
  return [${Array.from({ length: count }, (_, i) => `result${i + 1}`).join(', ')}];
}`;

  document.getElementById('sequentialCode').textContent = code;
}

// Update parallel code display based on settings
function updateParallelCode() {
  const count = parseInt(requestCountSlider.value);
  let code = `// Parallel approach with Promise.all()
async function fetchDataInParallel() {
  console.time('Parallel');
  
  // All requests start at the same time
  const results = await Promise.all([`;

  for (let i = 1; i <= count; i++) {
    code += `\n    fetchData('endpoint${i}')${i < count ? ',' : ''}`;
  }

  code += `
  ]);
  
  console.timeEnd('Parallel');
  return results;
}`;

  document.getElementById('parallelCode').textContent = code;
}

// Update settings display
requestCountSlider.addEventListener('input', () => {
  requestCountValue.textContent = requestCountSlider.value;
  updateSequentialCode();
  updateParallelCode();
});

requestTimeSlider.addEventListener('input', () => {
  requestTimeValue.textContent = `${requestTimeSlider.value} ms`;
});

variabilitySlider.addEventListener('input', () => {
  variabilityValue.textContent = `${variabilitySlider.value}%`;
});

// Race simulation
const startRaceButton = document.getElementById('startRaceButton');
const resetButton = document.getElementById('resetButton');
const sequentialRacer = document.getElementById('sequentialRacer');
const parallelRacer = document.getElementById('parallelRacer');
const sequentialProgress = document.getElementById('sequentialProgress');
const parallelProgress = document.getElementById('parallelProgress');
const results = document.getElementById('results');
const sequentialTimeDisplay = document.getElementById('sequentialTime');
const parallelTimeDisplay = document.getElementById('parallelTime');
const winnerDisplay = document.getElementById('winner');
const improvementDisplay = document.getElementById('improvement');

// Simulation state
let isRacing = false;
let sequentialLabels = [];
let parallelLabels = [];

// Reset race
function resetRace() {
  isRacing = false;

  // Reset racers and progress bars
  sequentialRacer.style.left = '10px';
  parallelRacer.style.left = '10px';
  sequentialProgress.style.width = '0';
  parallelProgress.style.width = '0';
  sequentialProgress.textContent = '';
  parallelProgress.textContent = '';

  // Hide results
  results.classList.remove('active');

  // Remove request labels
  sequentialLabels.forEach(label => label.remove());
  parallelLabels.forEach(label => label.remove());
  sequentialLabels = [];
  parallelLabels = [];

  // Update button state
  startRaceButton.textContent = 'Start Race';
  startRaceButton.disabled = false;
}

// Create a simulated API request
function simulateRequest(requestNumber, baseTime) {
  const variability = parseInt(variabilitySlider.value) / 100;
  const randomFactor = 1 + (Math.random() * variability * 2 - variability);
  const time = Math.max(100, Math.round(baseTime * randomFactor));

  return new Promise(resolve => {
    setTimeout(() => {
      resolve({
        requestNumber,
        time });

    }, time);
  });
}

// Sequential race execution
async function runSequentialRace() {
  const count = parseInt(requestCountSlider.value);
  const baseTime = parseInt(requestTimeSlider.value);
  const trackWidth = document.querySelector('.track').clientWidth - 50; // Adjust for racer size and padding
  const totalTime = 5000; // 5 seconds track represents

  let cumulativeTime = 0;
  const results = [];

  for (let i = 1; i <= count; i++) {
    // Create request label
    const label = document.createElement('div');
    label.className = 'request-label sequential';
    label.textContent = `Request ${i}`;
    label.style.left = '10px';
    sequentialRacer.parentNode.appendChild(label);
    sequentialLabels.push(label);

    // Execute request
    const start = performance.now();
    const result = await simulateRequest(i, baseTime);
    const elapsed = performance.now() - start;
    results.push(result);

    // Update racer position
    cumulativeTime += elapsed;
    const position = Math.min(trackWidth, cumulativeTime / totalTime * trackWidth);
    sequentialRacer.style.left = `${position + 10}px`; // Add offset

    // Update progress bar
    const progressWidth = Math.min(100, cumulativeTime / totalTime * 100);
    sequentialProgress.style.width = `${progressWidth}%`;
    sequentialProgress.textContent = `${(cumulativeTime / 1000).toFixed(1)}s`;

    // Update label position
    label.style.left = `${position + 10}px`;
  }

  return cumulativeTime;
}

// Parallel race execution
async function runParallelRace() {
  const count = parseInt(requestCountSlider.value);
  const baseTime = parseInt(requestTimeSlider.value);
  const trackWidth = document.querySelector('.track').clientWidth - 50;
  const totalTime = 5000;

  // Create request labels for all requests
  for (let i = 1; i <= count; i++) {
    const label = document.createElement('div');
    label.className = 'request-label parallel';
    label.textContent = `Request ${i}`;
    label.style.left = '10px';
    parallelRacer.parentNode.appendChild(label);
    parallelLabels.push(label);
  }

  // Start all requests in parallel
  const start = performance.now();
  let animationId;
  let finished = false;

  // Set up the continuous animation
  function animate() {
    const elapsed = finished ? finalTime : performance.now() - start;
    const position = Math.min(trackWidth, elapsed / totalTime * trackWidth);

    // Update racer position
    parallelRacer.style.left = `${position + 10}px`;

    // Update progress bar
    const progressWidth = Math.min(100, elapsed / totalTime * 100);
    parallelProgress.style.width = `${progressWidth}%`;
    parallelProgress.textContent = `${(elapsed / 1000).toFixed(1)}s`;

    // Update all labels
    parallelLabels.forEach(label => {
      label.style.left = `${position + 10}px`;
    });

    if (!finished) {
      animationId = requestAnimationFrame(animate);
    }
  }

  // Start the animation
  animationId = requestAnimationFrame(animate);

  // Start all requests
  const requests = Array.from({ length: count }, (_, i) =>
  simulateRequest(i + 1, baseTime));


  // Wait for all requests to complete
  await Promise.all(requests);
  const finalTime = performance.now() - start;

  // Ensure animation stops smoothly
  finished = true;

  // Ensure one more frame gets rendered with the final position
  await new Promise(resolve => {
    requestAnimationFrame(() => {
      cancelAnimationFrame(animationId);
      resolve();
    });
  });

  return finalTime;
}

// Start race button
startRaceButton.addEventListener('click', async () => {
  if (isRacing) return;

  isRacing = true;
  results.classList.remove('active');
  startRaceButton.textContent = 'Racing...';
  startRaceButton.disabled = true;

  try {
    // Reset positions before starting
    sequentialRacer.style.left = '10px';
    parallelRacer.style.left = '10px';
    sequentialProgress.style.width = '0';
    parallelProgress.style.width = '0';
    sequentialProgress.textContent = '';
    parallelProgress.textContent = '';

    // Remove previous labels
    sequentialLabels.forEach(label => label.remove());
    parallelLabels.forEach(label => label.remove());
    sequentialLabels = [];
    parallelLabels = [];

    // Run both races simultaneously but independently
    // Start parallel first to ensure it has its animation loop running before sequential starts
    const parallelPromise = runParallelRace();
    const sequentialPromise = runSequentialRace();

    // Wait for both races to complete
    const [parallelTime, sequentialTime] = await Promise.all([parallelPromise, sequentialPromise]);

    // Show results
    sequentialTimeDisplay.textContent = `${(sequentialTime / 1000).toFixed(2)}s`;
    parallelTimeDisplay.textContent = `${(parallelTime / 1000).toFixed(2)}s`;

    // Determine winner
    if (sequentialTime < parallelTime) {
      winnerDisplay.textContent = 'Sequential (unusual!)';
      winnerDisplay.className = 'winner sequential';
      improvementDisplay.textContent = `Sequential was ${((1 - sequentialTime / parallelTime) * 100).toFixed(1)}% faster`;
    } else {
      winnerDisplay.textContent = 'Parallel';
      winnerDisplay.className = 'winner parallel';
      improvementDisplay.textContent = `${((1 - parallelTime / sequentialTime) * 100).toFixed(1)}% faster`;
    }

    results.classList.add('active');
  } catch (error) {
    console.error('Race error:', error);
  } finally {
    isRacing = false;
    startRaceButton.textContent = 'Race Again';
    startRaceButton.disabled = false;
  }
});

// Reset button
resetButton.addEventListener('click', resetRace);

// Initialize
updateSequentialCode();
updateParallelCode();

Object Methods: Enhanced Object Manipulation#

ES8 introduced several powerful methods for working with objects that make data transformation much more straightforward.

Object.entries() and Object.values()#

const person = {
  name: 'Alice',
  age: 28,
  occupation: 'Engineer'
};

// Get all values
const values = Object.values(person); 
// ['Alice', 28, 'Engineer']

// Get key-value pairs as arrays
const entries = Object.entries(person);
// [['name', 'Alice'], ['age', 28], ['occupation', 'Engineer']]

// Powerful when combined with array methods
const adults = Object.entries(person)
  .filter(([key, value]) => key === 'age' && value >= 18)
  .map(([key, value]) => `${value} years old`);
// ['28 years old']
Object Inspector
---html
<h1>Object Inspector</h1>
  <p>Explore ES8's powerful Object methods: <code class="code-inline">Object.keys()</code>, <code class="code-inline">Object.values()</code>, and <code class="code-inline">Object.entries()</code>.</p>
  
  <div class="example-btn-container">
    <button class="example-btn" data-example="person">Person Object</button>
    <button class="example-btn" data-example="product">Product Object</button>
    <button class="example-btn" data-example="nested">Nested Object</button>
    <button class="example-btn" data-example="types">Various Types</button>
    <button class="example-btn" data-example="empty">Empty Object</button>
  </div>
  
  <div class="container">
    <div class="card">
      <h2>Input Object</h2>
      
      <div class="object-editor">
        <div class="text-area-wrapper">
          <textarea id="objectInput" placeholder="Enter a valid JavaScript object (JSON format)">
{
  "name": "Alice",
  "age": 28,
  "occupation": "Software Engineer",
  "languages": ["JavaScript", "Python", "TypeScript"],
  "isActive": true
}</textarea>
          <div class="validation-status status-valid" id="validationStatus">Valid Object</div>
        </div>
        
        <button id="updateObjectButton">Update Object</button>
      </div>
      
      <div class="object-visualization" id="objectVisualization">
        <div class="object-box">
          <div class="object-title">Object</div>
          <div class="object-content" id="objectContent">
            <div class="object-property">
              <span class="property-key">name:</span>
              <span class="property-value property-string">"Alice"</span>
            </div>
            <div class="object-property">
              <span class="property-key">age:</span>
              <span class="property-value property-number">28</span>
            </div>
            <!-- More properties will be added dynamically -->
          </div>
        </div>
      </div>
    </div>
    
    <div class="card">
      <h2>Method Results</h2>
      
      <div class="method-tabs">
        <div class="method-tab active" data-method="keys">Object.keys()</div>
        <div class="method-tab" data-method="values">Object.values()</div>
        <div class="method-tab" data-method="entries">Object.entries()</div>
      </div>
      
      <div class="method-content active" data-content="keys">
        <div class="method-description">
          <p><code class="code-inline">Object.keys(obj)</code> returns an array of a given object's own enumerable string-keyed property names.</p>
        </div>
        
        <div class="output-header">
          <h3>Results</h3>
          <div class="output-controls">
            <div class="output-format-toggle">
              <button class="format-option active" data-format="pretty" data-method="keys">Pretty</button>
              <button class="format-option" data-format="raw" data-method="keys">Raw</button>
            </div>
            <button class="copy-btn" data-copy="keys">Copy</button>
          </div>
        </div>
        
        <div class="object-result" id="keysResult">
          <!-- Will be populated dynamically -->
        </div>
        
        <pre class="code-display" id="keysCode">
const keys = Object.keys(obj);
console.log(keys); // ["name", "age", "occupation", "languages", "isActive"]</pre>
      </div>
      
      <div class="method-content" data-content="values">
        <div class="method-description">
          <p><code class="code-inline">Object.values(obj)</code> returns an array of a given object's own enumerable string-keyed property values.</p>
        </div>
        
        <div class="output-header">
          <h3>Results</h3>
          <div class="output-controls">
            <div class="output-format-toggle">
              <button class="format-option active" data-format="pretty" data-method="values">Pretty</button>
              <button class="format-option" data-format="raw" data-method="values">Raw</button>
            </div>
            <button class="copy-btn" data-copy="values">Copy</button>
          </div>
        </div>
        
        <div class="object-result" id="valuesResult">
          <!-- Will be populated dynamically -->
        </div>
        
        <pre class="code-display" id="valuesCode">
const values = Object.values(obj);
console.log(values); // ["Alice", 28, "Software Engineer", ["JavaScript", "Python", "TypeScript"], true]</pre>
      </div>
      
      <div class="method-content" data-content="entries">
        <div class="method-description">
          <p><code class="code-inline">Object.entries(obj)</code> returns an array of a given object's own enumerable string-keyed property [key, value] pairs.</p>
        </div>
        
        <div class="output-header">
          <h3>Results</h3>
          <div class="output-controls">
            <div class="output-format-toggle">
              <button class="format-option active" data-format="pretty" data-method="entries">Pretty</button>
              <button class="format-option" data-format="raw" data-method="entries">Raw</button>
            </div>
            <button class="copy-btn" data-copy="entries">Copy</button>
          </div>
        </div>
        
        <div class="object-result" id="entriesResult">
          <!-- Will be populated dynamically -->
        </div>
        
        <pre class="code-display" id="entriesCode">
const entries = Object.entries(obj);
console.log(entries); 
// [
//   ["name", "Alice"],
//   ["age", 28],
//   ["occupation", "Software Engineer"],
//   ["languages", ["JavaScript", "Python", "TypeScript"]],
//   ["isActive", true]
// ]</pre>
      </div>
      
      <div class="object-visualization" id="transformVisualization">
        <div class="transform-arrow">
          ⬇️ Transformation ⬇️
        </div>
        <div class="object-box">
          <div class="object-title" id="transformTitle">Array of Keys</div>
          <div class="object-content" id="transformContent">
            <!-- Will be populated dynamically -->
          </div>
        </div>
      </div>
    </div>
  </div>
  
  <div class="container">
    <div class="card">
      <h2>Practical Examples</h2>
      
      <div class="method-tabs">
        <div class="method-tab active" data-example="filter">Filtering Objects</div>
        <div class="method-tab" data-example="transform">Transforming Data</div>
        <div class="method-tab" data-example="combine">Combining Methods</div>
      </div>
      
      <div class="method-content active" data-example-content="filter">
        <div class="method-description">
          <p>Using <code class="code-inline">Object.entries()</code> with array methods to filter objects by value.</p>
        </div>
        
        <pre class="code-display">
// Filter an object to only include properties with numeric values
function filterNumericValues(obj) {
  return Object.fromEntries(
    Object.entries(obj).filter(([key, value]) => typeof value === 'number')
  );
}

const person = {
  name: "Alice",
  age: 28,
  height: 165,
  profession: "Engineer",
  yearsExperience: 5
};

const numericProperties = filterNumericValues(person);
console.log(numericProperties); // { age: 28, height: 165, yearsExperience: 5 }</pre>
      </div>
      
      <div class="method-content" data-example-content="transform">
        <div class="method-description">
          <p>Using <code class="code-inline">Object.entries()</code> and <code class="code-inline">Object.fromEntries()</code> to transform objects.</p>
        </div>
        
        <pre class="code-display">
// Convert all string values in an object to uppercase
function uppercaseStringValues(obj) {
  return Object.fromEntries(
    Object.entries(obj).map(([key, value]) => {
      return [key, typeof value === 'string' ? value.toUpperCase() : value];
    })
  );
}

const product = {
  id: 123,
  name: "Wireless Headphones",
  brand: "AudioTech",
  price: 99.99,
  inStock: true
};

const uppercaseProduct = uppercaseStringValues(product);
console.log(uppercaseProduct);
// { id: 123, name: "WIRELESS HEADPHONES", brand: "AUDIOTECH", price: 99.99, inStock: true }</pre>
      </div>
      
      <div class="method-content" data-example-content="combine">
        <div class="method-description">
          <p>Combining Object methods with array methods for powerful data transformations.</p>
        </div>
        
        <pre class="code-display">
// Group an array of objects by a property
function groupBy(array, key) {
  return array.reduce((result, item) => {
    // Get the value of the key we want to group by
    const groupKey = item[key];
    
    // If this is a new group, create an array for it
    if (!result[groupKey]) {
      result[groupKey] = [];
    }
    
    // Add this item to the appropriate group
    result[groupKey].push(item);
    
    return result;
  }, {});
}

const employees = [
  { id: 1, name: "Alice", department: "Engineering" },
  { id: 2, name: "Bob", department: "Marketing" },
  { id: 3, name: "Charlie", department: "Engineering" },
  { id: 4, name: "Diana", department: "HR" },
  { id: 5, name: "Eve", department: "Marketing" }
];

const byDepartment = groupBy(employees, "department");
console.log(Object.keys(byDepartment)); // ["Engineering", "Marketing", "HR"]
console.log(byDepartment.Engineering.length); // 2
console.log(byDepartment.Marketing.map(emp => emp.name)); // ["Bob", "Eve"]</pre>
      </div>
    </div>
    
    <div class="card">
      <h2>Use Cases and Patterns</h2>
      
      <h3>When to use each method</h3>
      <ul>
        <li><strong>Object.keys():</strong> When you only need to work with the property names of an object</li>
        <li><strong>Object.values():</strong> When you only need the values and don't care about the property names</li>
        <li><strong>Object.entries():</strong> When you need both property names and their values, especially for transformations</li>
      </ul>
      
      <h3>Common Patterns</h3>
      <ul>
        <li><strong>Checking for properties:</strong> <code class="code-inline">Object.keys(obj).includes('propertyName')</code></li>
        <li><strong>Computing statistics:</strong> <code class="code-inline">Object.values(obj).reduce((sum, val) => sum + val, 0)</code></li>
        <li><strong>Creating a new object:</strong> <code class="code-inline">Object.fromEntries(Object.entries(obj).map(([k, v]) => [k, transform(v)]))</code></li>
        <li><strong>Filtering an object:</strong> <code class="code-inline">Object.fromEntries(Object.entries(obj).filter(([k, v]) => condition(k, v)))</code></li>
      </ul>
      
      <div class="method-description">
        <p>These methods greatly simplify working with objects in JavaScript, reducing the need for explicit loops and temporary variables. Combined with array methods like <code class="code-inline">map()</code>, <code class="code-inline">filter()</code>, and <code class="code-inline">reduce()</code>, they enable powerful functional programming patterns.</p>
      </div>
    </div>
  </div>
---css
/* Dark mode awareness */
    :root {
      --bg-color: #fff;
      --text-color: #333;
      --card-bg: #f0f0f0;
      --highlight: #4a90e2;
      --secondary: #9b59b6;
      --success: #2ecc71;
      --error: #e74c3c;
      --warning: #f39c12;
      --info: #3498db;
      --border: #ddd;
      --code-bg: rgba(0, 0, 0, 0.05);
      --key-color: #e67e22;
      --value-color: #3498db;
      --string-color: #27ae60;
      --number-color: #9b59b6;
      --boolean-color: #f1c40f;
      --null-color: #7f8c8d;
      --property-bg: rgba(0, 0, 0, 0.02);
      --tab-active-bg: rgba(52, 152, 219, 0.1);
    }
    
    @media (prefers-color-scheme: dark) {
      :root {
        --bg-color: #1a1a1a;
        --text-color: #e0e0e0;
        --card-bg: #2a2a2a;
        --highlight: #5a9cf0;
        --secondary: #bb6aef;
        --success: #3ee887;
        --error: #ff6b6b;
        --warning: #ffb142;
        --info: #54a0ff;
        --border: #444;
        --code-bg: rgba(255, 255, 255, 0.05);
        --key-color: #ffb142;
        --value-color: #5a9cf0;
        --string-color: #3ee887;
        --number-color: #bb6aef;
        --boolean-color: #f9ca24;
        --null-color: #a0a0a0;
        --property-bg: rgba(255, 255, 255, 0.03);
        --tab-active-bg: rgba(90, 156, 240, 0.15);
      }
    }
    
    body {
      font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
      background-color: var(--bg-color);
      color: var(--text-color);
      max-width: 900px;
      margin: 0 auto;
      padding: 20px;
      transition: background-color 0.3s, color 0.3s;
    }
    
    h1, h2, h3 {
      color: var(--highlight);
    }
    
    .container {
      display: grid;
      grid-template-columns: 1fr 1fr;
      gap: 20px;
      margin-top: 20px;
    }
    
    .card {
      background-color: var(--card-bg);
      border-radius: 8px;
      padding: 20px;
      box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
    }
    
    .object-editor {
      display: flex;
      flex-direction: column;
    }
    
    .method-display {
      margin-top: 20px;
    }
    
    .method-tabs {
      display: flex;
      border-bottom: 1px solid var(--border);
      margin-bottom: 10px;
    }
    
    .method-tab {
      padding: 10px 15px;
      cursor: pointer;
      border-bottom: 3px solid transparent;
      transition: all 0.2s;
    }
    
    .method-tab:hover {
      background-color: rgba(0, 0, 0, 0.03);
    }
    
    .method-tab.active {
      color: var(--highlight);
      border-bottom-color: var(--highlight);
      background-color: var(--tab-active-bg);
    }
    
    .method-content {
      display: none;
      padding: 15px;
      background-color: var(--code-bg);
      border-radius: 5px;
      overflow-x: auto;
    }
    
    .method-content.active {
      display: block;
    }
    
    .code-display {
      font-family: 'Courier New', monospace;
      white-space: pre;
      line-height: 1.5;
    }
    
    .text-area-wrapper {
      position: relative;
      margin-bottom: 15px;
    }
    
    textarea {
      width: 100%;
      min-height: 200px;
      padding: 15px;
      border-radius: 5px;
      border: 1px solid var(--border);
      background-color: var(--code-bg);
      color: var(--text-color);
      font-family: 'Courier New', monospace;
      font-size: 14px;
      resize: vertical;
      box-sizing: border-box;
    }
    
    .validation-status {
      position: absolute;
      bottom: 10px;
      right: 10px;
      padding: 5px 10px;
      border-radius: 3px;
      font-size: 12px;
    }
    
    .status-valid {
      background-color: var(--success);
      color: white;
    }
    
    .status-invalid {
      background-color: var(--error);
      color: white;
    }
    
    button {
      background-color: var(--highlight);
      color: white;
      border: none;
      border-radius: 4px;
      padding: 10px 15px;
      cursor: pointer;
      transition: opacity 0.2s;
      margin-top: 10px;
    }
    
    button:hover {
      opacity: 0.9;
    }
    
    button.secondary {
      background-color: var(--secondary);
    }
    
    .example-btn-container {
      display: flex;
      flex-wrap: wrap;
      gap: 10px;
      margin-top: 10px;
      margin-bottom: 20px;
    }
    
    .example-btn {
      background-color: var(--info);
      padding: 5px 10px;
      font-size: 12px;
    }
    
    .output-header {
      display: flex;
      justify-content: space-between;
      align-items: center;
      margin-bottom: 10px;
    }
    
    .output-controls {
      display: flex;
      gap: 10px;
    }
    
    .copy-btn {
      background-color: var(--info);
      padding: 5px 10px;
      font-size: 12px;
    }
    
    .output-format-toggle {
      display: flex;
      border: 1px solid var(--border);
      border-radius: 4px;
      overflow: hidden;
    }
    
    .format-option {
      padding: 5px 10px;
      font-size: 12px;
      cursor: pointer;
      background-color: transparent;
      border: none;
      color: var(--text-color);
    }
    
    .format-option.active {
      background-color: var(--highlight);
      color: white;
    }
    
    .object-result {
      margin-top: 5px;
    }
    
    .object-property {
      padding: 5px 10px;
      border-radius: 3px;
      margin-bottom: 2px;
      background-color: var(--property-bg);
      display: flex;
      word-break: break-all;
    }
    
    .property-key {
      color: var(--key-color);
      font-weight: bold;
      margin-right: 5px;
    }
    
    .property-value {
      color: var(--value-color);
    }
    
    .property-string {
      color: var(--string-color);
    }
    
    .property-number {
      color: var(--number-color);
    }
    
    .property-boolean {
      color: var(--boolean-color);
    }
    
    .property-null {
      color: var(--null-color);
    }
    
    .array-wrapper {
      border-left: 2px solid var(--border);
      margin-left: 5px;
      padding-left: 10px;
    }
    
    .method-description {
      margin-bottom: 15px;
      padding: 10px;
      background-color: rgba(0, 0, 0, 0.03);
      border-left: 3px solid var(--highlight);
      border-radius: 3px;
    }
    
    .code-inline {
      background-color: var(--code-bg);
      padding: 2px 5px;
      border-radius: 3px;
      font-family: 'Courier New', monospace;
    }
    
    @media (max-width: 768px) {
      .container {
        grid-template-columns: 1fr;
      }
    }
    
    .toggle-mode {
      position: absolute;
      top: 20px;
      right: 20px;
      background: none;
      border: 2px solid var(--highlight);
      color: var(--highlight);
      font-size: 14px;
    }
    
    .object-visualization {
      margin-top: 20px;
      padding: 15px;
      background-color: var(--code-bg);
      border-radius: 5px;
    }
    
    .object-box {
      border: 2px solid var(--highlight);
      border-radius: 5px;
      padding: 10px;
      margin-bottom: 20px;
    }
    
    .object-title {
      text-align: center;
      margin-bottom: 10px;
      font-weight: bold;
      color: var(--highlight);
    }
    
    .transform-arrow {
      text-align: center;
      font-size: 24px;
      margin: 15px 0;
      color: var(--secondary);
    }
---js
// Check for system preference and set initial mode
if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) {
  document.documentElement.setAttribute('data-theme', 'dark');
}

// Method tabs handling
const methodTabs = document.querySelectorAll('.method-tab[data-method]');
const methodContents = document.querySelectorAll('.method-content[data-content]');

methodTabs.forEach(tab => {
  tab.addEventListener('click', () => {
    const method = tab.getAttribute('data-method');

    // Update active tab
    methodTabs.forEach(t => t.classList.remove('active'));
    tab.classList.add('active');

    // Update active content
    methodContents.forEach(content => {
      if (content.getAttribute('data-content') === method) {
        content.classList.add('active');
      } else {
        content.classList.remove('active');
      }
    });

    // Update transformation visualization
    updateTransformVisualization(method);
  });
});

// Example tabs handling
const exampleTabs = document.querySelectorAll('.method-tab[data-example]');
const exampleContents = document.querySelectorAll('.method-content[data-example-content]');

exampleTabs.forEach(tab => {
  tab.addEventListener('click', () => {
    const example = tab.getAttribute('data-example');

    // Update active tab
    exampleTabs.forEach(t => t.classList.remove('active'));
    tab.classList.add('active');

    // Update active content
    exampleContents.forEach(content => {
      if (content.getAttribute('data-example-content') === example) {
        content.classList.add('active');
      } else {
        content.classList.remove('active');
      }
    });
  });
});

// Format toggle handling
const formatButtons = document.querySelectorAll('.format-option');

formatButtons.forEach(button => {
  button.addEventListener('click', () => {
    const format = button.getAttribute('data-format');
    const method = button.getAttribute('data-method');

    // Update active format
    document.querySelectorAll(`.format-option[data-method="${method}"]`).forEach(b => {
      b.classList.remove('active');
    });
    button.classList.add('active');

    // Update display format
    updateDisplay(method, format);
  });
});

// Copy button handling
const copyButtons = document.querySelectorAll('.copy-btn');

copyButtons.forEach(button => {
  button.addEventListener('click', () => {
    const method = button.getAttribute('data-copy');
    const resultElement = document.getElementById(`${method}Result`);
    const text = resultElement.textContent;

    navigator.clipboard.writeText(text).then(() => {
      const originalText = button.textContent;
      button.textContent = 'Copied!';
      setTimeout(() => {
        button.textContent = originalText;
      }, 2000);
    });
  });
});

// Object input handling
const objectInput = document.getElementById('objectInput');
const validationStatus = document.getElementById('validationStatus');
const updateObjectButton = document.getElementById('updateObjectButton');

// Example objects
const exampleObjects = {
  person: `{
  "name": "Alice",
  "age": 28,
  "occupation": "Software Engineer",
  "languages": ["JavaScript", "Python", "TypeScript"],
  "isActive": true
}`,
  product: `{
  "id": "P12345",
  "name": "Wireless Headphones",
  "price": 129.99,
  "category": "Electronics",
  "inStock": true,
  "specifications": {
    "brand": "AudioTech",
    "color": "Black",
    "bluetooth": "5.0"
  }
}`,
  nested: `{
  "company": "TechCorp",
  "departments": {
    "engineering": {
      "employees": 50,
      "manager": "Bob Smith",
      "projects": ["Alpha", "Beta"]
    },
    "marketing": {
      "employees": 20,
      "manager": "Jane Doe",
      "campaigns": ["Summer Sale", "Product Launch"]
    }
  },
  "founded": 2010
}`,
  types: `{
  "string": "Hello World",
  "number": 42,
  "boolean": true,
  "null": null,
  "array": [1, 2, 3],
  "object": {"nested": "value"},
  "emptyArray": [],
  "zero": 0,
  "emptyString": ""
}`,
  empty: `{}` };


// Example buttons
const exampleButtons = document.querySelectorAll('.example-btn');

exampleButtons.forEach(button => {
  button.addEventListener('click', () => {
    const example = button.getAttribute('data-example');
    objectInput.value = exampleObjects[example];
    validateAndUpdateObject();
  });
});

// Validate JSON input
function validateObject(jsonString) {
  try {
    JSON.parse(jsonString);
    return { valid: true, object: JSON.parse(jsonString) };
  } catch (error) {
    return { valid: false, error: error.message };
  }
}

// Input validation
objectInput.addEventListener('input', () => {
  const result = validateObject(objectInput.value);

  if (result.valid) {
    validationStatus.textContent = 'Valid Object';
    validationStatus.className = 'validation-status status-valid';
  } else {
    validationStatus.textContent = 'Invalid Object';
    validationStatus.className = 'validation-status status-invalid';
  }
});

// Update object button
updateObjectButton.addEventListener('click', validateAndUpdateObject);

function validateAndUpdateObject() {
  const result = validateObject(objectInput.value);

  if (result.valid) {
    updateObjectDisplay(result.object);
    updateResultDisplays(result.object);
  }
}

// Update the object visualization
function updateObjectDisplay(obj) {
  const objectContent = document.getElementById('objectContent');
  objectContent.innerHTML = '';

  if (Object.keys(obj).length === 0) {
    const emptyMessage = document.createElement('div');
    emptyMessage.textContent = '{ } - Empty Object';
    emptyMessage.style.textAlign = 'center';
    emptyMessage.style.color = 'var(--text-color)';
    emptyMessage.style.fontStyle = 'italic';
    objectContent.appendChild(emptyMessage);
    return;
  }

  for (const [key, value] of Object.entries(obj)) {
    const property = document.createElement('div');
    property.className = 'object-property';

    const keySpan = document.createElement('span');
    keySpan.className = 'property-key';
    keySpan.textContent = `${key}:`;

    const valueSpan = document.createElement('span');
    valueSpan.className = 'property-value';

    // Add appropriate styling based on value type
    if (typeof value === 'string') {
      valueSpan.classList.add('property-string');
      valueSpan.textContent = `"${value}"`;
    } else if (typeof value === 'number') {
      valueSpan.classList.add('property-number');
      valueSpan.textContent = value;
    } else if (typeof value === 'boolean') {
      valueSpan.classList.add('property-boolean');
      valueSpan.textContent = value;
    } else if (value === null) {
      valueSpan.classList.add('property-null');
      valueSpan.textContent = 'null';
    } else if (Array.isArray(value)) {
      valueSpan.textContent = '[';

      const arrayWrapper = document.createElement('div');
      arrayWrapper.className = 'array-wrapper';

      for (let i = 0; i < value.length; i++) {
        const arrayItem = document.createElement('div');
        arrayItem.className = 'object-property';

        const itemSpan = document.createElement('span');

        if (typeof value[i] === 'string') {
          itemSpan.className = 'property-value property-string';
          itemSpan.textContent = `"${value[i]}"${i < value.length - 1 ? ',' : ''}`;
        } else if (typeof value[i] === 'number') {
          itemSpan.className = 'property-value property-number';
          itemSpan.textContent = `${value[i]}${i < value.length - 1 ? ',' : ''}`;
        } else if (typeof value[i] === 'boolean') {
          itemSpan.className = 'property-value property-boolean';
          itemSpan.textContent = `${value[i]}${i < value.length - 1 ? ',' : ''}`;
        } else if (value[i] === null) {
          itemSpan.className = 'property-value property-null';
          itemSpan.textContent = `null${i < value.length - 1 ? ',' : ''}`;
        } else if (typeof value[i] === 'object') {
          itemSpan.className = 'property-value';
          itemSpan.textContent = `{...}${i < value.length - 1 ? ',' : ''}`;
        }

        arrayItem.appendChild(itemSpan);
        arrayWrapper.appendChild(arrayItem);
      }

      if (value.length === 0) {
        const emptyArray = document.createElement('div');
        emptyArray.textContent = 'Empty Array';
        emptyArray.style.color = 'var(--text-color)';
        emptyArray.style.fontStyle = 'italic';
        arrayWrapper.appendChild(emptyArray);
      }

      const closingBracket = document.createElement('span');
      closingBracket.textContent = ']';

      valueSpan.appendChild(arrayWrapper);
      valueSpan.appendChild(closingBracket);
    } else if (typeof value === 'object') {
      valueSpan.textContent = '{...}';
    }

    property.appendChild(keySpan);
    property.appendChild(valueSpan);
    objectContent.appendChild(property);
  }
}

// Update all result displays
function updateResultDisplays(obj) {
  // Get method results
  const keys = Object.keys(obj);
  const values = Object.values(obj);
  const entries = Object.entries(obj);

  // Update code examples
  document.getElementById('keysCode').textContent = `const keys = Object.keys(obj);
console.log(keys); // ${JSON.stringify(keys)}`;

  document.getElementById('valuesCode').textContent = `const values = Object.values(obj);
console.log(values); // ${JSON.stringify(values)}`;

  document.getElementById('entriesCode').textContent = `const entries = Object.entries(obj);
console.log(entries); 
// ${JSON.stringify(entries, null, 2).replace(/],/g, '],\n //').replace('[[', '[\n //  [').replace(']]', ']\n //]')}`;

  // Update displays with current format
  const keysFormat = document.querySelector('.format-option[data-method="keys"].active').getAttribute('data-format');
  const valuesFormat = document.querySelector('.format-option[data-method="values"].active').getAttribute('data-format');
  const entriesFormat = document.querySelector('.format-option[data-method="entries"].active').getAttribute('data-format');

  updateDisplay('keys', keysFormat);
  updateDisplay('values', valuesFormat);
  updateDisplay('entries', entriesFormat);

  // Update active method visualization
  const activeMethod = document.querySelector('.method-tab[data-method].active').getAttribute('data-method');
  updateTransformVisualization(activeMethod);
}

// Update display format for a specific method
function updateDisplay(method, format) {
  const resultElement = document.getElementById(`${method}Result`);
  const obj = validateObject(objectInput.value).object;

  let result;
  if (method === 'keys') result = Object.keys(obj);else
  if (method === 'values') result = Object.values(obj);else
  if (method === 'entries') result = Object.entries(obj);

  if (format === 'raw') {
    resultElement.innerHTML = `<pre>${JSON.stringify(result)}</pre>`;
  } else {
    // Pretty format
    resultElement.innerHTML = '';

    if (result.length === 0) {
      const emptyMessage = document.createElement('div');
      emptyMessage.textContent = '[] - Empty Array';
      emptyMessage.style.textAlign = 'center';
      emptyMessage.style.color = 'var(--text-color)';
      emptyMessage.style.fontStyle = 'italic';
      resultElement.appendChild(emptyMessage);
      return;
    }

    if (method === 'keys' || method === 'values') {
      for (let i = 0; i < result.length; i++) {
        const item = document.createElement('div');
        item.className = 'object-property';

        const valueSpan = document.createElement('span');

        if (typeof result[i] === 'string') {
          valueSpan.className = 'property-value property-string';
          valueSpan.textContent = `"${result[i]}"`;
        } else if (typeof result[i] === 'number') {
          valueSpan.className = 'property-value property-number';
          valueSpan.textContent = result[i];
        } else if (typeof result[i] === 'boolean') {
          valueSpan.className = 'property-value property-boolean';
          valueSpan.textContent = result[i];
        } else if (result[i] === null) {
          valueSpan.className = 'property-value property-null';
          valueSpan.textContent = 'null';
        } else if (Array.isArray(result[i])) {
          valueSpan.className = 'property-value';
          valueSpan.textContent = JSON.stringify(result[i]);
        } else if (typeof result[i] === 'object') {
          valueSpan.className = 'property-value';
          valueSpan.textContent = JSON.stringify(result[i]);
        }

        item.appendChild(valueSpan);
        resultElement.appendChild(item);
      }
    } else if (method === 'entries') {
      for (let i = 0; i < result.length; i++) {
        const entry = result[i];
        const item = document.createElement('div');
        item.className = 'object-property';

        const keySpan = document.createElement('span');
        keySpan.className = 'property-key';
        keySpan.textContent = `"${entry[0]}": `;

        const valueSpan = document.createElement('span');

        if (typeof entry[1] === 'string') {
          valueSpan.className = 'property-value property-string';
          valueSpan.textContent = `"${entry[1]}"`;
        } else if (typeof entry[1] === 'number') {
          valueSpan.className = 'property-value property-number';
          valueSpan.textContent = entry[1];
        } else if (typeof entry[1] === 'boolean') {
          valueSpan.className = 'property-value property-boolean';
          valueSpan.textContent = entry[1];
        } else if (entry[1] === null) {
          valueSpan.className = 'property-value property-null';
          valueSpan.textContent = 'null';
        } else if (Array.isArray(entry[1])) {
          valueSpan.className = 'property-value';
          valueSpan.textContent = JSON.stringify(entry[1]);
        } else if (typeof entry[1] === 'object') {
          valueSpan.className = 'property-value';
          valueSpan.textContent = JSON.stringify(entry[1]);
        }

        item.appendChild(keySpan);
        item.appendChild(valueSpan);
        resultElement.appendChild(item);
      }
    }
  }
}

// Update transformation visualization
function updateTransformVisualization(method) {
  const transformTitle = document.getElementById('transformTitle');
  const transformContent = document.getElementById('transformContent');
  const obj = validateObject(objectInput.value).object;

  let result;
  if (method === 'keys') {
    transformTitle.textContent = 'Array of Keys';
    result = Object.keys(obj);
  } else if (method === 'values') {
    transformTitle.textContent = 'Array of Values';
    result = Object.values(obj);
  } else if (method === 'entries') {
    transformTitle.textContent = 'Array of [key, value] Pairs';
    result = Object.entries(obj);
  }

  transformContent.innerHTML = '';

  if (result.length === 0) {
    const emptyMessage = document.createElement('div');
    emptyMessage.textContent = '[] - Empty Array';
    emptyMessage.style.textAlign = 'center';
    emptyMessage.style.color = 'var(--text-color)';
    emptyMessage.style.fontStyle = 'italic';
    transformContent.appendChild(emptyMessage);
    return;
  }

  if (method === 'keys' || method === 'values') {
    result.forEach((item, index) => {
      const arrayItem = document.createElement('div');
      arrayItem.className = 'object-property';

      const indexSpan = document.createElement('span');
      indexSpan.className = 'property-key';
      indexSpan.textContent = `[${index}]: `;

      const valueSpan = document.createElement('span');

      if (typeof item === 'string') {
        valueSpan.className = 'property-value property-string';
        valueSpan.textContent = `"${item}"`;
      } else if (typeof item === 'number') {
        valueSpan.className = 'property-value property-number';
        valueSpan.textContent = item;
      } else if (typeof item === 'boolean') {
        valueSpan.className = 'property-value property-boolean';
        valueSpan.textContent = item;
      } else if (item === null) {
        valueSpan.className = 'property-value property-null';
        valueSpan.textContent = 'null';
      } else if (Array.isArray(item)) {
        valueSpan.className = 'property-value';
        valueSpan.textContent = JSON.stringify(item);
      } else if (typeof item === 'object') {
        valueSpan.className = 'property-value';
        valueSpan.textContent = JSON.stringify(item);
      }

      arrayItem.appendChild(indexSpan);
      arrayItem.appendChild(valueSpan);
      transformContent.appendChild(arrayItem);
    });
  } else if (method === 'entries') {
    result.forEach((entry, index) => {
      const arrayItem = document.createElement('div');
      arrayItem.className = 'object-property';

      const indexSpan = document.createElement('span');
      indexSpan.className = 'property-key';
      indexSpan.textContent = `[${index}]: `;

      const valueSpan = document.createElement('span');
      valueSpan.className = 'property-value';
      valueSpan.textContent = `["${entry[0]}", `;

      const innerValueSpan = document.createElement('span');

      if (typeof entry[1] === 'string') {
        innerValueSpan.className = 'property-string';
        innerValueSpan.textContent = `"${entry[1]}"`;
      } else if (typeof entry[1] === 'number') {
        innerValueSpan.className = 'property-number';
        innerValueSpan.textContent = entry[1];
      } else if (typeof entry[1] === 'boolean') {
        innerValueSpan.className = 'property-boolean';
        innerValueSpan.textContent = entry[1];
      } else if (entry[1] === null) {
        innerValueSpan.className = 'property-null';
        innerValueSpan.textContent = 'null';
      } else if (Array.isArray(entry[1]) || typeof entry[1] === 'object') {
        innerValueSpan.textContent = JSON.stringify(entry[1]);
      }

      valueSpan.appendChild(innerValueSpan);
      valueSpan.appendChild(document.createTextNode(']'));

      arrayItem.appendChild(indexSpan);
      arrayItem.appendChild(valueSpan);
      transformContent.appendChild(arrayItem);
    });
  }
}

// Initialize with default object
validateAndUpdateObject();

Object.getOwnPropertyDescriptors() (optional)#

This one is for later; skip to String Padding if you are new to JavaScript. It returns detailed information about object properties, including their configurability, writability, and getter/setter functions:

const obj = {
  get name() { return 'Alice'; },
  set age(value) { this._age = value; }
};

const descriptors = Object.getOwnPropertyDescriptors(obj);
/*
{
  name: {
    get: [Function: get name],
    set: undefined,
    enumerable: true,
    configurable: true
  },
  age: {
    get: undefined,
    set: [Function: set age],
    enumerable: true,
    configurable: true
  }
}
*/

// Perfect for copying (shallowly) objects with getters/setters
const clone = Object.defineProperties({}, 
  Object.getOwnPropertyDescriptors(obj));
Property Descriptor Explorer
---html
<h1>Property Descriptor Explorer</h1>
  <p>Explore and modify JavaScript object property descriptors using ES8's <code>Object.getOwnPropertyDescriptors()</code>.</p>
  
  <div class="example-btn-container">
    <button class="example-btn" data-example="basic">Basic Object</button>
    <button class="example-btn" data-example="gettersSetters">Getters & Setters</button>
    <button class="example-btn" data-example="nonEnumerable">Non-Enumerable Properties</button>
    <button class="example-btn" data-example="frozen">Frozen Object</button>
  </div>
  
  <div class="container">
    <div class="card">
      <h2>Object Definition</h2>
      
      <div class="text-area-wrapper">
        <textarea id="objectDefinition" placeholder="Enter JavaScript object definition">
const obj = {
  name: "Alice",
  age: 30,
  get fullName() {
    return `${this.name} Smith`;
  },
  set fullName(value) {
    this.name = value.split(" ")[0];
  }
};

// Non-enumerable property
Object.defineProperty(obj, "id", {
  value: 12345,
  enumerable: false
});

return obj;</textarea>
        <div class="validation-status status-valid" id="validationStatus">Valid Code</div>
      </div>
      
      <button id="parseObjectButton">Parse Object</button>
      
      <div class="object-actions">
        <button id="getDescriptorsButton">Get All Descriptors</button>
        <button id="cloneObjectButton" class="secondary">Clone Object</button>
      </div>
      
      <div class="output-console" id="outputConsole">
        <div class="console-line console-info">> Object ready for inspection</div>
      </div>
    </div>
    
    <div class="card">
      <h2>Property Descriptors</h2>
      
      <div class="property-list" id="propertyList">
        <!-- Property items will be added dynamically -->
      </div>
      
      <div class="descriptor-editor" id="descriptorEditor">
        <h3>Edit Property: <span id="currentProperty">-</span></h3>
        
        <div class="descriptor-form" id="descriptorForm">
          <div class="descriptor-group" id="valueGroup">
            <label for="propertyValue">Value:</label>
            <input type="text" id="propertyValue" placeholder="Property value">
          </div>
          
          <div class="descriptor-group" id="getterGroup" style="display: none;">
            <label for="propertyGetter">Getter Function:</label>
            <textarea id="propertyGetter" placeholder="Return value"></textarea>
          </div>
          
          <div class="descriptor-group" id="setterGroup" style="display: none;">
            <label for="propertySetter">Setter Function:</label>
            <textarea id="propertySetter" placeholder="Set value"></textarea>
          </div>
          
          <div class="descriptor-group">
            <div class="checkbox-wrapper">
              <input type="checkbox" id="enumerable" checked>
              <label for="enumerable">
                Enumerable
                <span class="tooltip">?
                  <span class="tooltip-text">When true, the property appears during enumeration of the object (for...in loops, Object.keys())</span>
                </span>
              </label>
            </div>
            
            <div class="checkbox-wrapper">
              <input type="checkbox" id="writable" checked>
              <label for="writable">
                Writable
                <span class="tooltip">?
                  <span class="tooltip-text">When true, the property value can be changed</span>
                </span>
              </label>
            </div>
            
            <div class="checkbox-wrapper">
              <input type="checkbox" id="configurable" checked>
              <label for="configurable">
                Configurable
                <span class="tooltip">?
                  <span class="tooltip-text">When true, the property descriptor can be changed and the property can be deleted</span>
                </span>
              </label>
            </div>
          </div>
          
          <div class="descriptor-actions">
            <button id="updatePropertyButton">Update Property</button>
            <button id="cancelEditButton" class="secondary">Cancel</button>
          </div>
        </div>
      </div>
      
      <div class="cloned-object" id="clonedObject" style="display: none;">
        <div class="clone-header">
          <h3>Cloned Object</h3>
          <button id="testCloneButton" class="warning">Test Clone</button>
        </div>
        
        <div class="code-display" id="cloneDisplay">
// Clone created using getOwnPropertyDescriptors
const clone = Object.defineProperties(
  {},
  Object.getOwnPropertyDescriptors(originalObj)
);

// Perfect clone with all property attributes preserved</div>
      </div>
    </div>
  </div>
  
  <div class="container">
    <div class="card">
      <h2>What are Property Descriptors?</h2>
      
      <p>In JavaScript, every object property has a descriptor that controls its behavior. A property descriptor contains these attributes:</p>
      
      <ul>
        <li><strong>value</strong> - The value associated with the property</li>
        <li><strong>writable</strong> - Whether the property's value can be changed</li>
        <li><strong>enumerable</strong> - Whether the property appears in enumeration (for...in, Object.keys())</li>
        <li><strong>configurable</strong> - Whether the property can be deleted or have its descriptor changed</li>
        <li><strong>get</strong> - Getter function that returns the property value</li>
        <li><strong>set</strong> - Setter function that sets the property value</li>
      </ul>
      
      <p>The <code>Object.getOwnPropertyDescriptors()</code> method returns all property descriptors of an object, including non-enumerable ones.</p>
      
      <div class="code-display">
// Basic descriptor example
Object.getOwnPropertyDescriptor(obj, 'name');
// {
//   value: "Alice",
//   writable: true,
//   enumerable: true,
//   configurable: true
// }

// Getter/setter example
Object.getOwnPropertyDescriptor(obj, 'fullName');
// {
//   get: [Function: get fullName],
//   set: [Function: set fullName],
//   enumerable: true,
//   configurable: true
// }</div>
    </div>
    
    <div class="card">
      <h2>Use Cases for getOwnPropertyDescriptors()</h2>
      
      <h3>1. Perfect Object Cloning</h3>
      <p>Unlike <code>Object.assign()</code>, this method can correctly copy getters, setters, and non-enumerable properties:</p>
      
      <div class="code-display">
// Complete clone that preserves all property attributes
const clone = Object.defineProperties(
  {},
  Object.getOwnPropertyDescriptors(originalObj)
);</div>
      
      <h3>2. Property Control</h3>
      <p>Create immutable properties or hide properties from enumeration:</p>
      
      <div class="code-display">
// Create a read-only property
Object.defineProperty(obj, 'id', {
  value: 12345,
  writable: false,
  enumerable: true,
  configurable: false
});</div>
      
      <h3>3. Mixin Implementation</h3>
      <p>Copy multiple properties from one object to another while preserving their descriptors:</p>
      
      <div class="code-display">
// Mixin pattern with correct descriptor copying
function mixin(target, ...sources) {
  for (const source of sources) {
    Object.defineProperties(
      target,
      Object.getOwnPropertyDescriptors(source)
    );
  }
  return target;
}</div>
    </div>
  </div>
---css
/* Dark mode awareness */
    :root {
      --bg-color: #fff;
      --text-color: #333;
      --card-bg: #f0f0f0;
      --highlight: #4a90e2;
      --secondary: #9b59b6;
      --success: #2ecc71;
      --error: #e74c3c;
      --warning: #f39c12;
      --info: #3498db;
      --border: #ddd;
      --code-bg: rgba(0, 0, 0, 0.05);
      --descriptor-bg: rgba(0, 0, 0, 0.02);
      --descriptor-border: rgba(0, 0, 0, 0.1);
      --checkbox-bg: #fff;
      --attribute-true: #2ecc71;
      --attribute-false: #e74c3c;
      --getter-color: #9b59b6;
      --setter-color: #f39c12;
      --clone-bg: #eafaf1;
    }
    
    @media (prefers-color-scheme: dark) {
      :root {
        --bg-color: #1a1a1a;
        --text-color: #e0e0e0;
        --card-bg: #2a2a2a;
        --highlight: #5a9cf0;
        --secondary: #bb6aef;
        --success: #3ee887;
        --error: #ff6b6b;
        --warning: #ffb142;
        --info: #54a0ff;
        --border: #444;
        --code-bg: rgba(255, 255, 255, 0.05);
        --descriptor-bg: rgba(255, 255, 255, 0.03);
        --descriptor-border: rgba(255, 255, 255, 0.1);
        --checkbox-bg: #333;
        --attribute-true: #3ee887;
        --attribute-false: #ff6b6b;
        --getter-color: #bb6aef;
        --setter-color: #ffb142;
        --clone-bg: #20392b;
      }
    }
    
    body {
      font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
      background-color: var(--bg-color);
      color: var(--text-color);
      max-width: 900px;
      margin: 0 auto;
      padding: 20px;
      transition: background-color 0.3s, color 0.3s;
    }
    
    h1, h2, h3 {
      color: var(--highlight);
    }
    
    .container {
      display: grid;
      grid-template-columns: 1fr 1fr;
      gap: 20px;
      margin-top: 20px;
    }
    
    .card {
      background-color: var(--card-bg);
      border-radius: 8px;
      padding: 20px;
      box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
    }
    
    .code-display {
      background-color: var(--code-bg);
      padding: 15px;
      border-radius: 4px;
      margin: 15px 0;
      font-family: 'Courier New', monospace;
      overflow-x: auto;
      white-space: pre;
      line-height: 1.5;
    }
    
    .text-area-wrapper {
      position: relative;
      margin-bottom: 15px;
    }
    
    textarea, input[type="text"] {
      width: 100%;
      padding: 10px;
      border-radius: 5px;
      border: 1px solid var(--border);
      background-color: var(--code-bg);
      color: var(--text-color);
      font-family: 'Courier New', monospace;
      box-sizing: border-box;
    }
    
    textarea {
      min-height: 120px;
      resize: vertical;
    }
    
    .validation-status {
      position: absolute;
      bottom: 10px;
      right: 10px;
      padding: 5px 10px;
      border-radius: 3px;
      font-size: 12px;
    }
    
    .status-valid {
      background-color: var(--success);
      color: white;
    }
    
    .status-invalid {
      background-color: var(--error);
      color: white;
    }
    
    button {
      background-color: var(--highlight);
      color: white;
      border: none;
      border-radius: 4px;
      padding: 8px 15px;
      cursor: pointer;
      transition: opacity 0.2s;
      margin: 5px 0;
    }
    
    button:hover {
      opacity: 0.9;
    }
    
    button.secondary {
      background-color: var(--secondary);
    }
    
    button.warning {
      background-color: var(--warning);
    }
    
    .example-btn-container {
      display: flex;
      flex-wrap: wrap;
      gap: 10px;
      margin-top: 10px;
      margin-bottom: 20px;
    }
    
    .example-btn {
      background-color: var(--info);
      padding: 5px 10px;
      font-size: 12px;
    }
    
    .property-list {
      margin-bottom: 20px;
    }
    
    .property-item {
      display: flex;
      justify-content: space-between;
      align-items: center;
      padding: 8px 10px;
      border-radius: 4px;
      background-color: var(--descriptor-bg);
      margin-bottom: 5px;
      border: 1px solid var(--descriptor-border);
      cursor: pointer;
      transition: background-color 0.2s;
    }
    
    .property-item:hover {
      background-color: rgba(0, 0, 0, 0.05);
    }
    
    .property-item.active {
      border-color: var(--highlight);
      background-color: rgba(74, 144, 226, 0.05);
    }
    
    .property-name {
      font-weight: bold;
    }
    
    .property-attributes {
      display: flex;
      gap: 5px;
    }
    
    .property-attribute {
      padding: 2px 5px;
      border-radius: 3px;
      font-size: 11px;
      font-weight: bold;
    }
    
    .attribute-true {
      background-color: var(--attribute-true);
      color: white;
    }
    
    .attribute-false {
      background-color: var(--attribute-false);
      color: white;
    }
    
    .attribute-getter {
      background-color: var(--getter-color);
      color: white;
    }
    
    .attribute-setter {
      background-color: var(--setter-color);
      color: white;
    }
    
    .descriptor-editor {
      margin-top: 20px;
    }
    
    .descriptor-form {
      background-color: var(--descriptor-bg);
      padding: 15px;
      border-radius: 5px;
      border: 1px solid var(--descriptor-border);
    }
    
    .descriptor-group {
      margin-bottom: 15px;
    }
    
    .descriptor-group label {
      display: block;
      margin-bottom: 5px;
      font-weight: bold;
    }
    
    .checkbox-wrapper {
      display: flex;
      align-items: center;
      margin-bottom: 10px;
    }
    
    .checkbox-wrapper input[type="checkbox"] {
      margin-right: 10px;
      width: 18px;
      height: 18px;
      background-color: var(--checkbox-bg);
    }
    
    .descriptor-actions {
      display: flex;
      gap: 10px;
      margin-top: 15px;
    }
    
    .cloned-object {
      margin-top: 20px;
      padding: 15px;
      border-radius: 5px;
      background-color: var(--clone-bg);
      border: 1px solid var(--border);
    }
    
    .clone-header {
      display: flex;
      justify-content: space-between;
      align-items: center;
      margin-bottom: 10px;
    }
    
    .object-actions {
      display: flex;
      gap: 10px;
      margin-top: 15px;
    }
    
    .output-console {
      background-color: var(--code-bg);
      padding: 15px;
      border-radius: 5px;
      margin-top: 20px;
      font-family: 'Courier New', monospace;
      min-height: 100px;
      max-height: 200px;
      overflow-y: auto;
    }
    
    .console-line {
      margin-bottom: 5px;
    }
    
    .console-success {
      color: var(--success);
    }
    
    .console-error {
      color: var(--error);
    }
    
    .console-info {
      color: var(--info);
    }
    
    .toggle-mode {
      position: absolute;
      top: 20px;
      right: 20px;
      background: none;
      border: 2px solid var(--highlight);
      color: var(--highlight);
      font-size: 14px;
    }
    
    .tooltip {
      position: relative;
      display: inline-block;
      margin-left: 5px;
      font-size: 14px;
      color: var(--info);
      cursor: help;
    }
    
    .tooltip-text {
      visibility: hidden;
      width: 200px;
      background-color: rgba(0, 0, 0, 0.8);
      color: #fff;
      text-align: center;
      border-radius: 6px;
      padding: 5px;
      position: absolute;
      z-index: 1;
      bottom: 125%;
      left: 50%;
      margin-left: -100px;
      opacity: 0;
      transition: opacity 0.3s;
      font-weight: normal;
      font-size: 12px;
    }
    
    .tooltip:hover .tooltip-text {
      visibility: visible;
      opacity: 1;
    }
    
    .property-type {
      font-size: 12px;
      color: rgba(128, 128, 128, 0.8);
      margin-left: 5px;
    }
    
    @media (max-width: 768px) {
      .container {
        grid-template-columns: 1fr;
      }
    }
---js
// Check for system preference and set initial mode
if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) {
  document.documentElement.setAttribute('data-theme', 'dark');
}

// DOM elements
const objectDefinition = document.getElementById('objectDefinition');
const validationStatus = document.getElementById('validationStatus');
const parseObjectButton = document.getElementById('parseObjectButton');
const getDescriptorsButton = document.getElementById('getDescriptorsButton');
const cloneObjectButton = document.getElementById('cloneObjectButton');
const outputConsole = document.getElementById('outputConsole');
const propertyList = document.getElementById('propertyList');
const descriptorEditor = document.getElementById('descriptorEditor');
const currentProperty = document.getElementById('currentProperty');
const propertyValue = document.getElementById('propertyValue');
const propertyGetter = document.getElementById('propertyGetter');
const propertySetter = document.getElementById('propertySetter');
const enumerableCheckbox = document.getElementById('enumerable');
const writableCheckbox = document.getElementById('writable');
const configurableCheckbox = document.getElementById('configurable');
const updatePropertyButton = document.getElementById('updatePropertyButton');
const cancelEditButton = document.getElementById('cancelEditButton');
const clonedObjectSection = document.getElementById('clonedObject');
const cloneDisplay = document.getElementById('cloneDisplay');
const testCloneButton = document.getElementById('testCloneButton');

// Example objects
const examples = {
  basic: `
const obj = {
  name: "Alice",
  age: 30,
  isActive: true,
  hobbies: ["coding", "reading", "hiking"]
};

// Return the object for inspection
return obj;`,

  gettersSetters: `
const obj = {
  firstName: "John",
  lastName: "Doe",
  
  get fullName() {
    return \`\${this.firstName} \${this.lastName}\`;
  },
  
  set fullName(value) {
    const parts = value.split(" ");
    this.firstName = parts[0];
    this.lastName = parts[1] || "";
  },
  
  age: 35,
  
  get isAdult() {
    return this.age >= 18;
  }
};

// Return the object for inspection
return obj;`,

  nonEnumerable: `
const obj = {
  name: "Public Property",
  visible: true
};

// Add non-enumerable properties
Object.defineProperty(obj, "id", {
  value: 12345,
  enumerable: false,
  writable: false,
  configurable: false
});

Object.defineProperty(obj, "createdAt", {
  value: "2023-03-15",
  enumerable: false,
  writable: true,
  configurable: true
});

// Return the object for inspection
return obj;`,

  frozen: `
const obj = {
  name: "Immutable Object",
  data: {
    value: 42,
    nested: true
  }
};

// Make a property read-only
Object.defineProperty(obj, "id", {
  value: "frozen-123",
  writable: false,
  configurable: false
});

// Make the entire object immutable
Object.freeze(obj);

// Return the object for inspection
return obj;` };


// Global variables
let currentObject = null;
let currentDescriptors = null;
let clonedObjectInstance = null;
let selectedProperty = null;

// Load example
function loadExample(example) {
  objectDefinition.value = examples[example];
  validateCode();
}

// Example buttons
const exampleButtons = document.querySelectorAll('.example-btn');
exampleButtons.forEach(button => {
  button.addEventListener('click', () => {
    const example = button.getAttribute('data-example');
    loadExample(example);
  });
});

// Validate code
function validateCode() {
  try {
    new Function(objectDefinition.value);
    validationStatus.textContent = 'Valid Code';
    validationStatus.className = 'validation-status status-valid';
    return true;
  } catch (error) {
    validationStatus.textContent = 'Invalid Code';
    validationStatus.className = 'validation-status status-invalid';
    return false;
  }
}

// Parse object
function parseObject() {
  if (!validateCode()) {
    log('Error: Invalid JavaScript code', 'error');
    return;
  }

  try {
    const createObjectFn = new Function(objectDefinition.value);
    currentObject = createObjectFn();

    log('Object parsed successfully', 'success');
    log(`Object has ${Object.keys(currentObject).length} enumerable properties`, 'info');

    // Clear property list and clone section
    propertyList.innerHTML = '';
    hideDescriptorEditor();
    clonedObjectSection.style.display = 'none';

    return true;
  } catch (error) {
    log(`Error parsing object: ${error.message}`, 'error');
    return false;
  }
}

// Get all descriptors
function getAllDescriptors() {
  if (!currentObject) {
    if (!parseObject()) return;
  }

  try {
    currentDescriptors = Object.getOwnPropertyDescriptors(currentObject);
    displayPropertyList();

    log(`Retrieved descriptors for ${Object.keys(currentDescriptors).length} properties`, 'success');
  } catch (error) {
    log(`Error getting descriptors: ${error.message}`, 'error');
  }
}

// Display property list
function displayPropertyList() {
  propertyList.innerHTML = '';

  for (const [propName, descriptor] of Object.entries(currentDescriptors)) {
    const propertyItem = document.createElement('div');
    propertyItem.className = 'property-item';
    propertyItem.setAttribute('data-property', propName);

    // Add click handler
    propertyItem.addEventListener('click', () => {
      selectProperty(propName);
    });

    const nameSpan = document.createElement('span');
    nameSpan.className = 'property-name';
    nameSpan.textContent = propName;

    // Add property type indicator
    const typeSpan = document.createElement('span');
    typeSpan.className = 'property-type';

    if ('get' in descriptor || 'set' in descriptor) {
      typeSpan.textContent = 'accessor';
    } else if (typeof descriptor.value === 'function') {
      typeSpan.textContent = 'function';
    } else if (typeof descriptor.value === 'object' && descriptor.value !== null) {
      typeSpan.textContent = Array.isArray(descriptor.value) ? 'array' : 'object';
    } else {
      typeSpan.textContent = typeof descriptor.value;
    }

    nameSpan.appendChild(typeSpan);

    const attributesDiv = document.createElement('div');
    attributesDiv.className = 'property-attributes';

    // Add getter/setter indicators
    if ('get' in descriptor) {
      const getterAttr = document.createElement('span');
      getterAttr.className = 'property-attribute attribute-getter';
      getterAttr.textContent = 'GETTER';
      attributesDiv.appendChild(getterAttr);
    }

    if ('set' in descriptor) {
      const setterAttr = document.createElement('span');
      setterAttr.className = 'property-attribute attribute-setter';
      setterAttr.textContent = 'SETTER';
      attributesDiv.appendChild(setterAttr);
    }

    // Add enumerable indicator
    const enumerableAttr = document.createElement('span');
    enumerableAttr.className = `property-attribute attribute-${descriptor.enumerable}`;
    enumerableAttr.textContent = descriptor.enumerable ? 'ENUM' : 'NON-ENUM';
    attributesDiv.appendChild(enumerableAttr);

    // Add writable indicator (only for data properties)
    if ('writable' in descriptor) {
      const writableAttr = document.createElement('span');
      writableAttr.className = `property-attribute attribute-${descriptor.writable}`;
      writableAttr.textContent = descriptor.writable ? 'WRITE' : 'READ-ONLY';
      attributesDiv.appendChild(writableAttr);
    }

    // Add configurable indicator
    const configurableAttr = document.createElement('span');
    configurableAttr.className = `property-attribute attribute-${descriptor.configurable}`;
    configurableAttr.textContent = descriptor.configurable ? 'CONFIG' : 'LOCKED';
    attributesDiv.appendChild(configurableAttr);

    propertyItem.appendChild(nameSpan);
    propertyItem.appendChild(attributesDiv);
    propertyList.appendChild(propertyItem);
  }
}

// Select a property for editing
function selectProperty(propName) {
  // Remove active class from all properties
  document.querySelectorAll('.property-item').forEach(item => {
    item.classList.remove('active');
  });

  // Add active class to selected property
  const propertyItem = document.querySelector(`.property-item[data-property="${propName}"]`);
  if (propertyItem) {
    propertyItem.classList.add('active');
  }

  selectedProperty = propName;
  currentProperty.textContent = propName;

  const descriptor = currentDescriptors[propName];

  // Show/hide appropriate fields based on property type
  const isAccessor = 'get' in descriptor || 'set' in descriptor;
  document.getElementById('valueGroup').style.display = isAccessor ? 'none' : 'block';
  document.getElementById('getterGroup').style.display = isAccessor && 'get' in descriptor ? 'block' : 'none';
  document.getElementById('setterGroup').style.display = isAccessor && 'set' in descriptor ? 'block' : 'none';

  // Set current values
  if (isAccessor) {
    if ('get' in descriptor && descriptor.get) {
      propertyGetter.value = descriptor.get.toString();
    } else {
      propertyGetter.value = 'function() {\n  // Getter code\n}';
    }

    if ('set' in descriptor && descriptor.set) {
      propertySetter.value = descriptor.set.toString();
    } else {
      propertySetter.value = 'function(value) {\n  // Setter code\n}';
    }
  } else {
    let valueStr;
    try {
      valueStr = JSON.stringify(descriptor.value);
    } catch (e) {
      valueStr = String(descriptor.value);
    }
    propertyValue.value = valueStr;
  }

  // Set checkbox values
  enumerableCheckbox.checked = descriptor.enumerable;
  writableCheckbox.checked = isAccessor ? false : descriptor.writable;
  writableCheckbox.disabled = isAccessor;
  configurableCheckbox.checked = descriptor.configurable;

  // Show the editor
  descriptorEditor.style.display = 'block';

  // Scroll to editor
  descriptorEditor.scrollIntoView({ behavior: 'smooth' });
}

// Hide the descriptor editor
function hideDescriptorEditor() {
  descriptorEditor.style.display = 'none';
  selectedProperty = null;

  // Remove active class from all properties
  document.querySelectorAll('.property-item').forEach(item => {
    item.classList.remove('active');
  });
}

// Update property
function updateProperty() {
  if (!selectedProperty || !currentObject) return;

  try {
    const descriptor = currentDescriptors[selectedProperty];
    const isAccessor = 'get' in descriptor || 'set' in descriptor;

    const newDescriptor = {
      enumerable: enumerableCheckbox.checked,
      configurable: configurableCheckbox.checked };


    if (isAccessor) {
      // For accessor properties
      if (document.getElementById('getterGroup').style.display !== 'none') {
        try {
          // Convert getter string to function
          const getterBody = propertyGetter.value;
          // Extract function body from the getter
          const getterFnBody = getterBody.substring(
          getterBody.indexOf('{') + 1,
          getterBody.lastIndexOf('}'));

          newDescriptor.get = new Function('return ' + propertyGetter.value)();
        } catch (e) {
          log(`Error parsing getter: ${e.message}`, 'error');
          return;
        }
      }

      if (document.getElementById('setterGroup').style.display !== 'none') {
        try {
          // Convert setter string to function
          newDescriptor.set = new Function('return ' + propertySetter.value)();
        } catch (e) {
          log(`Error parsing setter: ${e.message}`, 'error');
          return;
        }
      }
    } else {
      // For data properties
      try {
        newDescriptor.value = JSON.parse(propertyValue.value);
      } catch (e) {
        // If not valid JSON, use as string
        newDescriptor.value = propertyValue.value;
      }

      newDescriptor.writable = writableCheckbox.checked;
    }

    // Only redefine if configurable
    if (descriptor.configurable) {
      Object.defineProperty(currentObject, selectedProperty, newDescriptor);
      log(`Property "${selectedProperty}" updated successfully`, 'success');

      // Update descriptors and refresh display
      currentDescriptors = Object.getOwnPropertyDescriptors(currentObject);
      displayPropertyList();

      // Keep the same property selected
      selectProperty(selectedProperty);
    } else {
      log(`Cannot modify non-configurable property "${selectedProperty}"`, 'error');
    }
  } catch (error) {
    log(`Error updating property: ${error.message}`, 'error');
  }
}

// Clone the object
function cloneObject() {
  if (!currentObject || !currentDescriptors) {
    log('No object to clone', 'error');
    return;
  }

  try {
    clonedObjectInstance = Object.defineProperties(
    {},
    currentDescriptors);


    // Show cloned object section
    clonedObjectSection.style.display = 'block';

    // Update clone display
    let cloneCode = `// Clone created using getOwnPropertyDescriptors
const clone = Object.defineProperties(
  {},
  Object.getOwnPropertyDescriptors(originalObj)
);

// Original object properties:
// ${Object.keys(currentObject).join(', ')}

// Cloned object properties:
// ${Object.keys(clonedObjectInstance).join(', ')}`;

    cloneDisplay.textContent = cloneCode;

    log('Object cloned successfully with all property descriptors preserved', 'success');

    // Scroll to clone section
    clonedObjectSection.scrollIntoView({ behavior: 'smooth' });
  } catch (error) {
    log(`Error cloning object: ${error.message}`, 'error');
  }
}

// Test the cloned object
function testClone() {
  if (!clonedObjectInstance) {
    log('No cloned object to test', 'error');
    return;
  }

  log('Testing cloned object...', 'info');

  // Test for getters and setters
  for (const [propName, descriptor] of Object.entries(currentDescriptors)) {
    if ('get' in descriptor) {
      try {
        const originalValue = currentObject[propName];
        const clonedValue = clonedObjectInstance[propName];

        log(`Getter "${propName}": Original = ${originalValue}, Clone = ${clonedValue}`,
        originalValue === clonedValue ? 'success' : 'error');
      } catch (e) {
        log(`Error testing getter "${propName}": ${e.message}`, 'error');
      }
    }

    if ('set' in descriptor) {
      log(`Setter "${propName}" exists in clone`, 'info');
    }
  }

  // Test enumerability
  const originalEnumKeys = Object.keys(currentObject);
  const clonedEnumKeys = Object.keys(clonedObjectInstance);

  log(`Enumerable properties: Original = ${originalEnumKeys.length}, Clone = ${clonedEnumKeys.length}`,
  originalEnumKeys.length === clonedEnumKeys.length ? 'success' : 'error');

  // Test property descriptors match
  for (const propName of Object.getOwnPropertyNames(currentObject)) {
    const originalDesc = Object.getOwnPropertyDescriptor(currentObject, propName);
    const clonedDesc = Object.getOwnPropertyDescriptor(clonedObjectInstance, propName);

    if (!clonedDesc) {
      log(`Property "${propName}" missing from clone`, 'error');
      continue;
    }

    // Compare descriptor properties
    const enumMatch = originalDesc.enumerable === clonedDesc.enumerable;
    const configMatch = originalDesc.configurable === clonedDesc.configurable;

    if ('value' in originalDesc) {
      const writableMatch = originalDesc.writable === clonedDesc.writable;

      log(`Property "${propName}": ` +
      `Enumerable match: ${enumMatch}, ` +
      `Configurable match: ${configMatch}, ` +
      `Writable match: ${writableMatch}`,
      enumMatch && configMatch && writableMatch ? 'success' : 'error');
    } else {
      log(`Accessor "${propName}": ` +
      `Enumerable match: ${enumMatch}, ` +
      `Configurable match: ${configMatch}`,
      enumMatch && configMatch ? 'success' : 'error');
    }
  }

  log('Clone test completed', 'info');

  // Scroll to console
  outputConsole.scrollIntoView({ behavior: 'smooth' });
}

// Log message to console
function log(message, type = 'info') {
  const line = document.createElement('div');
  line.className = `console-line console-${type}`;
  line.textContent = `> ${message}`;
  outputConsole.appendChild(line);

  // Scroll to bottom
  outputConsole.scrollTop = outputConsole.scrollHeight;
}

// Input validation
objectDefinition.addEventListener('input', validateCode);

// Button event listeners
parseObjectButton.addEventListener('click', parseObject);
getDescriptorsButton.addEventListener('click', getAllDescriptors);
cloneObjectButton.addEventListener('click', cloneObject);
updatePropertyButton.addEventListener('click', updateProperty);
cancelEditButton.addEventListener('click', hideDescriptorEditor);
testCloneButton.addEventListener('click', testClone);

// Initialize
hideDescriptorEditor();
clonedObjectSection.style.display = 'none';

// Load default example
loadExample('basic');

String Padding: Small Feature, Big Impact#

ES8 introduced two methods that simplify string formatting:

// Left padding (great for numbers)
'5'.padStart(2, '0'); // '05'
'42'.padStart(5, '*'); // '***42'

// Right padding
'Hello'.padEnd(10, '.'); // 'Hello.....'

These methods are particularly useful for:

  • Formatting numbers (credit cards, phone numbers)

  • Creating aligned tables in console or monospace fonts

  • Padding strings to fixed lengths for storage formats

String Padding Playground
---html
<h1>String Padding Playground</h1>
  <p>Explore the ES8 string padding methods: <code>padStart()</code> and <code>padEnd()</code>.</p>
  
  <div class="example-btn-container">
    <button class="example-btn" data-example="numbers">Number Formatting</button>
    <button class="example-btn" data-example="table">Table Alignment</button>
    <button class="example-btn" data-example="credit-card">Credit Card Masking</button>
    <button class="example-btn" data-example="filename">Filename Padding</button>
  </div>
  
  <div class="container">
    <div class="card">
      <h2>String Padding Controls</h2>
      
      <div class="input-group">
        <label for="inputString">Input String:</label>
        <input type="text" id="inputString" value="Hello" placeholder="Enter string to pad">
      </div>
      
      <div class="slider-group">
        <label for="padLength">Target Length:</label>
        <div class="slider-with-value">
          <input type="range" id="padLength" min="0" max="50" value="10" step="1">
          <span class="slider-value" id="padLengthValue">10</span>
        </div>
      </div>
      
      <div class="input-group">
        <label for="padChar">Padding Character:</label>
        <input type="text" id="padChar" value="*" maxlength="5" placeholder="Character(s) for padding">
      </div>
      
      <div class="toggle-group">
        <div class="toggle-button active" data-method="padStart">padStart</div>
        <div class="toggle-button" data-method="padEnd">padEnd</div>
      </div>
      
      <h3>Result</h3>
      <div class="result-display" id="resultDisplay"></div>
      
      <div class="result-code" id="resultCode">const result = "Hello".padStart(10, "*");</div>
      
      <div class="visualizer">
        <div class="string-container" id="visualizerDisplay"></div>
      </div>
    </div>
    
    <div class="card">
      <h2>Method Details</h2>
      
      <div class="method-description" id="methodDescription">
        <h3>String.prototype.padStart()</h3>
        <p>Pads the current string from the start with a given string to reach a given length.</p>
        <p><strong>Syntax:</strong> str.padStart(targetLength [, padString])</p>
        <ul>
          <li><strong>targetLength:</strong> The length of the resulting string once the current string has been padded.</li>
          <li><strong>padString:</strong> The string to pad with. Default is " " (space).</li>
        </ul>
        <p>If the padString is too long to stay within the targetLength, it will be truncated from the end.</p>
      </div>
      
      <h3>Real-World Examples</h3>
      
      <div class="use-case">
        <div class="use-case-title">
          <span>Number Formatting</span>
          <button class="copy-button" data-code="number">Copy Code</button>
        </div>
        <div class="code-output" id="numberExample">
// Add leading zeros to numbers
function formatNumber(num, width) {
  return String(num).padStart(width, '0');
}

console.log(formatNumber(42, 5));     // "00042"
console.log(formatNumber(1234, 5));   // "01234"
console.log(formatNumber(98765, 5));  // "98765"</div>
      </div>
      
      <div class="use-case">
        <div class="use-case-title">
          <span>Credit Card Masking</span>
          <button class="copy-button" data-code="creditCard">Copy Code</button>
        </div>
        <div class="code-output" id="creditCardExample">
// Mask credit card number (show only last 4 digits)
function maskCreditCard(cardNumber) {
  const last4 = cardNumber.slice(-4);
  return last4.padStart(cardNumber.length, '*');
}

console.log(maskCreditCard('4111111111111111')); // "************1111"
console.log(maskCreditCard('378282246310005'));  // "***********0005"</div>
      </div>
      
      <div class="use-case">
        <div class="use-case-title">
          <span>Table Alignment</span>
          <button class="copy-button" data-code="table">Copy Code</button>
        </div>
        <div class="code-output" id="tableExample">
// Create aligned table columns with padEnd
function createTable(data) {
  // Find the maximum width needed for each column
  const maxWidths = data.reduce((widths, row) => {
    row.forEach((cell, i) => {
      widths[i] = Math.max(widths[i] || 0, String(cell).length);
    });
    return widths;
  }, []);
  
  // Format each row
  return data.map(row => {
    return row.map((cell, i) => {
      return String(cell).padEnd(maxWidths[i] + 2);
    }).join('');
  }).join('\\n');
}

const tableData = [
  ['Name', 'Age', 'City'],
  ['Alice', 28, 'New York'],
  ['Bob', 35, 'Los Angeles'],
  ['Charlie', 42, 'Chicago']
];

console.log(createTable(tableData));</div>
      </div>
    </div>
  </div>
  
  <div class="container">
    <div class="card">
      <h2>padStart vs. padEnd</h2>
      
      <div class="vs-display">
        <div class="vs-column">
          <div class="vs-title">padStart</div>
          <div class="vs-code">string.padStart(length, padChar)</div>
          <div class="vs-output" id="padStartVs"></div>
        </div>
        
        <div class="vs-column">
          <div class="vs-title">padEnd</div>
          <div class="vs-code">string.padEnd(length, padChar)</div>
          <div class="vs-output" id="padEndVs"></div>
        </div>
      </div>
      
      <h3>Usage Considerations</h3>
      <ul>
        <li><strong>padStart</strong> is ideal for number formatting, alignment of right-justified text, or when you need leading characters.</li>
        <li><strong>padEnd</strong> works well for column alignment in tables, creating fixed-width fields, or trailing decorations.</li>
        <li>If the target length is less than or equal to the original string length, the original string is returned unchanged.</li>
        <li>The padding character(s) can be any string. If multiple characters are provided, they repeat as needed.</li>
      </ul>
      
      <h3>Before ES8</h3>
      <p>Before these methods were introduced, padding strings required custom functions:</p>
      
      <div class="code-output">
// Old way to add padding to the start of a string
function padStart(str, targetLength, padChar = ' ') {
  str = String(str);
  padChar = String(padChar);
  
  if (str.length >= targetLength) {
    return str;
  }
  
  const padding = padChar.repeat(Math.ceil((targetLength - str.length) / padChar.length));
  return padding.slice(0, targetLength - str.length) + str;
}

// Old way to add padding to the end of a string
function padEnd(str, targetLength, padChar = ' ') {
  str = String(str);
  padChar = String(padChar);
  
  if (str.length >= targetLength) {
    return str;
  }
  
  const padding = padChar.repeat(Math.ceil((targetLength - str.length) / padChar.length));
  return str + padding.slice(0, targetLength - str.length);
}</div>
    </div>
    
    <div class="card">
      <h2>Common Use Cases</h2>
      
      <table>
        <thead>
          <tr>
            <th>Use Case</th>
            <th>Method</th>
            <th>Example</th>
          </tr>
        </thead>
        <tbody>
          <tr>
            <td>Number Formatting</td>
            <td>padStart</td>
            <td><code>"42".padStart(5, "0")</code> → <code>"00042"</code></td>
          </tr>
          <tr>
            <td>Table Column Alignment</td>
            <td>padEnd</td>
            <td><code>"Name".padEnd(10)</code> → <code>"Name      "</code></td>
          </tr>
          <tr>
            <td>Credit Card Masking</td>
            <td>padStart</td>
            <td><code>"1234".padStart(16, "*")</code> → <code>"************1234"</code></td>
          </tr>
          <tr>
            <td>Decimal Alignment</td>
            <td>Both</td>
            <td><code>num.toString().padStart(2, "0").padEnd(5, "0")</code></td>
          </tr>
          <tr>
            <td>File Name Sequences</td>
            <td>padStart</td>
            <td><code>"1".padStart(3, "0")</code> → <code>"001"</code></td>
          </tr>
          <tr>
            <td>Progress Bar</td>
            <td>padStart/padEnd</td>
            <td><code>"".padStart(progress, "█").padEnd(total, "░")</code></td>
          </tr>
          <tr>
            <td>Code Indentation</td>
            <td>padStart</td>
            <td><code>line.padStart(line.length + indentLevel * 2)</code></td>
          </tr>
          <tr>
            <td>String Centering</td>
            <td>Both</td>
            <td><code>str.padStart((width + str.length) / 2).padEnd(width)</code></td>
          </tr>
        </tbody>
      </table>
      
      <h3>Performance Considerations</h3>
      <p>The native padStart/padEnd methods are:</p>
      <ul>
        <li>More readable than manual implementations</li>
        <li>Often more performant than custom functions</li>
        <li>Universally supported in modern browsers (IE needs polyfill)</li>
        <li>Optimized for handling different edge cases</li>
      </ul>
      
      <div class="tooltip">
        Pro Tip
        <span class="tooltip-text">When padding with multiple characters, be aware that if they don't fit evenly, they'll be truncated!</span>
      </div>
    </div>
  </div>
---css
/* Dark mode awareness */
    :root {
      --bg-color: #fff;
      --text-color: #333;
      --card-bg: #f0f0f0;
      --highlight: #4a90e2;
      --secondary: #9b59b6;
      --success: #2ecc71;
      --error: #e74c3c;
      --warning: #f39c12;
      --info: #3498db;
      --border: #ddd;
      --code-bg: rgba(0, 0, 0, 0.05);
      --result-bg: rgba(0, 0, 0, 0.02);
      --table-header: #e0e0e0;
      --table-even: rgba(0, 0, 0, 0.025);
      --padding-char: #e74c3c;
      --string-char: #2ecc71;
    }
    
    @media (prefers-color-scheme: dark) {
      :root {
        --bg-color: #1a1a1a;
        --text-color: #e0e0e0;
        --card-bg: #2a2a2a;
        --highlight: #5a9cf0;
        --secondary: #bb6aef;
        --success: #3ee887;
        --error: #ff6b6b;
        --warning: #ffb142;
        --info: #54a0ff;
        --border: #444;
        --code-bg: rgba(255, 255, 255, 0.05);
        --result-bg: rgba(255, 255, 255, 0.02);
        --table-header: #333;
        --table-even: rgba(255, 255, 255, 0.025);
        --padding-char: #ff6b6b;
        --string-char: #3ee887;
      }
    }
    
    body {
      font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
      background-color: var(--bg-color);
      color: var(--text-color);
      max-width: 900px;
      margin: 0 auto;
      padding: 20px;
      transition: background-color 0.3s, color 0.3s;
    }
    
    h1, h2, h3 {
      color: var(--highlight);
    }
    
    .container {
      display: grid;
      grid-template-columns: 1fr 1fr;
      gap: 20px;
      margin-top: 20px;
    }
    
    .card {
      background-color: var(--card-bg);
      border-radius: 8px;
      padding: 20px;
      box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
    }
    
    .input-group {
      margin-bottom: 20px;
    }
    
    label {
      display: block;
      margin-bottom: 5px;
      font-weight: bold;
    }
    
    input[type="text"], input[type="number"] {
      width: 100%;
      padding: 10px;
      border-radius: 4px;
      border: 1px solid var(--border);
      background-color: var(--bg-color);
      color: var(--text-color);
      box-sizing: border-box;
    }
    
    .slider-group {
      margin-bottom: 20px;
    }
    
    .slider-with-value {
      display: flex;
      align-items: center;
      gap: 10px;
    }
    
    .slider-value {
      min-width: 40px;
      text-align: center;
    }
    
    input[type="range"] {
      flex-grow: 1;
      height: 8px;
      -webkit-appearance: none;
      background: var(--border);
      border-radius: 4px;
    }
    
    input[type="range"]::-webkit-slider-thumb {
      -webkit-appearance: none;
      width: 16px;
      height: 16px;
      border-radius: 50%;
      background: var(--highlight);
      cursor: pointer;
    }
    
    input[type="range"]::-moz-range-thumb {
      width: 16px;
      height: 16px;
      border-radius: 50%;
      background: var(--highlight);
      cursor: pointer;
      border: none;
    }
    
    .toggle-group {
      display: flex;
      gap: 10px;
      margin-bottom: 20px;
    }
    
    .toggle-button {
      flex: 1;
      padding: 10px;
      background: var(--bg-color);
      border: 1px solid var(--border);
      border-radius: 4px;
      cursor: pointer;
      text-align: center;
      transition: all 0.2s;
    }
    
    .toggle-button.active {
      background: var(--highlight);
      color: white;
      border-color: var(--highlight);
    }
    
    .result-display {
      background-color: var(--result-bg);
      padding: 15px;
      border-radius: 4px;
      border: 1px solid var(--border);
      margin-bottom: 20px;
      min-height: 60px;
      font-family: monospace;
      white-space: pre;
      overflow-x: auto;
    }
    
    .result-code {
      font-family: monospace;
      background-color: var(--code-bg);
      padding: 15px;
      border-radius: 4px;
      overflow-x: auto;
      white-space: pre;
    }
    
    .char {
      display: inline-block;
      padding: 2px;
      margin: 1px;
      border-radius: 2px;
      text-align: center;
    }
    
    .pad-char {
      background-color: var(--padding-char);
      color: white;
    }
    
    .str-char {
      background-color: var(--string-char);
      color: white;
    }
    
    button {
      background-color: var(--highlight);
      color: white;
      border: none;
      border-radius: 4px;
      padding: 10px 15px;
      cursor: pointer;
      transition: opacity 0.2s;
    }
    
    button:hover {
      opacity: 0.9;
    }
    
    button.secondary {
      background-color: var(--secondary);
    }
    
    .example-btn-container {
      display: flex;
      flex-wrap: wrap;
      gap: 10px;
      margin-top: 10px;
      margin-bottom: 20px;
    }
    
    .example-btn {
      background-color: var(--info);
      padding: 8px 12px;
      font-size: 14px;
    }
    
    table {
      width: 100%;
      border-collapse: collapse;
      margin-bottom: 20px;
    }
    
    th, td {
      padding: 10px 15px;
      text-align: left;
      border-bottom: 1px solid var(--border);
    }
    
    th {
      background-color: var(--table-header);
    }
    
    tr:nth-child(even) {
      background-color: var(--table-even);
    }
    
    .visualizer {
      display: flex;
      gap: 15px;
      align-items: center;
      margin-top: 20px;
    }
    
    .string-container {
      flex-grow: 1;
      padding: 10px;
      background-color: var(--code-bg);
      border-radius: 4px;
      overflow-x: auto;
      white-space: nowrap;
      text-align: center;
      min-height: 50px;
    }
    
    .method-description {
      margin-bottom: 15px;
      padding: 10px;
      background-color: rgba(0, 0, 0, 0.03);
      border-left: 3px solid var(--highlight);
      border-radius: 3px;
    }
    
    .code-output {
      background-color: var(--result-bg);
      padding: 15px;
      border-radius: 4px;
      border: 1px solid var(--border);
      margin-top: 15px;
      font-family: monospace;
      overflow-x: auto;
    }
    
    .use-case {
      margin-bottom: 20px;
    }
    
    .use-case-title {
      display: flex;
      justify-content: space-between;
      align-items: center;
      margin-bottom: 10px;
      font-weight: bold;
      color: var(--highlight);
    }
    
    .copy-button {
      background-color: var(--info);
      padding: 4px 8px;
      font-size: 12px;
    }
    
    .vs-display {
      display: flex;
      gap: 20px;
      margin-bottom: 20px;
    }
    
    .vs-column {
      flex: 1;
      padding: 10px;
      background-color: var(--result-bg);
      border-radius: 4px;
      border: 1px solid var(--border);
    }
    
    .vs-title {
      font-weight: bold;
      margin-bottom: 10px;
      text-align: center;
    }
    
    .vs-code {
      font-family: monospace;
      background-color: var(--code-bg);
      padding: 10px;
      border-radius: 4px;
      margin-bottom: 10px;
    }
    
    .vs-output {
      font-family: monospace;
      padding: 10px;
      background-color: var(--code-bg);
      border-radius: 4px;
    }
    
    @media (max-width: 768px) {
      .container {
        grid-template-columns: 1fr;
      }
      
      .vs-display {
        flex-direction: column;
      }
    }
    
    .toggle-mode {
      position: absolute;
      top: 20px;
      right: 20px;
      background: none;
      border: 2px solid var(--highlight);
      color: var(--highlight);
      font-size: 14px;
    }
    
    .tooltip {
      position: relative;
      display: inline-block;
      cursor: help;
    }
    
    .tooltip .tooltip-text {
      visibility: hidden;
      width: 200px;
      background-color: rgba(0, 0, 0, 0.8);
      color: #fff;
      text-align: center;
      border-radius: 6px;
      padding: 5px;
      position: absolute;
      z-index: 1;
      bottom: 125%;
      left: 50%;
      margin-left: -100px;
      opacity: 0;
      transition: opacity 0.3s;
    }
    
    .tooltip:hover .tooltip-text {
      visibility: visible;
      opacity: 1;
    }
---js
// Check for system preference and set initial mode
if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) {
  document.documentElement.setAttribute('data-theme', 'dark');
}

// DOM elements
const inputString = document.getElementById('inputString');
const padLength = document.getElementById('padLength');
const padLengthValue = document.getElementById('padLengthValue');
const padChar = document.getElementById('padChar');
const methodButtons = document.querySelectorAll('.toggle-button');
const resultDisplay = document.getElementById('resultDisplay');
const resultCode = document.getElementById('resultCode');
const visualizerDisplay = document.getElementById('visualizerDisplay');
const methodDescription = document.getElementById('methodDescription');
const padStartVs = document.getElementById('padStartVs');
const padEndVs = document.getElementById('padEndVs');

// Copy buttons
const copyButtons = document.querySelectorAll('.copy-button');

// Global variables
let currentMethod = 'padStart';
let currentResult = '';

// Example presets
const examples = {
  numbers: {
    inputString: '42',
    padLength: 5,
    padChar: '0',
    method: 'padStart' },

  table: {
    inputString: 'Name',
    padLength: 15,
    padChar: ' ',
    method: 'padEnd' },

  'credit-card': {
    inputString: '1234',
    padLength: 16,
    padChar: '*',
    method: 'padStart' },

  filename: {
    inputString: '7',
    padLength: 3,
    padChar: '0',
    method: 'padStart' } };



// Example buttons
const exampleButtons = document.querySelectorAll('.example-btn');
exampleButtons.forEach(button => {
  button.addEventListener('click', () => {
    const example = button.getAttribute('data-example');
    if (examples[example]) {
      const preset = examples[example];
      inputString.value = preset.inputString;
      padLength.value = preset.padLength;
      padLengthValue.textContent = preset.padLength;
      padChar.value = preset.padChar;
      setMethod(preset.method);
      updateResult();
    }
  });
});

// Pad length slider
padLength.addEventListener('input', () => {
  padLengthValue.textContent = padLength.value;
  updateResult();
});

// Input string and pad char inputs
inputString.addEventListener('input', updateResult);
padChar.addEventListener('input', updateResult);

// Method toggle buttons
methodButtons.forEach(button => {
  button.addEventListener('click', () => {
    const method = button.getAttribute('data-method');
    setMethod(method);
    updateResult();
  });
});

// Set active method
function setMethod(method) {
  currentMethod = method;
  methodButtons.forEach(btn => {
    if (btn.getAttribute('data-method') === method) {
      btn.classList.add('active');
    } else {
      btn.classList.remove('active');
    }
  });

  // Update method description
  if (method === 'padStart') {
    methodDescription.innerHTML = `
          <h3>String.prototype.padStart()</h3>
          <p>Pads the current string from the start with a given string to reach a given length.</p>
          <p><strong>Syntax:</strong> str.padStart(targetLength [, padString])</p>
          <ul>
            <li><strong>targetLength:</strong> The length of the resulting string once the current string has been padded.</li>
            <li><strong>padString:</strong> The string to pad with. Default is " " (space).</li>
          </ul>
          <p>If the padString is too long to stay within the targetLength, it will be truncated from the end.</p>
        `;
  } else {
    methodDescription.innerHTML = `
          <h3>String.prototype.padEnd()</h3>
          <p>Pads the current string from the end with a given string to reach a given length.</p>
          <p><strong>Syntax:</strong> str.padEnd(targetLength [, padString])</p>
          <ul>
            <li><strong>targetLength:</strong> The length of the resulting string once the current string has been padded.</li>
            <li><strong>padString:</strong> The string to pad with. Default is " " (space).</li>
          </ul>
          <p>If the padString is too long to stay within the targetLength, it will be truncated from the end.</p>
        `;
  }
}

// Update the result
function updateResult() {
  const str = inputString.value;
  const length = parseInt(padLength.value);
  const char = padChar.value || ' ';

  // Apply padding
  currentResult = currentMethod === 'padStart' ?
  str.padStart(length, char) :
  str.padEnd(length, char);

  // Update result display
  resultDisplay.textContent = `"${currentResult}"`;

  // Update code example
  resultCode.textContent = `const result = "${str}".${currentMethod}(${length}, "${char}");`;

  // Update visualizer
  visualizerDisplay.innerHTML = '';

  for (let i = 0; i < currentResult.length; i++) {
    const charSpan = document.createElement('span');
    charSpan.className = 'char';

    if (currentMethod === 'padStart') {
      // For padStart, the padding is at the beginning
      if (i < currentResult.length - str.length) {
        charSpan.className += ' pad-char';
      } else {
        charSpan.className += ' str-char';
      }
    } else {
      // For padEnd, the padding is at the end
      if (i < str.length) {
        charSpan.className += ' str-char';
      } else {
        charSpan.className += ' pad-char';
      }
    }

    charSpan.textContent = currentResult[i];
    visualizerDisplay.appendChild(charSpan);
  }

  // Update side-by-side comparison
updateSideBySide();
}

// Update side-by-side comparison
function updateSideBySide() {
  const str = inputString.value;
  const length = parseInt(padLength.value);
  const char = padChar.value || ' ';

  const padStartResult = str.padStart(length, char);
  const padEndResult = str.padEnd(length, char);

  padStartVs.textContent = `"${padStartResult}"`;
  padEndVs.textContent = `"${padEndResult}"`;
}

// Copy buttons
copyButtons.forEach(button => {
  button.addEventListener('click', () => {
    const codeType = button.getAttribute('data-code');
    let codeToCopy = '';

    switch (codeType) {
      case 'number':
        codeToCopy = document.getElementById('numberExample').textContent;
        break;
      case 'creditCard':
        codeToCopy = document.getElementById('creditCardExample').textContent;
        break;
      case 'table':
        codeToCopy = document.getElementById('tableExample').textContent;
        break;
      default:
        return;}


    navigator.clipboard.writeText(codeToCopy).then(() => {
      const originalText = button.textContent;
      button.textContent = 'Copied!';
      setTimeout(() => {
        button.textContent = originalText;
      }, 2000);
    });
  });
});

// Initialize
updateResult();

Trailing Commas in Function Parameters (optional)#

A small syntax change you can skip on a first read. A trailing comma is a comma after the last item. ES8 allows it in parameter lists, which keeps version-control diffs (the line-by-line change view) quieter when you add a parameter:

// Pre-ES8 - a trailing comma here was a SyntaxError, so adding a parameter edits two lines
function createUserOld(
  name,
  email,
  role
) {
  // function body
}
// ES8 - the last line stays untouched when you add a parameter
function createUser(
  name,
  email,
  role, // Trailing comma is valid!
) {
  // function body
}

Building a Better API Client with ES8 Features#

Let's bring everything together by seeing how ES8 transforms a typical API client implementation:

// Modern API client with ES8 features
class ApiClient {
  constructor(baseUrl) {
    this.baseUrl = baseUrl;
  }

  async get(endpoint, queryParams = {}) {
    try {
      // Convert query parameters to URL-friendly format
      const queryString = Object.entries(queryParams)
        .map(([key, value]) => `${key}=${encodeURIComponent(value)}`)
        .join('&');

      const url = `${this.baseUrl}/${endpoint}${queryString ? `?${queryString}` : ''}`;

      const response = await fetch(url);

      // Check for error status codes
      if (!response.ok) {
        throw new Error(`API error: ${response.status}`);
      }

      return await response.json();
    } catch (error) {
      console.error(`Request failed for ${endpoint}:`, error);
      throw error;
    }
  }

  // Additional methods like post(), put(), delete() would follow similar patterns
}

// Usage
const api = new ApiClient('https://api.example.com');

// Clean, async/await syntax
async function getUserWithPosts(userId) {
  try {
    const user = await api.get(`users/${userId}`);
    const posts = await api.get('posts', { userId: user.id });

    return {
      ...user,
      posts,
    };
  } catch (error) {
    console.error('Failed to get user data:', error);
    throw error;
  }
}
Modern API Client Demo
---html
<h1>Modern API Client Demo</h1>
  <p>Experience the evolution of API client patterns in JavaScript from callbacks to async/await.</p>
  
  <div class="container">
    <div class="card">
      <h2>API Client Implementation</h2>
      
      <div class="controls">
        <h3>Select an API Endpoint</h3>
        <div class="api-endpoints">
          <div class="api-endpoint active" data-endpoint="posts">Posts</div>
          <div class="api-endpoint" data-endpoint="users">Users</div>
          <div class="api-endpoint" data-endpoint="comments">Comments</div>
          <div class="api-endpoint" data-endpoint="albums">Albums</div>
          <div class="api-endpoint" data-endpoint="photos">Photos</div>
        </div>
      </div>
      
      <div class="implementation-tabs">
        <div class="implementation-tab" data-implementation="callback">Callback</div>
        <div class="implementation-tab" data-implementation="promise">Promise</div>
        <div class="implementation-tab active" data-implementation="async">Async/Await</div>
      </div>
      
      <div class="implementation-content" data-content="callback">
        <div class="code-editor">
          <pre class="code-display" id="callbackCode">
/**
 * Callback-based API client implementation
 */
function ApiClient(baseUrl) {
  this.baseUrl = baseUrl;
}

ApiClient.prototype.get = function(endpoint, callback) {
  console.log(`Fetching ${endpoint} with callback pattern...`);
  const startTime = performance.now();
  
  // Create a new XMLHttpRequest (the old way)
  const xhr = new XMLHttpRequest();
  
  xhr.open('GET', `${this.baseUrl}/${endpoint}`, true);
  
  xhr.onload = function() {
    const endTime = performance.now();
    const timeElapsed = endTime - startTime;
    
    if (xhr.status >= 200 && xhr.status < 300) {
      try {
        const data = JSON.parse(xhr.responseText);
        callback(null, data, timeElapsed, xhr.status);
      } catch (error) {
        callback(new Error('Failed to parse response'), null, timeElapsed, xhr.status);
      }
    } else {
      callback(new Error(`Request failed with status ${xhr.status}`), 
               null, timeElapsed, xhr.status);
    }
  };
  
  xhr.onerror = function() {
    const endTime = performance.now();
    const timeElapsed = endTime - startTime;
    callback(new Error('Network error occurred'), null, timeElapsed, 0);
  };
  
  xhr.send();
};

// Usage example
const client = new ApiClient('https://jsonplaceholder.typicode.com');

function fetchData() {
  // Show loading state
  toggleLoading(true);
  clearConsole();
  clearResponse();
  
  logToConsole('Starting callback API request...', 'info');
  
  client.get('posts', function(error, data, time, status) {
    if (error) {
      logToConsole(`Error: ${error.message}`, 'error');
      showResponse(null, time, status);
    } else {
      logToConsole(`Success! Received ${data.length} items`, 'success');
      logToConsole(`Request completed in ${time.toFixed(2)} ms`, 'info');
      showResponse(data, time, status);
    }
    
    // Hide loading state
    toggleLoading(false);
  });
}
</pre>
          <div class="loading-overlay" id="callbackLoading">
            <div class="spinner"></div>
          </div>
        </div>
        
        <div class="api-actions">
          <button id="executeCallbackBtn">Execute Request</button>
          <button id="resetCallbackBtn" class="secondary">Reset</button>
        </div>
      </div>
      
      <div class="implementation-content" data-content="promise">
        <div class="code-editor">
          <pre class="code-display" id="promiseCode">
/**
 * Promise-based API client implementation
 */
class ApiClient {
  constructor(baseUrl) {
    this.baseUrl = baseUrl;
  }
  
  get(endpoint) {
    console.log(`Fetching ${endpoint} with promise pattern...`);
    const startTime = performance.now();
    
    return fetch(`${this.baseUrl}/${endpoint}`)
      .then(response => {
        const endTime = performance.now();
        const timeElapsed = endTime - startTime;
        
        if (!response.ok) {
          throw {
            error: new Error(`Request failed with status ${response.status}`),
            timeElapsed,
            status: response.status
          };
        }
        
        return response.json()
          .then(data => {
            return {
              data,
              timeElapsed,
              status: response.status
            };
          })
          .catch(error => {
            throw {
              error: new Error('Failed to parse response'),
              timeElapsed,
              status: response.status
            };
          });
      })
      .catch(error => {
        // If error is already formatted, rethrow it
        if (error.timeElapsed !== undefined) {
          throw error;
        }
        
        // Otherwise, format the error
        const endTime = performance.now();
        const timeElapsed = endTime - startTime;
        
        throw {
          error: new Error('Network error occurred'),
          timeElapsed,
          status: 0
        };
      });
  }
}

// Usage example
const client = new ApiClient('https://jsonplaceholder.typicode.com');

function fetchData() {
  // Show loading state
  toggleLoading(true);
  clearConsole();
  clearResponse();
  
  logToConsole('Starting promise-based API request...', 'info');
  
  client.get('posts')
    .then(result => {
      logToConsole(`Success! Received ${result.data.length} items`, 'success');
      logToConsole(`Request completed in ${result.timeElapsed.toFixed(2)} ms`, 'info');
      showResponse(result.data, result.timeElapsed, result.status);
    })
    .catch(error => {
      logToConsole(`Error: ${error.error.message}`, 'error');
      showResponse(null, error.timeElapsed, error.status);
    })
    .finally(() => {
      // Hide loading state
      toggleLoading(false);
    });
}
</pre>
          <div class="loading-overlay" id="promiseLoading">
            <div class="spinner"></div>
          </div>
        </div>
        
        <div class="api-actions">
          <button id="executePromiseBtn">Execute Request</button>
          <button id="resetPromiseBtn" class="secondary">Reset</button>
        </div>
      </div>
      
      <div class="implementation-content active" data-content="async">
        <div class="code-editor">
          <pre class="code-display" id="asyncCode">
/**
 * Async/await API client implementation
 */
class ApiClient {
  constructor(baseUrl) {
    this.baseUrl = baseUrl;
  }
  
  async get(endpoint) {
    console.log(`Fetching ${endpoint} with async/await pattern...`);
    const startTime = performance.now();
    
    try {
      const response = await fetch(`${this.baseUrl}/${endpoint}`);
      const endTime = performance.now();
      const timeElapsed = endTime - startTime;
      
      if (!response.ok) {
        throw {
          error: new Error(`Request failed with status ${response.status}`),
          timeElapsed,
          status: response.status
        };
      }
      
      try {
        const data = await response.json();
        return {
          data,
          timeElapsed,
          status: response.status
        };
      } catch (error) {
        throw {
          error: new Error('Failed to parse response'),
          timeElapsed,
          status: response.status
        };
      }
    } catch (error) {
      // If error is already formatted, rethrow it
      if (error.timeElapsed !== undefined) {
        throw error;
      }
      
      // Otherwise, format the error
      const endTime = performance.now();
      const timeElapsed = endTime - startTime;
      
      throw {
        error: new Error('Network error occurred'),
        timeElapsed,
        status: 0
      };
    }
  }
}

// Usage example
const client = new ApiClient('https://jsonplaceholder.typicode.com');

async function fetchData() {
  // Show loading state
  toggleLoading(true);
  clearConsole();
  clearResponse();
  
  logToConsole('Starting async/await API request...', 'info');
  
  try {
    const result = await client.get('posts');
    logToConsole(`Success! Received ${result.data.length} items`, 'success');
    logToConsole(`Request completed in ${result.timeElapsed.toFixed(2)} ms`, 'info');
    showResponse(result.data, result.timeElapsed, result.status);
  } catch (error) {
    logToConsole(`Error: ${error.error.message}`, 'error');
    showResponse(null, error.timeElapsed, error.status);
  } finally {
    // Hide loading state
    toggleLoading(false);
  }
}
</pre>
          <div class="loading-overlay" id="asyncLoading">
            <div class="spinner"></div>
          </div>
        </div>
        
        <div class="api-actions">
          <button id="executeAsyncBtn">Execute Request</button>
          <button id="resetAsyncBtn" class="secondary">Reset</button>
        </div>
      </div>
      
      <div class="console-wrapper">
        <h3>Console Output</h3>
        <div class="console" id="console">
          <div class="console-line console-info">// Console output will appear here</div>
        </div>
      </div>
    </div>
    
    <div class="card">
      <h2>API Response</h2>
      
      <div class="api-response">
        <div class="response-header">
          <div>Result</div>
          <div class="response-status" id="responseStatus">200 OK</div>
        </div>
        <div class="response-body" id="responseBody">
          // API response will appear here
        </div>
      </div>
      
      <div class="performance-metrics">
        <div class="metrics-title">Performance Metrics</div>
        <div class="metric">
          <div>Request time:</div>
          <div class="metric-value" id="requestTime">-</div>
        </div>
        <div class="metric">
          <div>Response size:</div>
          <div class="metric-value" id="responseSize">-</div>
        </div>
      </div>
      
      <div class="info-panel">
        <div class="info-title">About This Demo</div>
        <p>This demo uses the <a href="https://jsonplaceholder.typicode.com/" target="_blank">JSONPlaceholder</a> API, a free online REST API for testing and prototyping.</p>
        <p>You can explore different API endpoints and see how they're implemented using three different JavaScript patterns:</p>
        <ul>
          <li><strong>Callbacks</strong>: The traditional approach using XMLHttpRequest</li>
          <li><strong>Promises</strong>: Using fetch API with .then() chains</li>
          <li><strong>Async/Await</strong>: Modern approach with cleaner syntax</li>
        </ul>
        <p>Click "Execute Request" to see each implementation in action.</p>
      </div>
      
      <div class="method-comparison">
        <div class="comparison-title">Pattern Comparison</div>
        <table class="comparison-table">
          <thead>
            <tr>
              <th>Feature</th>
              <th>Callbacks</th>
              <th>Promises</th>
              <th>Async/Await</th>
            </tr>
          </thead>
          <tbody>
            <tr>
              <td>Readability</td>
              <td>Low (nested)</td>
              <td>Medium (chains)</td>
              <td>High (sequential)</td>
            </tr>
            <tr>
              <td>Error Handling</td>
              <td>Error callbacks</td>
              <td>Catch blocks</td>
              <td>Try/catch blocks</td>
            </tr>
            <tr>
              <td>Composability</td>
              <td>Difficult</td>
              <td>Good</td>
              <td>Excellent</td>
            </tr>
            <tr>
              <td>Debugging</td>
              <td>Challenging</td>
              <td>Moderate</td>
              <td>Straightforward</td>
            </tr>
            <tr>
              <td>Browser Support</td>
              <td>All browsers</td>
              <td>Modern browsers</td>
              <td>Modern browsers</td>
            </tr>
          </tbody>
        </table>
      </div>
    </div>
  </div>
---css
/* Dark mode awareness */
    :root {
      --bg-color: #fff;
      --text-color: #333;
      --card-bg: #f0f0f0;
      --highlight: #4a90e2;
      --secondary: #9b59b6;
      --success: #2ecc71;
      --error: #e74c3c;
      --warning: #f39c12;
      --info: #3498db;
      --border: #ddd;
      --code-bg: rgba(0, 0, 0, 0.05);
      --api-card-bg: rgba(0, 0, 0, 0.02);
      --tab-active-bg: rgba(74, 144, 226, 0.1);
      --spinner-color: #4a90e2;
      --response-header: #e0e0e0;
      --response-highlight: #2ecc71;
      --console-bg: #2d2d2d;
      --console-text: #f8f8f2;
      --console-success: #a6e22e;
      --console-info: #66d9ef;
      --console-error: #f92672;
      --console-warn: #fd971f;
      --loading-overlay: rgba(255, 255, 255, 0.7);
    }
    
    @media (prefers-color-scheme: dark) {
      :root {
        --bg-color: #1a1a1a;
        --text-color: #e0e0e0;
        --card-bg: #2a2a2a;
        --highlight: #5a9cf0;
        --secondary: #bb6aef;
        --success: #3ee887;
        --error: #ff6b6b;
        --warning: #ffb142;
        --info: #54a0ff;
        --border: #444;
        --code-bg: rgba(255, 255, 255, 0.05);
        --api-card-bg: rgba(255, 255, 255, 0.03);
        --tab-active-bg: rgba(90, 156, 240, 0.15);
        --spinner-color: #5a9cf0;
        --response-header: #333;
        --response-highlight: #3ee887;
        --console-bg: #1e1e1e;
        --console-text: #f8f8f2;
        --console-success: #a6e22e;
        --console-info: #66d9ef;
        --console-error: #f92672;
        --console-warn: #fd971f;
        --loading-overlay: rgba(0, 0, 0, 0.7);
      }
    }
    
    body {
      font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
      background-color: var(--bg-color);
      color: var(--text-color);
      max-width: 1000px;
      margin: 0 auto;
      padding: 20px;
      transition: background-color 0.3s, color 0.3s;
    }
    
    h1, h2, h3 {
      color: var(--highlight);
    }
    
    .container {
      display: grid;
      grid-template-columns: 1fr 1fr;
      gap: 20px;
      margin-top: 20px;
    }
    
    .card {
      background-color: var(--card-bg);
      border-radius: 8px;
      padding: 20px;
      box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
    }
    
    .controls {
      margin-bottom: 20px;
    }
    
    .api-endpoints {
      display: flex;
      flex-wrap: wrap;
      gap: 10px;
      margin-bottom: 20px;
    }
    
    .api-endpoint {
      background-color: var(--api-card-bg);
      border: 1px solid var(--border);
      border-radius: 4px;
      padding: 10px 15px;
      cursor: pointer;
      transition: all 0.2s;
    }
    
    .api-endpoint:hover {
      border-color: var(--highlight);
    }
    
    .api-endpoint.active {
      background-color: var(--highlight);
      color: white;
      border-color: var(--highlight);
    }
    
    .implementation-tabs {
      display: flex;
      border-bottom: 1px solid var(--border);
      margin-bottom: 20px;
      position: sticky;
      top: 0;
      background-color: var(--card-bg);
      z-index: 10;
      padding-top: 10px;
    }
    
    .implementation-tab {
      padding: 10px 20px;
      cursor: pointer;
      border-bottom: 3px solid transparent;
    }
    
    .implementation-tab:hover {
      background-color: rgba(0, 0, 0, 0.05);
    }
    
    .implementation-tab.active {
      border-bottom-color: var(--highlight);
      background-color: var(--tab-active-bg);
    }
    
    .implementation-content {
      display: none;
      position: relative;
    }
    
    .implementation-content.active {
      display: block;
    }
    
    .code-editor {
      position: relative;
      height: 350px;
      overflow: hidden;
      border-radius: 4px;
      margin-bottom: 20px;
    }
    
    .code-display {
      position: absolute;
      top: 0;
      left: 0;
      right: 0;
      bottom: 0;
      margin: 0;
      padding: 15px;
      background-color: var(--code-bg);
      font-family: 'Courier New', monospace;
      overflow: auto;
      white-space: pre;
      line-height: 1.5;
      font-size: 14px;
      tab-size: 2;
    }
    
    .code-display:focus {
      outline: none;
      border: 1px solid var(--highlight);
    }
    
    .api-actions {
      display: flex;
      gap: 10px;
    }
    
    button {
      background-color: var(--highlight);
      color: white;
      border: none;
      border-radius: 4px;
      padding: 10px 15px;
      cursor: pointer;
      transition: opacity 0.2s;
    }
    
    button:hover {
      opacity: 0.9;
    }
    
    button.secondary {
      background-color: var(--secondary);
    }
    
    button:disabled {
      opacity: 0.5;
      cursor: not-allowed;
    }
    
    .loading-overlay {
      position: absolute;
      top: 0;
      left: 0;
      right: 0;
      bottom: 0;
      background-color: var(--loading-overlay);
      display: flex;
      align-items: center;
      justify-content: center;
      z-index: 100;
      display: none;
    }
    
    .spinner {
      width: 40px;
      height: 40px;
      border-radius: 50%;
      border: 3px solid var(--spinner-color);
      border-top-color: transparent;
      animation: spin 1s linear infinite;
    }
    
    @keyframes spin {
      to { transform: rotate(360deg); }
    }
    
    .api-response {
      background-color: var(--code-bg);
      border-radius: 4px;
      overflow: hidden;
      max-height: 400px;
      display: flex;
      flex-direction: column;
    }
    
    .response-header {
      background-color: var(--response-header);
      padding: 10px 15px;
      font-weight: bold;
      display: flex;
      justify-content: space-between;
    }
    
    .response-status {
      padding: 3px 8px;
      border-radius: 3px;
      background-color: var(--success);
      color: white;
      font-size: 12px;
    }
    
    .response-status.error {
      background-color: var(--error);
    }
    
    .response-body {
      padding: 15px;
      overflow: auto;
      flex-grow: 1;
      font-family: 'Courier New', monospace;
      white-space: pre-wrap;
    }
    
    .highlight {
      color: var(--response-highlight);
      font-weight: bold;
    }
    
    .performance-metrics {
      margin-top: 20px;
      padding: 15px;
      background-color: var(--api-card-bg);
      border-radius: 4px;
      border: 1px solid var(--border);
    }
    
    .metrics-title {
      font-weight: bold;
      margin-bottom: 10px;
    }
    
    .metric {
      display: flex;
      justify-content: space-between;
      margin-bottom: 5px;
    }
    
    .metric-value {
      font-weight: bold;
    }
    
    .console-wrapper {
      margin-top: 20px;
    }
    
    .console {
      background-color: var(--console-bg);
      color: var(--console-text);
      padding: 15px;
      border-radius: 4px;
      font-family: 'Courier New', monospace;
      height: 150px;
      overflow-y: auto;
    }
    
    .console-line {
      margin-bottom: 5px;
      line-height: 1.4;
    }
    
    .console-success {
      color: var(--console-success);
    }
    
    .console-info {
      color: var(--console-info);
    }
    
    .console-error {
      color: var(--console-error);
    }
    
    .console-warning {
      color: var(--console-warn);
    }
    
    .info-panel {
      margin-top: 20px;
      padding: 15px;
      background-color: var(--api-card-bg);
      border-radius: 4px;
      border: 1px solid var(--border);
    }
    
    .info-title {
      font-weight: bold;
      margin-bottom: 10px;
    }
    
    .method-comparison {
      margin-top: 20px;
    }
    
    .comparison-title {
      font-weight: bold;
      margin-bottom: 10px;
      color: var(--highlight);
    }
    
    .comparison-table {
      width: 100%;
      border-collapse: collapse;
      margin-bottom: 20px;
    }
    
    .comparison-table th, .comparison-table td {
      padding: 10px;
      border: 1px solid var(--border);
      text-align: left;
    }
    
    .comparison-table th {
      background-color: var(--response-header);
    }
    
    .comparison-table tr:nth-child(even) {
      background-color: var(--api-card-bg);
    }
    
    .toggle-mode {
      position: absolute;
      top: 20px;
      right: 20px;
      background: none;
      border: 2px solid var(--highlight);
      color: var(--highlight);
      font-size: 14px;
    }
    
    @media (max-width: 900px) {
      .container {
        grid-template-columns: 1fr;
      }
    }
---js
// Check for system preference and set initial mode
if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) {
  document.documentElement.setAttribute('data-theme', 'dark');
}

// DOM Elements
const implementationTabs = document.querySelectorAll('.implementation-tab');
const implementationContents = document.querySelectorAll('.implementation-content');
const apiEndpoints = document.querySelectorAll('.api-endpoint');
const callbackCodeDisplay = document.getElementById('callbackCode');
const promiseCodeDisplay = document.getElementById('promiseCode');
const asyncCodeDisplay = document.getElementById('asyncCode');
const console = document.getElementById('console');
const responseBody = document.getElementById('responseBody');
const responseStatus = document.getElementById('responseStatus');
const requestTime = document.getElementById('requestTime');
const responseSize = document.getElementById('responseSize');

// Buttons
const executeCallbackBtn = document.getElementById('executeCallbackBtn');
const resetCallbackBtn = document.getElementById('resetCallbackBtn');
const executePromiseBtn = document.getElementById('executePromiseBtn');
const resetPromiseBtn = document.getElementById('resetPromiseBtn');
const executeAsyncBtn = document.getElementById('executeAsyncBtn');
const resetAsyncBtn = document.getElementById('resetAsyncBtn');

// Loading overlays
const callbackLoading = document.getElementById('callbackLoading');
const promiseLoading = document.getElementById('promiseLoading');
const asyncLoading = document.getElementById('asyncLoading');

// Current state
let currentImplementation = 'async';
let currentEndpoint = 'posts';

// Implementation tabs
implementationTabs.forEach(tab => {
  tab.addEventListener('click', () => {
    const implementation = tab.getAttribute('data-implementation');

    // Update tabs
    implementationTabs.forEach(t => t.classList.remove('active'));
    tab.classList.add('active');

    // Update content
    implementationContents.forEach(content => {
      if (content.getAttribute('data-content') === implementation) {
        content.classList.add('active');
      } else {
        content.classList.remove('active');
      }
    });

    // Update current implementation
    currentImplementation = implementation;

    // Update code displays with current endpoint
    updateCodeDisplays();
  });
});

// API endpoints
apiEndpoints.forEach(endpoint => {
  endpoint.addEventListener('click', () => {
    const endpointName = endpoint.getAttribute('data-endpoint');

    // Update endpoints
    apiEndpoints.forEach(e => e.classList.remove('active'));
    endpoint.classList.add('active');

    // Update current endpoint
    currentEndpoint = endpointName;

    // Update code displays
    updateCodeDisplays();
  });
});

// Update code displays based on current endpoint
function updateCodeDisplays() {
  const callbackCode = callbackCodeDisplay.textContent;
  const promiseCode = promiseCodeDisplay.textContent;
  const asyncCode = asyncCodeDisplay.textContent;

  callbackCodeDisplay.textContent = updateEndpointInCode(callbackCode, currentEndpoint);
  promiseCodeDisplay.textContent = updateEndpointInCode(promiseCode, currentEndpoint);
  asyncCodeDisplay.textContent = updateEndpointInCode(asyncCode, currentEndpoint);
}

// Update endpoint in code string
function updateEndpointInCode(code, endpoint) {
  // Replace the endpoint in fetch or get calls
  return code.replace(/get\(['"](posts|users|comments|albums|photos)['"]/g, `get("${endpoint}"`);
}

// Execute request buttons
executeCallbackBtn.addEventListener('click', () => {
  // Execute the callback code
  const code = callbackCodeDisplay.textContent;
  executeCode(code, 'callbackLoading');
});

executePromiseBtn.addEventListener('click', () => {
  // Execute the promise code
  const code = promiseCodeDisplay.textContent;
  executeCode(code, 'promiseLoading');
});

executeAsyncBtn.addEventListener('click', () => {
  // Execute the async code
  const code = asyncCodeDisplay.textContent;
  executeCode(code, 'asyncLoading');
});

// Reset buttons
resetCallbackBtn.addEventListener('click', () => {
  clearConsole();
  clearResponse();
});

resetPromiseBtn.addEventListener('click', () => {
  clearConsole();
  clearResponse();
});

resetAsyncBtn.addEventListener('click', () => {
  clearConsole();
  clearResponse();
});

// Toggle loading state
function toggleLoading(isLoading) {
  const loadingId = `${currentImplementation}Loading`;
  const loadingElement = document.getElementById(loadingId);

  if (loadingElement) {
    loadingElement.style.display = isLoading ? 'flex' : 'none';
  }

  // Disable buttons during loading
  executeCallbackBtn.disabled = isLoading;
  executePromiseBtn.disabled = isLoading;
  executeAsyncBtn.disabled = isLoading;
}

// Log message to console
function logToConsole(message, type = 'info') {
  const line = document.createElement('div');
  line.className = `console-line console-${type}`;
  line.textContent = `> ${message}`;
  console.appendChild(line);

  // Scroll to bottom
  console.scrollTop = console.scrollHeight;
}

// Clear console
function clearConsole() {
  console.innerHTML = '';
}

// Show API response
function showResponse(data, time, status) {
  // Update status
  responseStatus.textContent = status ? `${status} ${getStatusText(status)}` : 'Error';
  responseStatus.className = 'response-status' + (status >= 200 && status < 300 ? '' : ' error');

  // Update body
  if (data) {
    responseBody.innerHTML = formatJson(data);
    responseSize.textContent = `${JSON.stringify(data).length} bytes`;
  } else {
    responseBody.textContent = 'No data received';
    responseSize.textContent = '0 bytes';
  }

  // Update metrics
  requestTime.textContent = time ? `${time.toFixed(2)} ms` : '-';
}

// Clear response
function clearResponse() {
  responseBody.textContent = '// API response will appear here';
  responseStatus.textContent = '200 OK';
  responseStatus.className = 'response-status';
  requestTime.textContent = '-';
  responseSize.textContent = '-';
}

// Format JSON for display
function formatJson(json) {
  if (!json) return '';

  // Stringify and add basic highlighting
  let formatted = JSON.stringify(json, null, 2);

  // Highlight keys
  formatted = formatted.replace(/"([^"]+)":/g, '<span class="highlight">"$1"</span>:');

  return formatted;
}

// Get status text for HTTP status code
function getStatusText(status) {
  const statusTexts = {
    200: 'OK',
    201: 'Created',
    204: 'No Content',
    400: 'Bad Request',
    401: 'Unauthorized',
    403: 'Forbidden',
    404: 'Not Found',
    500: 'Internal Server Error' };


  return statusTexts[status] || '';
}

// Execute code (with safety measures)
function executeCode(codeString, loadingId) {
  // Add utility functions to the execution context
  const utilFunctions = `
        function logToConsole(message, type) {
          const consoleEl = document.getElementById('console');
          const line = document.createElement('div');
          line.className = 'console-line console-' + (type || 'info');
          line.textContent = '> ' + message;
          consoleEl.appendChild(line);
          consoleEl.scrollTop = consoleEl.scrollHeight;
        }
        
        function showResponse(data, time, status) {
          const responseBody = document.getElementById('responseBody');
          const responseStatus = document.getElementById('responseStatus');
          const requestTime = document.getElementById('requestTime');
          const responseSize = document.getElementById('responseSize');
          
          responseStatus.textContent = status ? status + ' ' + getStatusText(status) : 'Error';
          responseStatus.className = 'response-status' + (status >= 200 && status < 300 ? '' : ' error');
          
          if (data) {
            responseBody.innerHTML = formatJson(data);
            responseSize.textContent = JSON.stringify(data).length + ' bytes';
          } else {
            responseBody.textContent = 'No data received';
            responseSize.textContent = '0 bytes';
          }
          
          requestTime.textContent = time ? time.toFixed(2) + ' ms' : '-';
        }
        
        function clearConsole() {
          document.getElementById('console').innerHTML = '';
        }
        
        function clearResponse() {
          document.getElementById('responseBody').textContent = '// API response will appear here';
          document.getElementById('responseStatus').textContent = '200 OK';
          document.getElementById('responseStatus').className = 'response-status';
          document.getElementById('requestTime').textContent = '-';
          document.getElementById('responseSize').textContent = '-';
        }
        
        function toggleLoading(isLoading) {
          document.getElementById('${loadingId}').style.display = isLoading ? 'flex' : 'none';
          document.getElementById('executeCallbackBtn').disabled = isLoading;
          document.getElementById('executePromiseBtn').disabled = isLoading;
          document.getElementById('executeAsyncBtn').disabled = isLoading;
        }
        
        function getStatusText(status) {
          const statusTexts = {
            200: 'OK',
            201: 'Created',
            204: 'No Content',
            400: 'Bad Request',
            401: 'Unauthorized',
            403: 'Forbidden',
            404: 'Not Found',
            500: 'Internal Server Error'
          };
          
          return statusTexts[status] || '';
        }
        
        function formatJson(json) {
          if (!json) return '';
          
          let formatted = JSON.stringify(json, null, 2);
          formatted = formatted.replace(/"([^"]+)":/g, '<span class="highlight">"$1"</span>:');
          
          return formatted;
        }
      `;

  try {
    // Create a safe function with our utility functions included
    const fullCode = utilFunctions + codeString;
    const executeFunction = new Function(fullCode);

    // Execute the function
    executeFunction();
  } catch (error) {
    // Log any errors
    logToConsole(`Error executing code: ${error.message}`, 'error');
    toggleLoading(false);
  }
}

// Set initial active tab
implementationTabs.forEach(tab => {
  if (tab.getAttribute('data-implementation') === currentImplementation) {
    tab.classList.add('active');
  } else {
    tab.classList.remove('active');
  }
});

// Set initial active content
implementationContents.forEach(content => {
  if (content.getAttribute('data-content') === currentImplementation) {
    content.classList.add('active');
  } else {
    content.classList.remove('active');
  }
});

// Initialize
updateCodeDisplays();

Browser and Environment Support#

All of these features are old enough to be in every current browser and every maintained Node.js release. The minimum versions below are the ones we listed when we wrote this post. We did not re-check them in October 2026:

  • Async/await: Chrome 55+, Firefox 52+, Safari 10.1+, Edge 15+, Node.js 7.6+

  • Object methods: Chrome 54+, Firefox 47+, Safari 10.1+, Edge 14+, Node.js 7.0+

  • String padding: Chrome 57+, Firefox 48+, Safari 10+, Edge 15+, Node.js 8.0+

If you must support an environment older than these versions, a transpiler like Babel and a polyfill can fill the gap. Most projects no longer need either for these features.

ES7/ES8 Feature Compatibility Detector
---html
<div class="container">
    <header>
      <h1>ES7/ES8 Feature Compatibility Detector</h1>
      <p class="description">This tool checks your browser's support for key features introduced in ECMAScript 2016 (ES7) and ECMAScript 2017 (ES8).</p>
    </header>
    
    <div class="browser-info">
      <div class="browser-title">Your Browser Information</div>
      <div class="browser-details" id="browserDetails">
        <!-- Browser details will be populated here -->
      </div>
    </div>
    
    <div class="compatibility-summary">
      <div class="summary-title">Compatibility Summary</div>
      <div class="progress-container">
        <div class="progress-label">
          <span>Overall ES7/ES8 Support</span>
          <span id="overallPercentage">0%</span>
        </div>
        <div class="progress-bar">
          <div class="progress-fill" id="overallProgress" style="width: 0%"></div>
        </div>
      </div>
      
      <div class="progress-container">
        <div class="progress-label">
          <span>ES7 (2016) Features</span>
          <span id="es7Percentage">0%</span>
        </div>
        <div class="progress-bar">
          <div class="progress-fill" id="es7Progress" style="width: 0%"></div>
        </div>
      </div>
      
      <div class="progress-container">
        <div class="progress-label">
          <span>ES8 (2017) Features</span>
          <span id="es8Percentage">0%</span>
        </div>
        <div class="progress-bar">
          <div class="progress-fill" id="es8Progress" style="width: 0%"></div>
        </div>
      </div>
    </div>
    
    <div class="features-grid" id="featuresGrid">
      <!-- Feature cards will be populated here -->
    </div>
    
    <footer>
      <p>© 2025 ES7/ES8 Feature Compatibility Detector | Created to supplement "ES7 & ES8: Enhancing JavaScript's Async Capabilities"</p>
    </footer>
  </div>
---css
/* Dark mode awareness */
    :root {
      --bg-color: #fff;
      --text-color: #333;
      --card-bg: #f5f5f5;
      --highlight: #4a90e2;
      --secondary: #9b59b6;
      --success: #2ecc71;
      --warning: #f39c12;
      --error: #e74c3c;
      --info: #3498db;
      --border: #ddd;
      --feature-title: #333;
      --code-bg: #f0f0f0;
      --card-shadow: 0 2px 6px rgba(0, 0, 0, 0.1);
      --feature-bg-color: #fff;
    }
    
    @media (prefers-color-scheme: dark) {
      :root {
        --bg-color: #1a1a1a;
        --text-color: #e0e0e0;
        --card-bg: #2a2a2a;
        --highlight: #5a9cf0;
        --secondary: #bb6aef;
        --success: #3ee887;
        --warning: #ffb142;
        --error: #ff6b6b;
        --info: #54a0ff;
        --border: #444;
        --feature-title: #fff;
        --code-bg: #333;
        --card-shadow: 0 2px 6px rgba(0, 0, 0, 0.3);
        --feature-bg-color: #2c2c2c;
      }
    }
    
    body {
      font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
      background-color: var(--bg-color);
      color: var(--text-color);
      margin: 0;
      padding: 20px;
      transition: background-color 0.3s, color 0.3s;
    }
    
    .container {
      max-width: 1000px;
      margin: 0 auto;
    }
    
    header {
      margin-bottom: 30px;
      text-align: center;
      padding: 10px;
      background-color: var(--card-bg);
      border-radius: 8px;
      box-shadow: var(--card-shadow);
    }
    
    h1 {
      color: var(--highlight);
      margin-bottom: 10px;
    }
    
    .description {
      margin-bottom: 20px;
      line-height: 1.5;
    }
    
    .features-grid {
      display: grid;
      grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
      gap: 20px;
      margin-bottom: 30px;
    }
    
    .feature-card {
      background-color: var(--feature-bg-color);
      border-radius: 8px;
      overflow: hidden;
      box-shadow: var(--card-shadow);
      transition: transform 0.2s;
    }
    
    .feature-card:hover {
      transform: translateY(-3px);
    }
    
    .feature-header {
      padding: 15px;
      border-bottom: 1px solid var(--border);
      display: flex;
      justify-content: space-between;
      align-items: center;
    }
    
    .feature-title {
      font-weight: bold;
      font-size: 1.1rem;
      color: var(--feature-title);
      margin: 0;
    }
    
    .feature-version {
      background-color: var(--highlight);
      color: white;
      padding: 4px 8px;
      border-radius: 12px;
      font-size: 0.8rem;
      font-weight: bold;
    }
    
    .feature-version.es7 {
      background-color: var(--info);
    }
    
    .feature-version.es8 {
      background-color: var(--secondary);
    }
    
    .feature-body {
      padding: 15px;
    }
    
    .feature-description {
      margin-bottom: 15px;
      line-height: 1.5;
    }
    
    .code-example {
      background-color: var(--code-bg);
      padding: 12px;
      border-radius: 4px;
      font-family: monospace;
      font-size: 0.9rem;
      margin-bottom: 15px;
      overflow-x: auto;
    }
    
    .support-status {
      display: flex;
      align-items: center;
      padding: 8px 12px;
      border-radius: 4px;
      font-weight: bold;
      margin-top: 10px;
    }
    
    .support-status.supported {
      background-color: rgba(46, 204, 113, 0.2);
      color: var(--success);
    }
    
    .support-status.not-supported {
      background-color: rgba(231, 76, 60, 0.2);
      color: var(--error);
    }
    
    .support-status.warning {
      background-color: rgba(243, 156, 18, 0.2);
      color: var(--warning);
    }
    
    .icon {
      margin-right: 8px;
      font-size: 1.2rem;
    }
    
    .browser-info {
      background-color: var(--card-bg);
      border-radius: 8px;
      padding: 20px;
      margin-bottom: 30px;
      box-shadow: var(--card-shadow);
    }
    
    .browser-title {
      font-size: 1.2rem;
      font-weight: bold;
      margin-bottom: 15px;
      color: var(--highlight);
    }
    
    .browser-details {
      display: grid;
      grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
      gap: 15px;
    }
    
    .browser-detail {
      background-color: var(--feature-bg-color);
      padding: 12px;
      border-radius: 6px;
      box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
    }
    
    .detail-label {
      font-weight: bold;
      margin-bottom: 5px;
      color: var(--secondary);
    }
    
    .detail-value {
      font-family: monospace;
      word-break: break-word;
    }
    
    .compatibility-summary {
      background-color: var(--card-bg);
      border-radius: 8px;
      padding: 20px;
      margin-bottom: 30px;
      box-shadow: var(--card-shadow);
    }
    
    .summary-title {
      font-size: 1.2rem;
      font-weight: bold;
      margin-bottom: 15px;
      color: var(--highlight);
    }
    
    .progress-container {
      margin-bottom: 20px;
    }
    
    .progress-label {
      display: flex;
      justify-content: space-between;
      margin-bottom: 5px;
    }
    
    .progress-bar {
      height: 20px;
      background-color: var(--feature-bg-color);
      border-radius: 10px;
      overflow: hidden;
    }
    
    .progress-fill {
      height: 100%;
      background-color: var(--success);
      border-radius: 10px;
      transition: width 1s ease-in-out;
    }
    
    footer {
      text-align: center;
      margin-top: 40px;
      padding: 20px;
      border-top: 1px solid var(--border);
      font-size: 0.9rem;
    }
    
    .toggle-mode {
      position: absolute;
      top: 20px;
      right: 20px;
      background: none;
      border: 2px solid var(--highlight);
      color: var(--highlight);
      padding: 8px 12px;
      border-radius: 4px;
      cursor: pointer;
      font-size: 14px;
      transition: all 0.2s;
    }
    
    .toggle-mode:hover {
      background-color: var(--highlight);
      color: white;
    }
    
    @media (max-width: 600px) {
      .features-grid {
        grid-template-columns: 1fr;
      }
      
      .browser-details {
        grid-template-columns: 1fr;
      }
      
      .toggle-mode {
        position: relative;
        top: 0;
        right: 0;
        margin-bottom: 20px;
      }
    }
---js
// Check for system preference and set initial mode
if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) {
  document.documentElement.setAttribute('data-theme', 'dark');
}

// Features data
const features = [
{
  name: "Array.prototype.includes()",
  version: "ES7",
  description: "Checks if an array includes a specific element. Unlike indexOf(), it properly handles NaN values.",
  example: "const array = [1, 2, 3, NaN];\narray.includes(2); // true\narray.includes(NaN); // true\narray.indexOf(NaN) !== -1; // false (can't find NaN)",
  test: function () {
    return typeof Array.prototype.includes === 'function';
  } },

{
  name: "Exponentiation Operator (**)",
  version: "ES7",
  description: "A shorthand syntax for exponentiation, replacing Math.pow().",
  example: "const squared = 5 ** 2; // 25\nconst cubed = 2 ** 3; // 8\n\nlet value = 4;\nvalue **= 2; // 16",
  test: function () {
    try {
      eval("2 ** 3");
      return true;
    } catch (e) {
      return false;
    }
  } },

{
  name: "Async/Await",
  version: "ES8",
  description: "Provides a more readable way to work with Promises, making asynchronous code look synchronous.",
  example: "async function fetchData() {\n  try {\n    const response = await fetch('https://api.example.com/data');\n    const data = await response.json();\n    return data;\n  } catch (error) {\n    console.error('Error:', error);\n  }\n}",
  test: function () {
    try {
      eval("async function test() {}");
      return true;
    } catch (e) {
      return false;
    }
  } },

{
  name: "Object.values()",
  version: "ES8",
  description: "Returns an array of a given object's property values, in the same order as Object.keys().",
  example: "const person = { name: 'Alice', age: 28, job: 'Developer' };\nObject.values(person); // ['Alice', 28, 'Developer']",
  test: function () {
    return typeof Object.values === 'function';
  } },

{
  name: "Object.entries()",
  version: "ES8",
  description: "Returns an array of a given object's [key, value] pairs, in the same order as Object.keys().",
  example: "const person = { name: 'Alice', age: 28 };\nObject.entries(person);\n// [['name', 'Alice'], ['age', 28]]",
  test: function () {
    return typeof Object.entries === 'function';
  } },

{
  name: "Object.getOwnPropertyDescriptors()",
  version: "ES8",
  description: "Returns all property descriptors of an object, enabling proper copying of getters and setters.",
  example: "const obj = { get foo() { return 42; } };\nObject.getOwnPropertyDescriptors(obj);\n// { foo: { get: [Function: get foo], set: undefined, enumerable: true, configurable: true } }",
  test: function () {
    return typeof Object.getOwnPropertyDescriptors === 'function';
  } },

{
  name: "String.prototype.padStart()",
  version: "ES8",
  description: "Pads the start of a string with a specified character to a target length.",
  example: "// Add leading zeros\n'5'.padStart(3, '0'); // '005'\n\n// Center align text\n'hello'.padStart(10); // '     hello'",
  test: function () {
    return typeof String.prototype.padStart === 'function';
  } },

{
  name: "String.prototype.padEnd()",
  version: "ES8",
  description: "Pads the end of a string with a specified character to a target length.",
  example: "// Fixed width columns\n'hello'.padEnd(10); // 'hello     '\n\n// Formatting:\n'5'.padEnd(3, '0'); // '500'",
  test: function () {
    return typeof String.prototype.padEnd === 'function';
  } },

{
  name: "Trailing Commas in Function Parameters",
  version: "ES8",
  description: "Allows trailing commas in function parameter lists, making version control diffs cleaner when adding or removing parameters.",
  example: "function example(\n  param1,\n  param2,\n  param3, // <- Trailing comma is now valid\n) {\n  // function body\n}",
  test: function () {
    try {
      eval("function test(a, b, ) {}");
      return true;
    } catch (e) {
      return false;
    }
  } },

{
  name: "Shared Memory and Atomics",
  version: "ES8",
  description: "Provides a way to share memory between workers and the main thread, with atomic operations to prevent race conditions.",
  example: "// Create a shared buffer\nconst sharedBuffer = new SharedArrayBuffer(1024);\n\n// Create a shared typed array\nconst sharedArray = new Int32Array(sharedBuffer);\n\n// Atomic operations\nAtomics.store(sharedArray, 0, 42);",
  test: function () {
    return typeof SharedArrayBuffer === 'function' && typeof Atomics === 'object';
  } }];



// Get browser information
function getBrowserInfo() {
  const ua = navigator.userAgent;
  let browserName;
  let browserVersion;

  // Extract browser name and version
  if (ua.indexOf("Firefox") > -1) {
    browserName = "Mozilla Firefox";
    browserVersion = ua.match(/Firefox\/(\d+\.\d+)/)[1];
  } else if (ua.indexOf("SamsungBrowser") > -1) {
    browserName = "Samsung Internet";
    browserVersion = ua.match(/SamsungBrowser\/(\d+\.\d+)/)[1];
  } else if (ua.indexOf("Opera") > -1 || ua.indexOf("OPR") > -1) {
    browserName = "Opera";
    browserVersion = ua.match(/(?:Opera|OPR)\/(\d+\.\d+)/)[1];
  } else if (ua.indexOf("Trident") > -1) {
    browserName = "Internet Explorer";
    browserVersion = ua.match(/rv:(\d+\.\d+)/)[1];
  } else if (ua.indexOf("Edge") > -1) {
    browserName = "Microsoft Edge (Legacy)";
    browserVersion = ua.match(/Edge\/(\d+\.\d+)/)[1];
  } else if (ua.indexOf("Edg") > -1) {
    browserName = "Microsoft Edge (Chromium)";
    browserVersion = ua.match(/Edg\/(\d+\.\d+)/)[1];
  } else if (ua.indexOf("Chrome") > -1) {
    browserName = "Google Chrome";
    browserVersion = ua.match(/Chrome\/(\d+\.\d+)/)[1];
  } else if (ua.indexOf("Safari") > -1) {
    browserName = "Apple Safari";
    browserVersion = ua.match(/Version\/(\d+\.\d+)/)[1];
  } else {
    browserName = "Unknown Browser";
    browserVersion = "Unknown Version";
  }

  return {
    name: browserName,
    version: browserVersion,
    userAgent: ua,
    engine: navigator.product,
    platform: navigator.platform,
    language: navigator.language,
    cookiesEnabled: navigator.cookieEnabled };

}

// Display browser info
function displayBrowserInfo() {
  const browserInfo = getBrowserInfo();
  const browserDetails = document.getElementById('browserDetails');

  browserDetails.innerHTML = `
        <div class="browser-detail">
          <div class="detail-label">Browser</div>
          <div class="detail-value">${browserInfo.name} ${browserInfo.version}</div>
        </div>
        <div class="browser-detail">
          <div class="detail-label">Platform</div>
          <div class="detail-value">${browserInfo.platform}</div>
        </div>
        <div class="browser-detail">
          <div class="detail-label">Language</div>
          <div class="detail-value">${browserInfo.language}</div>
        </div>
        <div class="browser-detail">
          <div class="detail-label">User Agent</div>
          <div class="detail-value">${browserInfo.userAgent}</div>
        </div>
      `;
}

// Test features and calculate support
function testFeatures() {
  const featuresGrid = document.getElementById('featuresGrid');
  let supportedCount = 0;
  let es7Count = 0;
  let es7Supported = 0;
  let es8Count = 0;
  let es8Supported = 0;

  features.forEach(feature => {
    const isSupported = feature.test();
    if (isSupported) supportedCount++;

    if (feature.version === 'ES7') {
      es7Count++;
      if (isSupported) es7Supported++;
    } else if (feature.version === 'ES8') {
      es8Count++;
      if (isSupported) es8Supported++;
    }

    const featureCard = document.createElement('div');
    featureCard.className = 'feature-card';

    featureCard.innerHTML = `
          <div class="feature-header">
            <h3 class="feature-title">${feature.name}</h3>
            <span class="feature-version ${feature.version.toLowerCase()}">${feature.version}</span>
          </div>
          <div class="feature-body">
            <p class="feature-description">${feature.description}</p>
            <pre class="code-example">${feature.example}</pre>
            <div class="support-status ${isSupported ? 'supported' : 'not-supported'}">
              <span class="icon">${isSupported ? '✓' : '✗'}</span>
              <span>${isSupported ? 'Supported' : 'Not Supported'}</span>
            </div>
          </div>
        `;

    featuresGrid.appendChild(featureCard);
  });

  // Update progress bars
  const totalPercentage = Math.round(supportedCount / features.length * 100);
  const es7Percentage = Math.round(es7Supported / es7Count * 100);
  const es8Percentage = Math.round(es8Supported / es8Count * 100);

  document.getElementById('overallPercentage').textContent = `${totalPercentage}%`;
  document.getElementById('es7Percentage').textContent = `${es7Percentage}%`;
  document.getElementById('es8Percentage').textContent = `${es8Percentage}%`;

  document.getElementById('overallProgress').style.width = `${totalPercentage}%`;
  document.getElementById('es7Progress').style.width = `${es7Percentage}%`;
  document.getElementById('es8Progress').style.width = `${es8Percentage}%`;
}

// Initialize
window.addEventListener('DOMContentLoaded', () => {
  displayBrowserInfo();
  testFeatures();
});

Best Practices for ES8's Async/Await#

To make the most of async/await, keep these best practices in mind:

  1. Always handle errors with try/catch blocks or .catch() on the function call

  2. Remember that async functions always return promises

  3. Use Promise.all() for independent operations to improve performance

  4. Don't overuse await for operations that could run concurrently

  5. Consider a cleanup function for resources even if an error occurs

Conclusion: The JavaScript Renaissance Continues#

ES7 and ES8 might not have the same revolutionary impact as ES6, but they've significantly improved how we write JavaScript—particularly for handling asynchronous operations. Async/await alone has transformed one of the language's most challenging aspects into elegant, readable code that feels almost like synchronous programming.

As JavaScript continues to evolve with yearly updates, we see a language growing more mature, expressive, and developer-friendly. These incremental improvements are making JavaScript not just more powerful but also more enjoyable to write.

These features are not theoretical. They are old enough to use in any project today. Try the interactive examples above, then rewrite one of your own callback chains with async/await and see how much shorter it gets.

Comments require JavaScript.