JAVASCRIPT: FROM ZERO TO HERO
JavaScript: Part 4 - Asynchronous JavaScript (Deep Dive)
Welcome to Part 4. This is the turning point in your journey.
In Parts 1-3, we wrote code that was Synchronous.
- Synchronous means “one at a time.” Line 1 finishes, then Line 2 starts.
- Asynchronous means “in the background.” You start a task, move on to the next line, and come back to the first task when it’s done.
Why does this matter? Imagine a website where clicking “Login” freezes the entire screen for 5 seconds while it talks to the database. You couldn’t scroll, click, or even close the tab. That is Synchronous behavior. We want Asynchronous behavior: the user can keep browsing while the login happens in the background.
Module 13: How JavaScript Works (The Event Loop)
Before writing code, you need to understand the engine.
JavaScript is Single-Threaded. This means it has only one “brain” (the Call Stack). It can literally only do one thing at a time.
So how does it handle a timer or a network request without freezing? It cheats. It hands those tasks off to the browser (the Web APIs).
- Call Stack: Runs your JS code (variables, loops).
- Web APIs: Handles the slow stuff (timers, fetch requests, DOM events).
- Queue: When the slow stuff is done, it waits here.
- The Event Loop: This is the gatekeeper. It constantly checks: “Is the Call Stack empty?” If yes, it moves the item from the Queue to the Stack.
Demonstration:
console.log("1. Start");
// This is handed to the browser API. It does NOT pause the code here.
setTimeout(() => {
console.log("2. Timer Done");
}, 0); // Even with 0ms delay!
console.log("3. End");
Output:
1. Start
3. End
2. Timer Done
Wait, why? Because setTimeout goes to the Queue. The Event Loop waits for the main code (“3. End”) to finish before checking the Queue.
Module 14: The Old Way (Callbacks)
A Callback is simply a function you pass to another function to be executed later.
The Scenario: Logging In
Imagine we need to:
- Login user (2 seconds)
- Get their user ID.
- Get their recent posts using that ID.
// Simulating a fake server
function loginUser(email, password, callback) {
setTimeout(() => {
console.log("User logged in!");
// We pass the data back through the callback function
callback({ userEmail: email });
}, 2000);
}
function getUserPosts(email, callback) {
setTimeout(() => {
console.log("Posts retrieved!");
callback(["Post 1", "Post 2"]);
}, 1000);
}
The Problem: Callback Hell
To do these in order, we have to nest them inside each other.
console.log("Start Login...");
loginUser("owais@gmail.com", "1234", (user) => {
console.log(user.userEmail);
// Now that we are logged in, get posts
getUserPosts(user.userEmail, (posts) => {
console.log("Here are the posts: ", posts);
// If we needed to get comments on the posts, we'd nest AGAIN here...
});
});
console.log("End");
This “sideways pyramid” shape makes code hard to read and debug.
Module 15: The Modern Standard (Promises)
A Promise is an object that acts as a placeholder. It says: “I don’t have the data yet, but I promise to give you either the Data (Success) or an Error (Failure) later.”
15.1 The Lifecycle of a Promise
A Promise can be in one of three states:
- Pending: Initial state, neither fulfilled nor rejected.
- Fulfilled (Resolved): The operation completed successfully.
- Rejected: The operation failed.
15.2 Creating a Promise
Let’s rewrite our Login function using a Promise.
function loginUser(email, password) {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (email === "owais@gmail.com") {
// SUCCESS: We call resolve with the data
resolve({ userEmail: email });
} else {
// FAILURE: We call reject with an error
reject(new Error("Invalid email!"));
}
}, 2000);
});
}
15.3 Consuming a Promise (.then and .catch)
We don’t use callbacks anymore. We “chain” instructions.
loginUser("owais@gmail.com", "1234")
.then((userData) => {
console.log("Success:", userData);
// You can return ANOTHER promise here to chain them
})
.catch((error) => {
console.log("Error:", error.message);
})
.finally(() => {
console.log("This runs no matter what (good for cleaning up loading spinners)");
});
Module 16: The “Gold Standard” (Async / Await)
Promises are great, but chaining .then() can still look messy.
Async/Await (introduced in ES8) is built on top of Promises. It lets you write async code that reads like normal, top-to-bottom synchronous code.
16.1 The Syntax
async: Put this in front of a function to unlock theawaitkeyword.await: Put this in front of a Promise. It pauses the function execution until the Promise resolves.
// NOTE: We wrap this in a function because we can't use 'await' at the top level in old browsers
async function startApp() {
console.log("Process started...");
// The code effectively PAUSES here for 2 seconds
// But it does NOT freeze the browser
const user = await loginUser("owais@gmail.com", "1234");
console.log("User is:", user);
console.log("Process ended.");
}
startApp();
16.2 Handling Errors (try / catch)
In .then(), we used .catch(). In Async/Await, we use a standard try...catch block. This is how professional code looks.
async function startApp() {
try {
const user = await loginUser("wrong@gmail.com", "1234");
console.log("Logged in!");
} catch (error) {
// If the promise rejects, the code jumps here immediately
console.log("Login Failed:", error.message);
}
}
Module 17: Working with APIs (Fetch & JSON)
Now we apply this to the real world. We use the Fetch API to talk to servers.
17.1 What is JSON?
Servers don’t send JavaScript Objects; they send Text. JSON (JavaScript Object Notation) is a format that looks like a JS object but is actually a string.
JSON.stringify(obj): Converts JS Object -> JSON String (for sending).JSON.parse(json): Converts JSON String -> JS Object (for reading).
17.2 The fetch() function
fetch returns a Promise.
Example: A Real Weather Request
async function getWeather() {
try {
// 1. Send the request (Get the raw envelope)
// This is 'await' #1 because network requests take time
const response = await fetch("https://api.weatherapi.com/v1/current.json?key=YOUR_KEY&q=London");
// 2. Check if the response was successful (Status 200)
if (!response.ok) {
throw new Error("Could not find that city");
}
// 3. Parse the JSON (Open the envelope)
// This is 'await' #2 because reading the body stream takes time
const data = await response.json();
console.log(`The temp in ${data.location.name} is ${data.current.temp_c}°C`);
} catch (error) {
console.error("App Error:", error);
}
}
Project for Part 4: “Github User Finder”
Let’s build a small app that takes a GitHub username and pulls their real profile photo and bio.
HTML:
<input type="text" id="username" placeholder="Enter GitHub Username">
<button onclick="getProfile()">Search</button>
<div id="profile"></div>
JavaScript:
async function getProfile() {
const input = document.querySelector("#username");
const container = document.querySelector("#profile");
const user = input.value;
// Show loading state
container.innerHTML = "Searching...";
try {
const response = await fetch(`https://api.github.com/users/${user}`);
if (!response.ok) {
throw new Error("User not found!");
}
const data = await response.json();
// Render the HTML
container.innerHTML = `
<img src="${data.avatar_url}" width="100" style="border-radius: 50%">
<h3>${data.name}</h3>
<p>${data.bio}</p>
<p>Followers: ${data.followers}</p>
`;
} catch (err) {
container.innerHTML = `<span style="color: red">${err.message}</span>`;
}
}
Part 4 Summary
- Event Loop: JS uses a Queue and Stack to handle slow tasks without freezing.
- Callback Hell: The old, messy way of nesting functions.
- Promises: Objects that represent a future value (Pending -> Resolved/Rejected).
- Async/Await: Syntactic sugar that makes async code look cleaner and easier to read.
- Fetch: The modern tool for making HTTP requests. Always requires two awaits (one for the fetch, one for the
.json()conversion).
This concludes the “Core” JavaScript knowledge. You can now build functional web applications!