Learning Goals
3 minBy the end of this lesson you will be able to:
- Send a request with
fetch()and watch it appear in the Network tab. - Read
statusandokoff theResponsethat comes back. - Get the body of that response as text with
response.text(). - Explain why the data can never be on the very next line.
Warm-Up · Where Is the Weather?
5 minLast lesson you learned that slow work is handed to the browser. Asking another computer for data is the slowest thing you have done yet.
const url = 'https://api.open-meteo.com/v1/forecast?latitude=3.14&longitude=101.69¤t=temperature_2m';
console.log('Asking...');
const answer = fetch(url);
console.log(answer);Predict together
- Does
answerhold the temperature in Kuala Lumpur? - The request has to cross the world and come back. Has it had time?
Reveal
Asking...
Promise { <pending> }No temperature anywhere. The request has barely left your laptop, so there is nothing for answer to hold yet.
What it holds instead is a promise — think of it as a tracking number for the delivery. You get that instantly. The parcel comes later. Lesson 22 is all about them.
New Concept · Ask, Then Be Told
12 minYou order food delivery. You place the order — that is the request. Later a rider arrives with a bag — that is the response.
The bag is not the food. It has a label on the outside telling you whether the order went through, and the food is sealed inside. You have to open it. A Response works exactly like that bag.
Sending a request
const url = 'https://api.open-meteo.com/v1/forecast?latitude=3.14&longitude=101.69¤t=temperature_2m';
fetch(url).then((response) => {
console.log(response.status);
console.log(response.ok);
});fetch(url) sends the request. .then(...) hands over a function to run when the response arrives.
That is the callback idea from last lesson. In fry('roti canai', done) you passed the function in; with .then() you attach it after. Same promise, tidier shape.
The request
Sent the moment you call fetch.
Names a URL to ask.
Your code does not wait for it.
The response
Arrives later, in the .then().
Carries a status, headers and a body.
The body still has to be opened.
Reading the URL you are asking
This is the same URL anatomy from Lesson 1 of Level 1, with one part doing real work now.
httpsProtocolEncrypted. Never send a request over plain http if an https version exists.
api.open-meteo.comDomainWhich computer to ask. The api part is a convention meaning “this address serves data, not pages”.
/v1/forecastPath · the endpointWhich service on that computer. One address that answers a particular question is called an endpoint.
?latitude=3.14&longitude=101.69¤t=temperature_2mQuery · your questionThe details of your request. Change the numbers and you get a different city.
What the label on the bag says
response.status— a number.200means it worked.response.ok—truefor any status from 200 to 299.404— the server answered, but has nothing at that address.500— something broke on the server's side, not yours.
One thing to know now and remember hard: a 404 is not an error as far as fetch is concerned. The delivery arrived; the bag is just empty. Your .then() still runs. Checking ok yourself is Lesson 24's subject.
Opening the bag
The body is not handed to you with the label. Reading it is a second wait, so it needs a second .then():
fetch(url)
.then((response) => response.text())
.then((body) => {
console.log(body);
});response.text() gives you the body as one long string. Read it as a chain: ask, open the bag, use what was inside.
.then() calls. The label arrives first; the contents are read second.Why it matters
Every app on your phone does this. A live score, a bus arrival time, a food order — all of it is one computer asking another and rendering what comes back.
The body you get back today is an unreadable wall of text. Making sense of it is Lesson 21.
Worked Example · The Weather in Kuala Lumpur
12 minStart in the browser console — no files needed for the first four steps.
Step 1 — Open a console on a real page
Open any page on this site and press F12, then pick the Console tab. This is the same console you debugged in during Lesson 12.
Step 2 — Send your first request
const url = 'https://api.open-meteo.com/v1/forecast?latitude=3.14&longitude=101.69¤t=temperature_2m';
fetch(url).then((response) => {
console.log(response.status);
console.log(response.ok);
});200
trueYour code just spoke to a weather service in another country. The round trip took a fraction of a second.
Step 3 — Watch it happen in the Network tab
Switch to the Network tab and run the same lines again. A new row appears.
- Name —
forecast, the endpoint you asked for. - Status —
200. - Type —
fetch, because your code asked, not the page. - Time — how long the round trip took.
Click the row and open Response. That wall of text is what is sealed in the bag. Level 1 Lesson 1 showed you this tab for pages; it works the same for data.
Step 4 — Open the bag
fetch(url)
.then((response) => response.text())
.then((body) => {
console.log(body);
});{"latitude":3.1282952,"longitude":101.68548,"generationtime_ms":0.0212,"utc_offset_seconds":0,"timezone":"GMT","timezone_abbreviation":"GMT","elevation":62.0,"current_units":{"time":"iso8601","interval":"seconds","temperature_2m":"°C"},"current":{"time":"2026-08-08T02:15","interval":900,"temperature_2m":29.8}}Yours will differ — that is live weather. Hunt for temperature_2m near the end and you have found the real temperature in KL right now.
Notice what you have: one long string. You cannot reach into it for the temperature yet. Lesson 21 fixes that.
Step 5 — Put it in a page
Make weather.html and app.js in a new folder.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>KL Weather</title>
</head>
<body>
<h1>Weather in Kuala Lumpur</h1>
<button id="load">Get the weather</button>
<p id="output">Nothing yet.</p>
<script src="app.js"></script>
</body>
</html>const url = 'https://api.open-meteo.com/v1/forecast?latitude=3.14&longitude=101.69¤t=temperature_2m';
const load = document.querySelector('#load');
const output = document.querySelector('#output');
load.addEventListener('click', () => {
output.textContent = 'Loading...';
fetch(url)
.then((response) => response.text())
.then((body) => {
output.textContent = body;
});
});Step 6 — Serve the page, do not just open it
Do not double-click weather.html. A file opened that way has no proper web address, and browsers block many requests from such pages.
In VS Code, install the Live Server extension, then right-click weather.html and choose Open with Live Server. The address bar will show http://127.0.0.1:5500 instead of a file path.
What changed? Nothing in your code — but the page now has a real address, so the browser treats its requests as normal. Use Live Server for every page from here on.
Click the button. “Loading…” appears at once, then the weather text replaces it. That instant feedback matters, because the wait is real.
Try It Yourself
13 minKeep working in weather.html and app.js.
Task 1 — Move to Penang
Change the query to
latitude=5.41andlongitude=100.33. Reload, click, and check the temperature differs from the KL one.Then log
response.statusin the first.then()as well, so you can see both.Task 2 — Ask for something that is not there
Change the path from
/v1/forecastto/v1/nasi-lemakand click the button.Write down the
statusand whatokis. Did your.then()still run? Did anything crash? This surprises most people.Task 3 — Two cities, two buttons
Add a second button so the visitor can choose Kuala Lumpur or Penang. Write one function that takes a URL and does the fetching, and call it from both listeners.
Use the functions from Lesson 8 rather than copying the fetch twice. Then fade the result in with Lesson 18's transition.
🔥 Mini-Challenge · The Weather That Never Arrives
8 minPriya's page shows [object Promise] and never changes. Find three mistakes.
// priya-weather.js — buggy
const output = document.querySelector('#output');
const url = 'https://api.open-meteo.com/v1/forecast?latitude=5.41&longitude=100.33¤t=temperature_2m';
const response = fetch(url);
output.textContent = response.text();
console.log('Weather loaded');It works if: the paragraph shows the weather text for Penang, and Weather loaded appears in the console only once that text is on screen.
Reveal the answer
Mistake 1 — fetch does not hand back the response.
It hands back the tracking number, instantly. Storing it in response and using it on the next line is the Lesson 19 trap wearing new clothes: the answer cannot possibly be there yet. You need .then().
Mistake 2 — .text() does not hand back text.
Opening the bag is a second wait, so .text() needs its own .then() too. Here it is worse than that: response is a promise, not a Response, so response.text() is not even a function. The console shows a TypeError.
Mistake 3 — “loaded” is logged before anything loads.
The last written line runs first, exactly as in last lesson's countdown. Move it inside the final .then(), where it becomes true.
// priya-weather.js — fixed
const output = document.querySelector('#output');
const url = 'https://api.open-meteo.com/v1/forecast?latitude=5.41&longitude=100.33¤t=temperature_2m';
output.textContent = 'Loading...';
fetch(url)
.then((response) => response.text())
.then((body) => {
output.textContent = body;
console.log('Weather loaded');
});[object Promise] on a page is a useful signal. It almost always means a .then() is missing somewhere.
Recap
3 minfetch(url)sends a request and returns immediately — never the data..then((response) => ...)hands over what to do when the response arrives.- A
Responseis the bag:statusandokon the label, body sealed inside. response.text()opens the body, and needs a second.then().200worked ·404nothing there ·500the server broke.- A
404does not crash your code. Checkingokis your job — Lesson 24. fetchis built into every browser. Nothing to install, nothing to import.- Serve your pages with Live Server. A double-clicked file has no real address.
New words
- Request — your code asking another computer for something.
- Response — what comes back: a status, headers, and a body.
- API — an address that serves data instead of pages.
- Endpoint — one particular address on that API.
Where this is going
You can reach real data, but only as one long string. Next lesson gives that string its name — JSON — and turns it into objects and arrays you already know how to use.
📦 Homework
4 min to brief · ~20 min to doRequired
- Build
cities.htmlwith three buttons — Kuala Lumpur, Penang and Johor Bahru. Each fetches its own weather and shows the text, with “Loading…” while it waits. - Look up the coordinates for Johor Bahru yourself. Open the Network tab, click all three, and screenshot the three rows with their statuses. Bring the file and the screenshot.
Optional stretch
- Log
response.headers.get('content-type')in your first.then(). Write down what it says — it names the format you will learn next lesson. - Turn your wifi off and click a button. What appears in the console? Note it down; Lesson 24 explains why this one does count as an error when a
404does not.