From 1a1ae6d91f20cfc0a13588d1e8d97f93b6dcdee9 Mon Sep 17 00:00:00 2001 From: Ajay Dhangar Date: Fri, 4 Sep 2026 21:06:15 +0530 Subject: [PATCH 1/2] added more docs --- docs/01-getting-started/introduction.md | 6 - .../variables-and-scope.md | 6 +- .../event-loop-and-task-queue.md | 119 ++++++++++++ .../fetch-api-and-ajax.md | 162 +++++++++++++++++ .../promises-and-async-await.md | 155 ++++++++++++++++ .../async-await-playground.js | 22 +++ .../event-loop-exercise.js | 12 ++ .../event-loop-playground.js | 15 ++ .../asynchronous-javascript/live-fetch.js | 25 +++ src/components/js-live-code-editor/index.tsx | 169 ++++++++++++++---- 10 files changed, 643 insertions(+), 48 deletions(-) create mode 100644 docs/_scripts/asynchronous-javascript/async-await-playground.js create mode 100644 docs/_scripts/asynchronous-javascript/event-loop-exercise.js create mode 100644 docs/_scripts/asynchronous-javascript/event-loop-playground.js create mode 100644 docs/_scripts/asynchronous-javascript/live-fetch.js diff --git a/docs/01-getting-started/introduction.md b/docs/01-getting-started/introduction.md index 7092bdd..3b874c5 100644 --- a/docs/01-getting-started/introduction.md +++ b/docs/01-getting-started/introduction.md @@ -96,12 +96,6 @@ Ready to dive deeper? Proceed to the next page to configure your local developme ``` docs/ -├── 03-deep-dive-core/ -│ ├── _category_.json -│ ├── closures-and-lexical-scope.md -│ ├── prototype-and-inheritance.md -│ ├── execution-context-and-callstack.md -│ └── this-keyword-explained.md ├── 04-asynchronous-javascript/ │ ├── _category_.json │ ├── event-loop-and-task-queue.md diff --git a/docs/02-javascript-fundamentals/variables-and-scope.md b/docs/02-javascript-fundamentals/variables-and-scope.md index a362761..9e1e723 100644 --- a/docs/02-javascript-fundamentals/variables-and-scope.md +++ b/docs/02-javascript-fundamentals/variables-and-scope.md @@ -53,7 +53,7 @@ Variables declared outside any function or block belong to the global scope. :::note -When you run the above code, the output is undefined because the variable `globalAppTitle` is declared in the global scope and is accessible inside the function `printTitle()`. +In the example above, `globalAppTitle` is declared in the global scope, making it accessible from anywhere in the script, including inside functions like `printTitle()`. So, when we call `printTitle()`, it logs the value of `globalAppTitle` to the console. @@ -107,9 +107,9 @@ Try editing the script below to observe how block scope and variable re-assignme ## Best Practices -1. **Default to `const**`: Protect variables from unintended re-assignments. +1. **Default to `const`**: Protect variables from unintended re-assignments. 2. **Use `let` for reassignment**: Use only when values must mutate (e.g., loop counters, accumulators). -3. **Avoid `var**`: Prevent scope-leakage issues and silent hoisting bugs in modern applications. +3. **Avoid `var`**: Prevent scope-leakage issues and silent hoisting bugs in modern applications. ## Knowledge Check diff --git a/docs/04-asynchronous-javascript/event-loop-and-task-queue.md b/docs/04-asynchronous-javascript/event-loop-and-task-queue.md index e69de29..85aa56f 100644 --- a/docs/04-asynchronous-javascript/event-loop-and-task-queue.md +++ b/docs/04-asynchronous-javascript/event-loop-and-task-queue.md @@ -0,0 +1,119 @@ +--- +id: event-loop-and-task-queue +title: "The Event Loop & Task Queue Mechanics" +sidebar_label: Event Loop & Task Queues +sidebar_position: 1 +description: "Master the JavaScript Event Loop, asynchronous execution concurrency, Call Stack, Microtask Queue, Macrotask Queue, and microtask starvation." +tags: [javascript, async, event-loop, microtasks, macrotasks, concurrency] +keywords: [javascript, async, event-loop, microtasks, macrotasks, concurrency] +--- + +import JSEditor from "@site/src/components/js-live-code-editor"; +import CodeBlock from "@theme/CodeBlock"; +import firstExample from "!!raw-loader!../_scripts/asynchronous-javascript/event-loop-playground.js"; +import secondExample from "!!raw-loader!../_scripts/asynchronous-javascript/event-loop-exercise.js"; + +JavaScript is single-threaded, meaning it can only execute one line of code at a time on its main thread. Despite this limitation, it handles non-blocking asynchronous operations—such as network requests, file I/O, and timers—efficiently through the **Event Loop**. + +## Architecture of Asynchronous Execution + +To handle asynchronous tasks without freezing the user interface, the JavaScript engine works alongside browser Web APIs (or Node.js C++ bindings) and two distinct execution queues. + +```text +JavaScript Asynchronous Architecture +┌─────────────────────────────────────────────────────────────┐ +│ Call Stack (Synchronous Execution LIFO) │ +└──────────────────────────────┬──────────────────────────────┘ + │ Delegation + ▼ +┌─────────────────────────────────────────────────────────────┐ +│ Web APIs (Timers, Fetch HTTP, DOM Events, File Systems) │ +└──────────────────────────────┬──────────────────────────────┘ + │ Callbacks Ready + ▼ +┌──────────────────────────────┬──────────────────────────────┐ +│ Microtask Queue (High-Priority)│ Macrotask Queue (Task Queue) +│ ├── Promises (.then/catch) │ ├── setTimeout / setInterval │ +│ ├── queueMicrotask() │ ├── setImmediate (Node.js) │ +│ └── MutationObserver │ └── requestAnimationFrame │ +└──────────────┬───────────────┴──────────────┬───────────────┘ + │ │ + └───────────────┬──────────────┘ + │ Event Loop Tick + ▼ +┌─────────────────────────────────────────────────────────────┐ +│ Call Stack (Executed when Call Stack is empty) │ +└─────────────────────────────────────────────────────────────┘ + +``` + +## Microtasks vs. Macrotasks (Task Priorities) + +The Event Loop continuously monitors the **Call Stack**. When the stack is empty, it processes pending callbacks according to a strict priority hierarchy: + +| Queue Type | Operations | Execution Rule / Priority | +| --- | --- | --- | +| **Microtask Queue** | `Promise` callbacks, `queueMicrotask()`, `process.nextTick` (Node) | **Highest Priority**: Drained **completely** until empty before any macrotask runs. | +| **Macrotask Queue** | `setTimeout`, `setInterval`, `setImmediate`, I/O, UI Rendering | **Normal Priority**: Processes **one single task** per Event Loop tick, then checks microtasks again. | + +## The Event Loop Execution Algorithm + +During every cycle ("tick") of the Event Loop, the runtime follows these exact steps: + +1. **Execute Synchronous Code**: Process all frames in the Call Stack until it is completely empty. +2. **Drain Microtask Queue**: Process every single microtask currently in the queue. If a microtask schedules another microtask, it is executed in the *same* cycle. +3. **Render UI (Browser Only)**: Perform DOM re-paints and run `requestAnimationFrame` callbacks if needed. +4. **Execute One Macrotask**: Dequeue and run the oldest single task from the Macrotask Queue. +5. **Repeat**: Loop back to step 1. + +## Microtask Starvation + +Because the Event Loop must drain the **entire** Microtask Queue before yielding execution to macrotasks or UI rendering, continuously enqueuing microtasks will block the main thread indefinitely: + +```javascript title="Infinite Microtask Starvation Example" +// WARNING: This recursively starves the Macrotask Queue and freezes the UI! +function infiniteMicrotask() { + Promise.resolve().then(() => { + infiniteMicrotask(); // Enqueues another microtask infinitely + }); +} + +// infiniteMicrotask(); // Un-commenting will crash/freeze the thread! + +``` + +## Interactive Playground: Tracing Execution Order + +Predict the log sequence of synchronous statements, `setTimeout`, and `Promise.then` callbacks: + + + {firstExample} + + +## Best Practices + +1. **Use Promises/Microtasks for Instant State Updates**: When you need async operations to resolve immediately before DOM repainting or downstream state changes occurs. +2. **Offload Heavy Loops with `setTimeout`**: Yield execution back to the browser frame engine by breaking intensive computations into macrotask chunks (`setTimeout(fn, 0)`). +3. **Avoid Infinite Microtask Chaining**: Never recursively queue microtasks (`queueMicrotask` or `.then`) without exit conditions; doing so blocks DOM rendering and input events. + + +## Knowledge Check + +### Exercise Requirements: + +Determine the exact console output order for the following code snippet: + + + {secondExample} + + +#### Explanation: + +1. Synchronous execution logs `A` and `F`. +2. Microtask Queue processes `C` and `E`. +3. Inside microtask `C`, a new macrotask (`D`) is scheduled behind `B`. +4. Macrotask Queue runs `B` first (oldest macrotask), then runs `D`. + +:::success Next Up +Now that you have mastered Event Loop ordering and task queues, proceed to **Promises and Async/Await**! +::: \ No newline at end of file diff --git a/docs/04-asynchronous-javascript/fetch-api-and-ajax.md b/docs/04-asynchronous-javascript/fetch-api-and-ajax.md index e69de29..f693116 100644 --- a/docs/04-asynchronous-javascript/fetch-api-and-ajax.md +++ b/docs/04-asynchronous-javascript/fetch-api-and-ajax.md @@ -0,0 +1,162 @@ +--- +id: fetch-api-and-ajax +title: "Fetch API & Asynchronous HTTP" +sidebar_label: Fetch API & AJAX +sidebar_position: 3 +description: "Master asynchronous HTTP requests in JavaScript with the Fetch API, AbortController timeout handling, streaming responses, and proper error handling." +tags: [javascript, async, fetch, ajax, http, abortcontroller, web-apis] +keywords: [javascript, async, fetch, ajax, http, abortcontroller, web-apis] +--- + +import JSEditor from "@site/src/components/js-live-code-editor"; +import CodeBlock from "@theme/CodeBlock"; +import firstExample from "!!raw-loader!../_scripts/asynchronous-javascript/live-fetch.js"; + +Asynchronous JavaScript and XML (**AJAX**) allows web applications to communicate with backend servers asynchronously without reloading the current page. Modern JavaScript applications utilize the native promise-based **Fetch API** for networking. + +## Evolution: `XMLHttpRequest` to `fetch()` + +Before `fetch()`, asynchronous requests relied on `XMLHttpRequest` (XHR), which required verbose, callback-driven code: + +```javascript title="legacy-xhr.js" +const xhr = new XMLHttpRequest(); +xhr.open("GET", "https://api.example.com/data"); +xhr.onload = function () { + if (xhr.status >= 200 && xhr.status < 300) { + console.log(JSON.parse(xhr.responseText)); + } +}; +xhr.onerror = function () { + console.error("Network Error"); +}; +xhr.send(); +``` + +The modern **Fetch API** streamlines networking with standard Promises and cleaner configuration objects: + +```javascript title="modern-fetch.js" +// Modern Promise-based Fetch +fetch("https://api.example.com/data") + .then((response) => response.json()) + .then((data) => console.log(data)) + .catch((error) => console.error("Fetch Error:", error)); +``` + +## Configuring Requests & Headers + +The `fetch()` function accepts two arguments: the target `resource` URL and an optional `options` configuration object. + +```javascript title="fetch-config.js" +async function createUser(userData) { + const response = await fetch("https://api.example.com/users", { + method: "POST", // HTTP Method: GET, POST, PUT, DELETE, PATCH + headers: { + "Content-Type": "application/json", + "Authorization": "Bearer YOUR_JWT_TOKEN" + }, + body: JSON.stringify(userData), // Serialize JavaScript object to JSON + mode: "cors", // cors, no-cors, or same-origin + cache: "no-cache" + }); + + return await response.json(); +} + +``` + +## The Fetch Error-Handling Gotcha + +Unlike HTTP libraries like Axios, `fetch()` **does not reject** its Promise on HTTP error status codes (such as `404 Not Found` or `500 Internal Server Error`). A `fetch()` Promise only rejects on network failures or blocked requests. + +To handle HTTP errors properly, check the `response.ok` boolean property (`status` between 200–299): + +```javascript title="safe-fetch.js" +async function safeFetch(url) { + try { + const response = await fetch(url); + + // Check for HTTP error status codes (4xx, 5xx) + if (!response.ok) { + throw new Error(`HTTP Error! Status: ${response.status} ${response.statusText}`); + } + + const data = await response.json(); + return data; + } catch (error) { + console.error("Request Failed:", error.message); + } +} + +``` + +## Aborting Requests & Timeouts (`AbortController`) + +To cancel ongoing network requests or implement request timeouts, use the native **`AbortController`** interface: + +```javascript title="abort-fetch.js" +async function fetchWithTimeout(resource, options = {}) { + const { timeout = 5000 } = options; + + // 1. Create an AbortController instance + const controller = new AbortController(); + const id = setTimeout(() => controller.abort(), timeout); + + try { + const response = await fetch(resource, { + ...options, + signal: controller.signal // Connect signal to fetch + }); + clearTimeout(id); + return await response.json(); + } catch (error) { + if (error.name === "AbortError") { + throw new Error("Request timed out!"); + } + throw error; + } +} + +``` + +## Interactive Playground: Live Fetch Explorer + +Run the live `fetch()` request below using JSONPlaceholder API: + + + {firstExample} + + +## Best Practices + +1. **Always Check `response.ok`**: Never assume a resolved `fetch()` promise indicates a successful data payload. +2. **Set Request Timeouts**: Wrap network calls with `AbortController` to prevent hanging pending requests on poor connections. +3. **Use Typed Response Parsing**: Match body parsing methods to payloads (`.json()`, `.text()`, `.blob()`, or `.arrayBuffer()`). + +## Knowledge Check + +### Exercise Requirements: + +Write a reusable function `postJSON(url, payload)` using `async/await` that sends a `POST` request, handles non-2xx HTTP errors properly, and returns the parsed JSON response. + +```javascript title="solution.js" +async function postJSON(url, payload) { + const response = await fetch(url, { + method: "POST", + headers: { + "Content-Type": "application/json" + }, + body: JSON.stringify(payload) + }); + + if (!response.ok) { + throw new Error(`Failed to POST to ${url} - Status: ${response.status}`); + } + + return await response.json(); +} + +``` + +:::success Phase 04 Complete! +Congratulations! You have completed **Phase 04: Asynchronous JavaScript**. Proceed to **Phase 05: DOM & Browser APIs** to build interactive Web applications! +::: \ No newline at end of file diff --git a/docs/04-asynchronous-javascript/promises-and-async-await.md b/docs/04-asynchronous-javascript/promises-and-async-await.md index e69de29..43e56b5 100644 --- a/docs/04-asynchronous-javascript/promises-and-async-await.md +++ b/docs/04-asynchronous-javascript/promises-and-async-await.md @@ -0,0 +1,155 @@ +--- +id: promises-and-async-await +title: "Promises & Async/Await" +sidebar_label: Promises & Async/Await +sidebar_position: 2 +description: "Master JavaScript Promises, Promise chaining, static concurrency combinators, async/await syntax, top-level await, and resilient error handling pattern." +tags: [javascript, async, promises, async-await, error-handling, es6] +keywords: [javascript, async, promises, async-await, error-handling, es6] +--- + +import JSEditor from "@site/src/components/js-live-code-editor"; +import CodeBlock from "@theme/CodeBlock"; +import firstExample from "!!raw-loader!../_scripts/asynchronous-javascript/async-await-playground.js"; + +Managing asynchronous operations using nested callbacks leads to unmaintainable "Callback Hell." Modern JavaScript handles asynchronous workflows cleanly using **Promises** and **`async/await`** syntax. + +## Understanding Promises + +A **Promise** is an object representing the eventual completion (or failure) of an asynchronous operation and its resulting value. + +```text +Promise Lifecycle & States + ┌───────────────┐ + │ Pending │ + │ (Initial State)│ + └───────┬───────┘ + │ + ┌──────────────────┴──────────────────┐ + ▼ ▼ + ┌─────────────────┐ ┌─────────────────┐ + │ Fulfilled │ │ Rejected │ + │ (resolve(val)) │ │ (reject(err)) │ + └────────┬────────┘ └────────┬────────┘ + │ │ + ▼ ▼ + .then(onFulfilled) .catch(onRejected) + +``` + +A Promise can exist in one of three mutually exclusive states: + +* **`pending`**: Initial state; neither fulfilled nor rejected. +* **`fulfilled`**: Operation completed successfully (`resolve()` was called). +* **`rejected`**: Operation failed (`reject()` was called). + +## Promise Chaining & Error Propagation + +The `.then()` method returns a **new Promise**, allowing operations to be chained sequentially: + +```javascript +function fetchUser(id) { + return new Promise((resolve, reject) => { + if (id <= 0) reject(new Error("Invalid User ID")); + setTimeout(() => resolve({ id, name: "Alex" }), 100); + }); +} + +fetchUser(1) + .then((user) => { + console.log("Fetched User:", user.name); + return user.id; + }) + .then((userId) => { + console.log("Processing ID:", userId); + }) + .catch((error) => { + console.error("Error encountered:", error.message); + }) + .finally(() => { + console.log("Cleanup complete"); + }); + +``` + +## Async/Await Syntax + +Introduced in ES2017, `async` and `await` are syntactic sugar built on top of Promises, making asynchronous code look and behave like synchronous code. + +```javascript +async function getUserProfile(userId) { + try { + const user = await fetchUser(userId); + console.log(`User Profile: ${user.name}`); + return user; + } catch (error) { + console.error("Failed to load profile:", error.message); + } finally { + console.log("Request finished"); + } +} + +``` + +:::info Key Rules of Async/Await + +1. Marking a function `async` wraps its return value in a **Promise**. +2. `await` pauses function execution until the awaited Promise settles (fulfills or rejects). +3. `await` can only be used inside `async` functions (or at top-level in ES modules). +::: + +## Static Promise Combinators (Concurrency Methods) + +JavaScript provides 4 static methods to execute multiple Promises concurrently: + +| Method | Behavior | Fails When... | Use Case | +| --- | --- | --- | --- | +| **`Promise.all()`** | Resolves when **all** promises resolve; returns array of results. | **Any** promise rejects (short-circuits). | Parallel operations where all must succeed. | +| **`Promise.allSettled()`** | Resolves when **all** promises settle (returns status + value/reason). | **Never** rejects. | Batch jobs where failure of one shouldn't cancel others. | +| **`Promise.race()`** | Settles as soon as the **first** promise settles (fulfilled or rejected). | Rejects if first promise rejects. | Timeouts or returning fastest responding node. | +| **`Promise.any()`** | Resolves as soon as the **first** promise fulfills. | Rejects only if **all** promises reject. | Fetching redundant fallback endpoints. | + +```javascript +const requestA = fetch("/api/v1"); +const requestB = fetch("/api/v2"); + +// Wait for both in parallel +const [resA, resB] = await Promise.all([requestA, requestB]); + +``` + +## Interactive Playground: Async/Await Workflow + +Experiment with resolved vs. rejected Promises using `async/await` and `try/catch`: + + + {firstExample} + + +## Best Practices + +1. **Avoid Sequential `await` in Loops**: Use `Promise.all()` when requests don't depend on each other to prevent artificial performance bottlenecks. +2. **Always Handle Errors**: Wrap `await` expressions in `try/catch` blocks or attach a `.catch()` fallback to prevent unhandled promise rejections. +3. **Prefer `Promise.allSettled` for Batch Requests**: When firing multiple non-critical requests, `allSettled()` prevents one failing call from discarding successful results. + +## Knowledge Check + +### Exercise Requirements: + +Refactor the following promise-chain code to use clean `async/await` syntax with proper `try/catch` error handling: + +```javascript title="promise-chain.js" +async function loadData(id) { + try { + const user = await fetchUser(id); + const posts = await fetchPosts(user.id); + console.log(posts); + } catch (err) { + console.error("Error loading user posts:", err); + } +} +``` + +:::success Next Up +Now that you have mastered Promises and `async/await`, proceed to **Microtasks, Macrotasks, and Web APIs**! +::: \ No newline at end of file diff --git a/docs/_scripts/asynchronous-javascript/async-await-playground.js b/docs/_scripts/asynchronous-javascript/async-await-playground.js new file mode 100644 index 0000000..7dcbf24 --- /dev/null +++ b/docs/_scripts/asynchronous-javascript/async-await-playground.js @@ -0,0 +1,22 @@ +const mockApiCall = (shouldSucceed) => { + return new Promise((resolve, reject) => { + setTimeout(() => { + if (shouldSucceed) { + resolve({ data: "Fetched Data Successfully!" }); + } else { + reject(new Error("API Call Failed!")); + } + }, 1000); + }); +}; +async function runDataPipeline() { + console.log("Fetching data..."); + try { + const response = await mockApiCall(true); + console.log("Success:", response.data); + } catch (err) { + console.error("Caught error:", err.message); + } +} + +runDataPipeline(); diff --git a/docs/_scripts/asynchronous-javascript/event-loop-exercise.js b/docs/_scripts/asynchronous-javascript/event-loop-exercise.js new file mode 100644 index 0000000..9cf89d1 --- /dev/null +++ b/docs/_scripts/asynchronous-javascript/event-loop-exercise.js @@ -0,0 +1,12 @@ +console.log("A"); + +setTimeout(() => console.log("B"), 0); + +Promise.resolve().then(() => { + console.log("C"); + setTimeout(() => console.log("D"), 0); +}); + +Promise.resolve().then(() => console.log("E")); + +console.log("F"); \ No newline at end of file diff --git a/docs/_scripts/asynchronous-javascript/event-loop-playground.js b/docs/_scripts/asynchronous-javascript/event-loop-playground.js new file mode 100644 index 0000000..c0c08c0 --- /dev/null +++ b/docs/_scripts/asynchronous-javascript/event-loop-playground.js @@ -0,0 +1,15 @@ +console.log("1. Synchronous Start"); + +setTimeout(() => { + console.log("4. Macrotask: setTimeout"); +}, 0); + +Promise.resolve() + .then(() => { + console.log("3. Microtask: Promise 1"); + }) + .then(() => { + console.log("3. Microtask: Promise 2"); + }); + +console.log("2. Synchronous End"); diff --git a/docs/_scripts/asynchronous-javascript/live-fetch.js b/docs/_scripts/asynchronous-javascript/live-fetch.js new file mode 100644 index 0000000..5425bc6 --- /dev/null +++ b/docs/_scripts/asynchronous-javascript/live-fetch.js @@ -0,0 +1,25 @@ +async function getTodo() { + try { + // 1. Send GET request to the mock API + const response = await fetch("https://jsonplaceholder.typicode.com/todos/1"); + + // 2. Check if the HTTP status code is successful (200-299) + if (!response.ok) { + throw new Error(`HTTP Error! Status: ${response.status}`); + } + + // 3. Parse JSON response body + const todo = await response.json(); + + // 4. Output the result + console.log("Fetched Todo:", todo); + return todo; + + } catch (error) { + // 5. Catch network failures or manually thrown HTTP errors + console.error("Fetch Error:", error.message); + } +} + +// Execute the function +getTodo(); diff --git a/src/components/js-live-code-editor/index.tsx b/src/components/js-live-code-editor/index.tsx index 5d37151..9abeb1d 100644 --- a/src/components/js-live-code-editor/index.tsx +++ b/src/components/js-live-code-editor/index.tsx @@ -1,11 +1,11 @@ -import React, { useEffect, useState } from "react"; +import React, { useEffect, useState, useRef, useCallback } from "react"; import BrowserOnly from "@docusaurus/BrowserOnly"; import ExecutionEnvironment from "@docusaurus/ExecutionEnvironment"; import Editor from "react-simple-code-editor"; import inspect from "object-inspect"; import { Prism, themes } from "prism-react-renderer"; import clsx from "clsx"; -import { Copy, Check, Play, RotateCcw } from "lucide-react"; // Install lucide-react or use SVGs +import { Copy, Check, Play, RotateCcw } from "lucide-react"; import normalizeTokens from "./normalizeTokens"; import themeToDict from "./themeToDict"; @@ -21,30 +21,114 @@ if (ExecutionEnvironment.canUseDOM) { interface Props { children: string; title?: string; + run?: boolean; } export default function JSEditor({ children = "", title = "index.js" }: Props) { const [code, setCode] = useState(children.trim()); - const [output, setOutput] = useState(""); + const [outputLines, setOutputLines] = useState([]); const [copied, setCopied] = useState(false); + const [isRunning, setIsRunning] = useState(false); - const handleRun = () => { - const results: any[] = []; - const mockLog = (...args: any[]) => results.push(...args); + // Keep track of execution ID to prevent stale logs from previous runs + const executionIdRef = useRef(0); + + const handleRun = useCallback(async () => { + const currentExecutionId = ++executionIdRef.current; + setOutputLines([]); + setIsRunning(true); + + // Functional state update ensures logs are never lost or trapped in stale closures + const mockLog = (...args: any[]) => { + if (currentExecutionId !== executionIdRef.current) return; + + const formattedArgs = args + .map((arg) => (typeof arg === "string" ? arg : inspect(arg, { depth: 4 }))) + .join(" "); + + setOutputLines((prev) => [...prev, formattedArgs]); + }; try { - const script = code.replace(/console\.log\s*\(/g, "__log("); - const execute = new Function("__log", script); - execute(mockLog); - setOutput(results.map((line) => inspect(line)).join("\n") || "undefined"); + // 1. Transform console.log & console.error statements + let transformedCode = code + .replace(/console\.log\s*\(/g, "__log(") + .replace(/console\.error\s*\(/g, "__log('❌ Error:', "); + + // 2. Track pending macrotasks (setTimeout / setInterval) + const pendingTasks = new Set>(); + + const customSetTimeout = (cb: Function, delay?: number, ...args: any[]) => { + if (currentExecutionId !== executionIdRef.current) return; + + let resolveTask: () => void; + const taskPromise = new Promise((res) => { + resolveTask = res; + }); + pendingTasks.add(taskPromise); + + return window.setTimeout(async () => { + if (currentExecutionId !== executionIdRef.current) return; + try { + await cb(...args); + } catch (err: any) { + mockLog(`❌ Async Error: ${err.message}`); + } finally { + pendingTasks.delete(taskPromise); + resolveTask(); + } + }, delay); + }; + + const customSetInterval = (cb: Function, delay?: number, ...args: any[]) => { + if (currentExecutionId !== executionIdRef.current) return; + return window.setInterval(async () => { + if (currentExecutionId !== executionIdRef.current) return; + try { + await cb(...args); + } catch (err: any) { + mockLog(`❌ Async Error: ${err.message}`); + } + }, delay); + }; + + // 3. Construct an AsyncFunction wrapper to allow top-level await and async calls + const AsyncFunction = Object.getPrototypeOf(async function () {}).constructor; + const execute = new AsyncFunction( + "__log", + "setTimeout", + "setInterval", + transformedCode + ); + + // Execute synchronous phase + await execute(mockLog, customSetTimeout, customSetInterval); + + // 4. Wait for pending Promise microtasks to flush + await new Promise((res) => setTimeout(res, 0)); + + // 5. Wait for all tracked pending macrotasks (setTimeout callbacks) to complete + while (pendingTasks.size > 0) { + await Promise.all(Array.from(pendingTasks)); + // Flush microtasks queued during macrotask execution + await new Promise((res) => setTimeout(res, 0)); + } } catch (err: any) { - setOutput(`❌ Error: ${err.message}`); + if (currentExecutionId === executionIdRef.current) { + setOutputLines((prev) => [...prev, `❌ Runtime Error: ${err.message}`]); + } + } finally { + if (currentExecutionId === executionIdRef.current) { + setIsRunning(false); + } } - }; + }, [code]); const handleReset = () => { + executionIdRef.current++; setCode(children.trim()); - setOutput(""); + setOutputLines([]); + setIsRunning(false); }; const handleCopy = () => { @@ -62,44 +146,49 @@ export default function JSEditor({ children = "", title = "index.js" }: Props) { className={clsx( styles.browserWindow, "mb-10 overflow-hidden border-2 rounded-xl transition-all duration-300 shadow-lg hover:shadow-2xl", - "border-[var(--ifm-contents-border-color)]", + "border-[var(--ifm-contents-border-color)]" )} > {/* Header */}
- {/* 1. Left Section: Buttons (Fixed Width) */} + {/* Left Section: Window Buttons */}
- {/* 2. Center Section: Address Bar (Flexible & Centered) */} + {/* Center Section: Title Bar */}
- + {title}
- {/* 3. Right Section: Action Buttons (Fixed Width) */} + {/* Right Section: Copy Button */}
- {/* Output Area */} + {/* Console Output Area */}
Console Output - {output && Done} + {isRunning ? ( + Executing... + ) : outputLines.length > 0 ? ( + Done + ) : null}
             
               {() =>
-                output || (
+                outputLines.length > 0 ? (
+                  outputLines.join("\n")
+                ) : (
                   
                     // Run the code to see results...
                   
@@ -201,4 +292,4 @@ export default function JSEditor({ children = "", title = "index.js" }: Props) {
       
); -} +} \ No newline at end of file From bbd586aba7fe47751c606f07f14343506a3b31fc Mon Sep 17 00:00:00 2001 From: Ajay Dhangar Date: Fri, 4 Sep 2026 21:06:58 +0530 Subject: [PATCH 2/2] added more docs --- docs/_scripts/asynchronous-javascript/live-fetch.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/_scripts/asynchronous-javascript/live-fetch.js b/docs/_scripts/asynchronous-javascript/live-fetch.js index 5425bc6..5af25ce 100644 --- a/docs/_scripts/asynchronous-javascript/live-fetch.js +++ b/docs/_scripts/asynchronous-javascript/live-fetch.js @@ -22,4 +22,4 @@ async function getTodo() { } // Execute the function -getTodo(); +getTodo(); \ No newline at end of file