Session 9
Javascript Asynchronous Programming
JavaScript is inherently a single-threaded, synchronous language, meaning it executes code line-by-line from top to bottom. However, modern web applications frequently require tasks that take time to complete (e.g., fetching data from a server, waiting for user input, or running timers). If JavaScript waited for these tasks to finish before moving to the next line, the entire browser UI would freeze. This session introduces Asynchronous Programming, which allows JavaScript to initiate a long-running task, continue executing other code, and handle the result later using callbacks. This session covers the foundational async patterns required for responsive, non-blocking web applications.
Objectives
- Understand the difference between synchronous and asynchronous execution
- Master callback functions as the primary mechanism for async flow control
- Implement built-in async methods:
setTimeout()andsetInterval() - Load external resources dynamically using
XMLHttpRequest - Solve real-world async navigation and timing exercises
Synchronous vs. Asynchronous Execution
| Feature | Synchronous | Asynchronous |
|---|---|---|
| Execution Flow | Blocks until task completes | Starts task, moves to next line immediately |
| UI Impact | Freezes browser during long tasks | Keeps UI responsive |
| Example | console.log("Start"); console.log("End"); | setTimeout(() => console.log("End"), 2000); |
Callback Functions
A callback is a function passed as an argument to another function. It is not executed immediately; instead, it's "called back" at a later time when the parent function completes its task.
// Function that accepts a callback
function fetchData(callback) {
// Simulate delay
setTimeout(() => {
const data = "User Data Loaded";
callback(data); // Execute callback when ready
}, 1000);
}
// Pass callback WITHOUT parentheses
fetchData((result) => console.log(result)); // Logs after 1 second
Built-in Async Timer Methods
| Method | Syntax | Behavior |
|---|---|---|
setTimeout() | setTimeout(callback, delayInMs) | Executes callback once after delay |
setInterval() | setInterval(callback, intervalInMs) | Executes callback repeatedly every interval |
clearTimeout() / clearInterval() | clearInterval(timerID) | Stops the scheduled execution |
// Example: Live Clock
function updateClock() {
const now = new Date();
document.getElementById('clock').textContent = now.toLocaleTimeString();
}
const clockTimer = setInterval(updateClock, 1000); // Runs every second
clearInterval(clockTimer); // Stops it
Loading External Resources (XMLHttpRequest)
Before fetch() became standard, XMLHttpRequest (XHR) was the primary way to load external files asynchronously.
const xhr = new XMLHttpRequest();
xhr.open("GET", "external.html", true); // true = async
xhr.onload = function() {
if (xhr.status === 200) {
document.getElementById('container').innerHTML = xhr.responseText;
}
};
xhr.send(); // Triggers the request
Best Practices & Common Pitfalls
| Pitfall | Best Practice |
|---|---|
Passing callback() with parentheses | Pass function reference only: setTimeout(myFunc, 1000) not setTimeout(myFunc(), 1000) |
Assuming code after setTimeout runs later | setTimeout schedules execution; code immediately after it runs first |
| Forgetting to clear intervals | Always store setInterval ID and use clearInterval() to prevent memory leaks |
| Nesting callbacks deeply ("Callback Hell") | Keep nesting shallow; use named functions or modern Promise/async-await in production |
Ignoring XHR status codes | Always check xhr.status === 200 before processing responseText |
Quick Guide
// ⏱️ Timers
const timeoutID = setTimeout(() => console.log("Once"), 2000);
clearTimeout(timeoutID);
const intervalID = setInterval(() => console.log("Repeated"), 1000);
clearInterval(intervalID);
// 🔄 Callback Pattern
function asyncTask(param, callback) {
// Do work...
callback(result);
}
asyncTask("data", (res) => console.log(res));
// 🌐 XMLHttpRequest (Lab Requirement)
const xhr = new XMLHttpRequest();
xhr.open("GET", "file.html", true);
xhr.onload = () => { if(xhr.status===200) console.log(xhr.responseText); };
xhr.send();
Question 1
Problem Statement
Implement a clock in HTML which shows the live time in the format HH:MM:SS. Use the JavaScript setInterval() method and a callback function by displaying the system time every second.
Demo
Live Clock
Updates every second via setInterval()
Code
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Ex1: Live Clock</title>
<style>
body {
font-family: sans-serif;
text-align: center;
padding: 50px;
background: #f4f4f4;
}
</style>
</head>
<body>
<h1>Live Clock</h1>
<div
id="clock"
style="
font-size: 4rem;
font-weight: bold;
font-family: monospace;
margin-top: 20px;
"
>
00:00:00
</div>
<script>
// Callback function to update time
function updateClock() {
const now = new Date();
const h = String(now.getHours()).padStart(2, "0");
const m = String(now.getMinutes()).padStart(2, "0");
const s = String(now.getSeconds()).padStart(2, "0");
document.getElementById("clock").textContent = `${h}:${m}:${s}`;
}
// Set interval to execute callback every 1000ms
setInterval(updateClock, 1000);
updateClock(); // Initial call to avoid 1s delay
</script>
</body>
</html>
HTML View
Question 2
Problem Statement
Sometimes we use external resource files in our HTML page. The content of an external file cannot be used until loaded completely. This can be implemented with JavaScript Callback. Design a web page which loads an external HTML file on the event: onLoad. Hint: use the inbuilt class XMLHttpRequest() to use its functions: open(), onLoad() etc. to load an external HTML file.
Demo
External Content Loader (onMount)
Code
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Ex2: XHR Loader</title>
<style>
body {
font-family: sans-serif;
padding: 20px;
}
#output {
border: 2px dashed #aaa;
padding: 20px;
margin-top: 15px;
background: #fafafa;
min-height: 50px;
}
</style>
</head>
<body>
<h2>External Content Loader (onLoad Event)</h2>
<div id="output">Loading external.html...</div>
<script>
window.addEventListener("load", () => {
const xhr = new XMLHttpRequest();
// true = asynchronous
xhr.open("GET", "external.html", true);
xhr.onload = function () {
if (xhr.status === 200) {
document.getElementById("output").innerHTML =
xhr.responseText;
} else {
document.getElementById("output").textContent =
`❌ Failed to load (Status: ${xhr.status})`;
}
};
xhr.onerror = () => {
document.getElementById("output").textContent =
"❌ Network error. Ensure server is running.";
};
xhr.send();
});
</script>
</body>
</html>
<h3 style="color: blue">External File Loaded Successfully!</h3>
HTML View
Question 3
Problem Statement
The village crows own an old scalpel that they occasionally use on special missions say, to cut through screen doors or packaging. To be able to quickly track it down, every time the scalpel is moved to another nest, an entry is added to the storage of both the nest that had it and the nest that took it, under the name "scalpel", with its new location as the value.This means that finding the scalpel is a matter of following the breadcrumb trail of storage entries, until you find a nest where that points at the nest itself.
Write an async function locateScalpel that does this, starting at the nest on which it runs. You can use the anyStorage function defined earlier to access storage in arbitrary nests. The scalpel has been going around long enough that you may assume that every nest has a "scalpel" entry in its data storage.
Demo
🦅 Scalpel Tracker
Code
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Ex3: Locate Scalpel</title>
<style>
body {
font-family: sans-serif;
padding: 20px;
max-width: 600px;
margin: auto;
}
pre {
background: #f4f4f4;
padding: 15px;
border-radius: 5px;
white-space: pre-wrap;
}
</style>
</head>
<body>
<h2>🦅 Scalpel Tracker</h2>
<button id="findBtn">Find Scalpel</button>
<pre id="output">Waiting...</pre>
<script>
// Mock nest data (simulating distributed storage)
const nestData = {
CrowNestA: { scalpel: "CrowNestB" },
CrowNestB: { scalpel: "CrowNestC" },
CrowNestC: { scalpel: "CrowNestC" }, // Points to itself → found!
};
// Simulated async anyStorage function
function anyStorage(nest, name) {
return new Promise((resolve) => {
setTimeout(
() => resolve(nestData[nest]?.[name] || null),
600,
);
});
}
// Async locateScalpel implementation
async function locateScalpel(startNest) {
let current = startNest;
let trail = [current];
while (true) {
const next = await anyStorage(current, "scalpel");
if (next === current) break; // Found the self-referencing nest
trail.push(next);
current = next;
}
console.log({ foundAt: current, trail: trail.join(" → ") });
return { foundAt: current, trail: trail.join(" → ") };
}
document
.getElementById("findBtn")
.addEventListener("click", async () => {
document.getElementById("output").textContent =
"🔍 Tracing scalpel breadcrumbs...";
const result = await locateScalpel("CrowNestA");
document.getElementById("output").textContent =
`Found at: ${result.foundAt}\n👣 Trail: ${result.trail}`;
});
</script>
</body>
</html>