PHASE 4 — JSON, Promises, Fetch API & Async/Await (Working with Real Data)
Section 1 — Introduction: Why This Phase Changes Everything
PHASE 4 — JSON, Promises, Fetch API & Async/Await (Working with Real Data)
Section 1 — Introduction: Why This Phase Changes Everything
Up to this point, every piece of data in your programs has been something you typed. You wrote the names, the numbers, the arrays. That’s fine for learning — but real applications don’t work that way.
When you open a weather app, it doesn’t have tomorrow’s forecast hardcoded inside it. When you scroll Instagram, those posts aren’t written into the app’s files. When you check flight prices, those numbers are coming from a live database somewhere on a server — fetched in real time, every single time.
This is the difference between static data and dynamic data.
Static data lives in your code. It never changes unless you manually edit it. Dynamic data is fetched from external sources — servers, databases, third-party services — and it changes constantly without you touching a single line of code.
Real-life analogy: Think of ordering food online. You open an app, browse a restaurant’s menu (data fetched from their server), place an order (send data to their server), and get a confirmation (receive data back). Every step involves data moving between your device and a remote server. That’s exactly what modern web apps do — and in this phase, you’ll learn how to build exactly that.
By the end of Phase 4, you’ll be able to pull real data from the internet and display it on a webpage. That skill alone will transform how you think about building applications.
Section 2 — Understanding JSON
Before you can work with API data, you need to understand the format it comes in: JSON.
What is JSON?
JSON stands for JavaScript Object Notation. It’s a lightweight, text-based format used to store and transfer data. Almost every API in the world sends data back as JSON — it’s the universal language of data exchange on the web.
It looks almost identical to a JavaScript object, but there are important differences.
JSON vs JavaScript Object
JavaScript Object:
js
let student = {
name: "Rahul",
age: 20,
isPassed: true
};
JSON (same data, JSON format):
json
{
"name": "Rahul",
"age": 20,
"isPassed": true
}
The key difference: in JSON, all keys must be wrapped in double quotes. In a JavaScript object, keys don’t need quotes. JSON also cannot contain functions — it’s purely data.
Why do APIs use JSON? Because it’s readable by humans and parseable by virtually every programming language. A Python server, a JavaScript frontend, and a mobile app can all speak JSON.
JSON.parse() — Convert JSON to JavaScript Object
When you receive JSON data from an API (it arrives as a string), you need to convert it into a real JavaScript object before you can use it.
SYNTAX:
js
let jsObject = JSON.parse(jsonString);
EXAMPLE:
js
let jsonString = '{"name": "Rahul", "age": 20}';
let student = JSON.parse(jsonString);
console.log(student.name); // "Rahul"
console.log(student.age); // 20
After JSON.parse(), You have a real JavaScript object you can work with normally.
JSON.stringify() — Convert JavaScript Object to JSON
When you want to send data to a server (for example, submitting a form), you need to convert your JavaScript object into a JSON string.
SYNTAX:
js
let jsonString = JSON.stringify(jsObject);
EXAMPLE:
js
let student = { name: "Rahul", age: 20 };
let jsonString = JSON.stringify(student);
console.log(jsonString); // '{"name":"Rahul","age":20}'
console.log(typeof jsonString); // "string"
Practice
js
// Convert object to JSON
let product = { name: "Laptop", price: 55000, inStock: true };
let productJSON = JSON.stringify(product);
console.log(productJSON); // '{"name":"Laptop","price":55000,"inStock":true}'
// Convert JSON back to object let parsedProduct = JSON.parse(productJSON); console.log(parsedProduct.price); // 55000
Lock these two methods in — you'll use them constantly.
---
**Section 3 – Introduction to APIs**
**What is an API?**
**API **stands for **Application Programming Interface**. In practical terms, it's a service running on a server that your application can talk to — you send a request, it sends data back.
Think of an API as a restaurant waiter. You (the frontend) don't go into the kitchen (the database) yourself. You tell the waiter (API) what you want. The waiter brings it back to you in a standard format.
**What is a REST API?**
A **REST API **is the most common type. It works over HTTP — the same protocol your browser uses to load websites. You make requests to specific URLs **(called endpoints)**, and the server responds with data.
**Example endpoints:**
GET https://api.example.com/users → Get all users GET https://api.example.com/users/5 → Get user with ID 5 POST https://api.example.com/users → Create a new user
HTTP Methods (Brief Overview)
MethodPurposeGETFetch/read data from serverPOSTSend new data to serverPUTUpdate existing dataDELETERemove data
In Phase 4, you’ll primarily use GET — fetching data and displaying it. That covers 80% of what frontend developers do.
Real-world API examples:
- OpenWeather API — live weather data
- JSONPlaceholder — free fake API for practice
- TMDB API — movie data
- REST Countries API — country information
Section 4 — The Fetch API
fetch() is the built-in JavaScript tool for making HTTP requests. It's how your frontend code reaches out to an API over the internet.
Basic Fetch Syntax
js
fetch("https://api.example.com/data")
.then(response => response.json())
.then(data => {
console.log(data);
})
.catch(error => {
console.log("Error:", error);
});
Breaking It Down Step by Step
Step 1 — fetch("url") Sends an HTTP GET request to the URL. This is asynchronous — it doesn't freeze your code while waiting. It immediately returns a Promise.
Step 2 — .then(response => response.json()) When the server responds, you get a Response object. Calling .json() on it reads the response body and converts it from raw text into a JavaScript object. This also returns a Promise.
Step 3 — .then(data => { ... }) Now data is your actual usable JavaScript object — the parsed API response. Do whatever you want with it here.
Step 4 — .catch(error => { ... }) If anything goes wrong (network failure, invalid URL), the error is caught here instead of crashing your app.
Working Example: Fetching a User
We’ll use JSONPlaceholder — a free public API built specifically for practice.
Display in console:
js
fetch("https://jsonplaceholder.typicode.com/users/1")
.then(response => response.json())
.then(data => {
console.log(data.name); // "Leanne Graham"
console.log(data.email); // "Sincere@april.biz"
console.log(data.address.city); // "Gwenborough"
})
.catch(error => {
console.log("Something went wrong:", error);
});
Display in HTML:
html
<div id="userCard">Loading...</div>
<script>
let card = document.getElementById("userCard");
fetch("https://jsonplaceholder.typicode.com/users/1")
.then(response => response.json())
.then(data => {
card.innerHTML = `
<h2>${data.name}</h2>
<p>Email: ${data.email}</p>
<p>City: ${data.address.city}</p>
`;
})
.catch(error => {
card.innerText = "Failed to load user data.";
});
</script>
Real API. Real data. Displayed on a real webpage. You just built something genuinely useful.
Section 5 — Promises
To fully understandfetch(), you need to understand what it returns: a Promise.
What is a Promise?
A Promise is an object that represents the eventual result of an asynchronous operation. It’s JavaScript’s way of saying: “I don’t have the answer yet, but I promise to get back to you when I do.”
Real-life analogy: You order a package online. You don’t have it yet, but you have an order confirmation — a promise that it will arrive. While waiting, you don’t stand frozen at your door. You go about your day. When it arrives, you handle it.
The Three States of a Promise
Every Promise is in one of three states:

Creating a Promise
SYNTAX:
js
let promise = new Promise((resolve, reject) => {
// async logic here
// call resolve(value) on success
// call reject(error) on failure
});
EXAMPLE:
js
let checkAge = new Promise((resolve, reject) => {
let age = 20;
if (age >= 18) {
resolve("Access granted"); // fulfilled
} else {
reject("Access denied - too young"); // rejected
}
});
checkAge
.then(message => console.log(message)) // "Access granted"
.catch(error => console.log(error));
.then() and .catch()
js
promise
.then(result => {
// runs when Promise is fulfilled
console.log("Success:", result);
})
.catch(error => {
// runs when Promise is rejected
console.log("Error:", error);
});
These chain together — .then() handles success, .catch() handles failure. You can chain multiple .then() calls for sequential operations, which is exactly what fetch() does.
Section 6 — Async/Await: The Modern Way
Promises with .then() and .catch() work well, but when you have several asynchronous steps chained together, the code can become deeply nested and harder to read. Async/Await solves that by making asynchronous code look and behave like synchronous code.
What is async?
Adding the A async keyword before a function declaration tells JavaScript: "This function will do asynchronous work and will return a Promise."
What is await?
The await keyword can only be used inside an async function. It pauses execution of that function until the Promise resolves — then returns the result. While waiting, the rest of your program keeps running normally.
Syntax
js
async function functionName() {
try {
let response = await fetch("url");
let data = await response.json();
console.log(data);
} catch(error) {
console.log("Error:", error);
}
}
Why is this cleaner?
Compare the same fetch in both styles:
With .then() (Promise chaining):
js
fetch("https://jsonplaceholder.typicode.com/posts/1")
.then(res => res.json())
.then(data => {
document.getElementById("title").innerText = data.title;
})
.catch(err => console.log(err));
With async/await:
js
async function getPost() {
try {
let res = await fetch("https://jsonplaceholder.typicode.com/posts/1");
let data = await res.json();
document.getElementById("title").innerText = data.title;
} catch(err) {
console.log(err);
}
}
getPost();
Both do the exact same thing. But the async/await version reads top-to-bottom, like regular code. No nesting, no chaining. This is what you'll use in professional codebases.
Full Working Example: Display API Data in HTML
html
<div id="postCard">Loading...</div>
<script>
async function loadPost() {
let card = document.getElementById("postCard");
try {
let response = await fetch("https://jsonplaceholder.typicode.com/posts/1");
let post = await response.json();
card.innerHTML = `
<h2>${post.title}</h2>
<p>${post.body}</p>
`;
} catch(error) {
card.innerText = "Failed to load post. Please try again.";
console.log(error);
}
}
loadPost();
</script>
Clean, readable, and handles errors properly. This is the pattern you’ll use for the rest of your career.
Section 7 — Error Handling
Errors in async code are inevitable — networks go down, endpoints change, servers crash. Handling them gracefully is what separates professional code from beginner code.
try / catch / finally
js
async function fetchData() {
try {
// Code that might fail goes here
let response = await fetch("https://jsonplaceholder.typicode.com/users");
if (!response.ok) {
throw new Error("Server returned status: " + response.status);
}
let data = await response.json();
console.log(data);
} catch(error) {
// Runs if anything in try{} fails
console.log("Caught an error:", error.message);
} finally {
// Always runs - success or failure
console.log("Fetch attempt complete.");
}
}
try— wrap code that might failcatch(error)— handle the failure gracefullyfinally— runs regardless of outcome (good for hiding loading spinners)throw— manually trigger an error (like when the response status is 404 or 500)
Showing Errors in the UI
html
<div id="content">Loading...</div>
<div id="errorMsg" style="color: red; display: none;"></div>
<script>
async function loadData() {
let content = document.getElementById("content");
let errorMsg = document.getElementById("errorMsg");
try {
let response = await fetch("https://jsonplaceholder.typicode.com/users/1");
if (!response.ok) {
throw new Error("Could not fetch user. Status: " + response.status);
}
let user = await response.json();
content.innerText = "Welcome, " + user.name;
} catch(error) {
content.innerText = "";
errorMsg.style.display = "block";
errorMsg.innerText = "Error: " + error.message;
}
}
loadData();
</script>
Users should never see a blank screen or a JavaScript error. Always catch failures and tell the user something meaningful.
Section 8 — Mini Projects
Project 1: Random User Profile Fetcher
html
<!DOCTYPE html>
<html>
<head>
<title>User Fetcher</title>
<style>
body { font-family: Arial; text-align: center; padding: 40px; }
#card { border: 1px solid #ddd; padding: 20px; max-width: 300px; margin: 20px auto; border-radius: 8px; }
button { padding: 10px 24px; font-size: 16px; cursor: pointer; }
</style>
</head>
<body>
<h2>Random User Profile</h2>
<button id="fetchBtn">Get New User</button>
<div id="card">Click the button to load a user.</div>
<script>
let card = document.getElementById("card");
let btn = document.getElementById("fetchBtn");
let userId = 1;
btn.addEventListener("click", async function() {
card.innerText = "Loading...";
try {
let response = await fetch(`https://jsonplaceholder.typicode.com/users/${userId}`);
let user = await response.json();
card.innerHTML = `
<h3>${user.name}</h3>
<p>📧 ${user.email}</p>
<p>🏙️ ${user.address.city}</p>
<p>🏢 ${user.company.name}</p>
`;
userId = userId < 10 ? userId + 1 : 1; // cycle through 10 users
} catch(error) {
card.innerText = "Failed to load user.";
}
});
</script>
</body>
</html>
Click the button multiple times and watch different users load. Real API, live data.
Project 2: Product List Fetch & Display
html
<!DOCTYPE html>
<html>
<head>
<title>Product List</title>
<style>
body { font-family: Arial; padding: 20px; }
.product { border: 1px solid #eee; padding: 16px; margin: 10px 0; border-radius: 6px; }
.product h3 { margin: 0 0 8px; }
</style>
</head>
<body>
<h2>Products</h2>
<div id="productList">Loading products...</div>
<script>
async function loadProducts() {
let container = document.getElementById("productList");
try {
let response = await fetch("https://jsonplaceholder.typicode.com/posts?_limit=5");
let products = await response.json();
container.innerHTML = "";
products.forEach(product => {
let div = document.createElement("div");
div.classList.add("product");
div.innerHTML = `
<h3>${product.title}</h3>
<p>${product.body.substring(0, 80)}...</p>
<small>ID: ${product.id}</small>
`;
container.appendChild(div);
});
} catch(error) {
container.innerText = "Could not load products.";
}
}
loadProducts();
</script>
</body>
</html>
This is the exact pattern used for product listing pages, news feeds, and dashboards.
Project 3: Weather App (Basic Version)
For this project, sign up for a free API key at openweathermap.org. The structure below shows how you’d integrate it — replace YOUR_API_KEY with your actual key.
html
<!DOCTYPE html>
<html>
<head>
<title>Weather App</title>
<style>
body { font-family: Arial; text-align: center; padding: 40px; background: #f0f8ff; }
input { padding: 10px; width: 200px; font-size: 16px; }
button { padding: 10px 20px; font-size: 16px; cursor: pointer; }
#weather { margin-top: 30px; font-size: 18px; }
</style>
</head>
<body>
<h2>🌤 Weather App</h2>
<input type="text" id="cityInput" placeholder="Enter city name">
<button id="searchBtn">Search</button>
<div id="weather"></div>
<script>
const API_KEY = "YOUR_API_KEY";
let btn = document.getElementById("searchBtn");
let weatherDiv = document.getElementById("weather");
btn.addEventListener("click", async function() {
let city = document.getElementById("cityInput").value.trim();
if (!city) return;
weatherDiv.innerText = "Fetching weather...";
try {
let response = await fetch(
`https://api.openweathermap.org/data/2.5/weather?q=${city}&appid=${API_KEY}&units=metric`
);
if (!response.ok) throw new Error("City not found");
let data = await response.json();
weatherDiv.innerHTML = `
<h3>${data.name}, ${data.sys.country}</h3>
<p>🌡️ Temperature: ${data.main.temp}°C</p>
<p>💧 Humidity: ${data.main.humidity}%</p>
<p>🌥️ Condition: ${data.weather[0].description}</p>
`;
} catch(error) {
weatherDiv.innerText = "Error: " + error.message;
}
});
</script>
</body>
</html>
To test: Get a free API key from openweathermap.org (takes 2 minutes), paste it in, enter any city name — and you’ll see live weather data instantly.
Section 9 — Common Beginner Mistakes
Mistake 1: Forgetting await
js
// Wrong — data is a Promise object, not actual data
let data = fetch("https://jsonplaceholder.typicode.com/users/1");
console.log(data); // Promise { <pending> }
// Correct
let response = await fetch("https://jsonplaceholder.typicode.com/users/1");
let data = await response.json();
console.log(data); // actual user object
Every time you see Promise { <pending> } in your console, a missing await is almost always the cause.
Mistake 2: Not handling errors
Network requests fail. WiFi cuts out. APIs go down. If you don’t have a try/catch, your app crashes silently and the user sees a broken page with no explanation.
Mistake 3: Confusing JSON and JavaScript objects
After fetch(), you call .json() to convert the response — but that result is already a JavaScript object. Don't call JSON.parse() on it again. Conversely, don't try to use a raw JSON string as an object without parsing it first.
Mistake 4: Ignoring response.ok
fetch() only throws an error for network failures. A 404 or 500 response from the server is considered a "successful" fetch. Always check response.ok:
js
if (!response.ok) {
throw new Error("HTTP error: " + response.status);
}
Section 10 — Professional Tips
1. Always handle errors — every single fetch
No exceptions. Every await fetch() should be inside a try/catch. If an error isn't caught, it becomes a silent bug that's extremely hard to track down.
2. Keep API logic in separate functions
Don’t write fetch calls inside event listeners directly. Extract them:
js
// Good — clean and reusable
async function getUser(id) {
let response = await fetch(`https://api.example.com/users/${id}`);
return await response.json();
}
btn.addEventListener("click", async () => {
let user = await getUser(1);
displayUser(user);
});
3. Show loading indicators
Users need to know something is happening. While data loads, show a spinner or a “Loading…” message. Use finally to hide it:
js
async function loadData() {
spinner.style.display = "block";
try {
// fetch
} catch(e) {
// error
} finally {
spinner.style.display = "none"; // always hides
}
}
4. Debug fetch issues with the Network tab
Open DevTools → Network tab → refresh your page. You’ll see every HTTP request made, the URL it hit, the status code (200 = success, 404 = not found, 500 = server error), and the actual response body. This is your most powerful tool for diagnosing API issues.
5. Test your API in the browser or Postman first
Before writing JavaScript, paste the API URL directly into your browser. If you see JSON data in the browser, the endpoint works. If you see an error, the problem isn’t your JS — it’s the URL or your API key.
What You’ll Learn in Phase 5 — Advanced JavaScript & Local Storage
In Phase 5, you’ll deepen your JavaScript mastery with advanced concepts that power modern applications. You’ll learn Local Storage — how to save data in the browser so it persists even after the page refreshes. You’ll explore higher-order array methods like map(), filter(), and reduce() that lets you manipulate data with elegance and precision. You'll understand closures, the event loop, and debouncing — concepts that senior developers use every day. Phase 5 is where you stop writing code that works and start writing code that's efficient, professional, and built to scale.
메타데이터
- post_id
- 736a77fd34aa
- slug
- phase-4-json-promises-fetch-api-async-await-working-with-real-data-736a77fd34aa
- url
- https://medium.com/@pranshi100verma/phase-4-json-promises-fetch-api-async-await-working-with-real-data-736a77fd34aa
- canonical_url
- https://medium.com/@pranshi100verma/phase-4-json-promises-fetch-api-async-await-working-with-real-data-736a77fd34aa
- author_url
- https://medium.com/@pranshi100verma
- status
- ok
- fetched_at
- 2026-07-13 06:23:13