Some tasks take time: loading product data from a server, waiting for a timer, reading a file the user picked. If JavaScript simply stopped and waited, the whole page would freeze – no scrolling, no clicking – until the task finished. Instead, JavaScript starts the task, carries on with other work, and handles the result when it arrives. This is asynchronous programming, and it is how every modern site loads data without reloading the page.
This final lesson of the track, which ends with the Fetch API, builds on functions, objects and JSON and events, so revisit those if anything below feels unfamiliar.
Synchronous vs asynchronous
Normal code is synchronous: each line finishes before the next starts. setTimeout(callback, 1000) is the simplest asynchronous example. It asks the browser to run the callback after about one second, and returns immediately. Code written after it runs first. Early JavaScript handled every async task with callbacks like this, but nesting one callback inside another inside another quickly became unreadable. Promises were created to fix that.
Promises
A Promise is an object that stands for a value you will get later. It is always in one of three states:
- pending – the task is still running;
- fulfilled – it finished successfully and has a value;
- rejected – it failed and has an error.
You react to the result with .then() for success, .catch() for failure and .finally() for code that should run either way, such as hiding a loading message. The example below creates its own Promise that resolves after one second, imitating a slow server.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Promise demo</title>
<style>
body { font-family: system-ui, sans-serif; padding: 16px; line-height: 1.5; }
</style>
</head>
<body>
<button id="load" type="button">Load today's specials</button>
<p id="status"></p>
<ul id="menu"></ul>
<script>
// Pretend server: resolves with data after 1 second
function getSpecials() {
return new Promise((resolve) => {
setTimeout(() => {
resolve(['Pongal', 'Kesari bath', 'Filter coffee']);
}, 1000);
});
}
const statusEl = document.getElementById('status');
const menu = document.getElementById('menu');
document.getElementById('load').addEventListener('click', () => {
statusEl.textContent = 'Loading…';
menu.replaceChildren(); // clear any old items
getSpecials()
.then((items) => {
for (const item of items) {
const li = document.createElement('li');
li.textContent = item;
menu.append(li);
}
statusEl.textContent = 'Loaded!';
})
.catch(() => {
statusEl.textContent = 'Something went wrong.';
});
statusEl.textContent += ' (this line ran before the data arrived)';
});
</script>
</body>
</html>async and await
Chains of .then() work, but async/await lets you write asynchronous code that reads like ordinary top-to-bottom code. Mark a function with async, and inside it put await in front of a Promise. The function pauses at that line until the Promise settles – without freezing the page – and await gives you the fulfilled value. If the Promise rejects, await throws an error, which you catch with a normal try…catch block.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>async await</title>
<style>
body { font-family: system-ui, sans-serif; padding: 16px; line-height: 1.5; }
</style>
</head>
<body>
<button id="check" type="button">Check weather</button>
<pre id="out"></pre>
<script>
const fakeWeather = { Chennai: 33, Madurai: 35, Ooty: 18 };
// Pretend API: succeeds for known cities, fails otherwise
function getTemperature(city) {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (city in fakeWeather) {
resolve(fakeWeather[city]);
} else {
reject(new Error(`No data for ${city}`));
}
}, 500);
});
}
const out = document.getElementById('out');
async function showWeather() {
out.textContent = 'Loading…';
try {
// Start all three requests at once and wait for every result
const temps = await Promise.all([
getTemperature('Chennai'),
getTemperature('Madurai'),
getTemperature('Ooty')
]);
out.textContent = `Chennai ${temps[0]}°C, Madurai ${temps[1]}°C, Ooty ${temps[2]}°C\n`;
const kodai = await getTemperature('Kodaikanal'); // this one fails
out.textContent += `Kodaikanal ${kodai}°C`;
} catch (error) {
out.textContent += `Error: ${error.message}`;
} finally {
out.textContent += '\nDone.';
}
}
document.getElementById('check').addEventListener('click', showWeather);
</script>
</body>
</html>Tip: Promise.all() runs several Promises at the same time and waits for all of them, so three half-second requests take about half a second in total, not one and a half. If any of them rejects, the whole Promise.all() rejects.
Fetching data with the Fetch API
The Fetch API is the browser’s built-in way to make HTTP requests. fetch(url) returns a Promise for a Response object. Reading the body is a second asynchronous step: response.json() returns a Promise for the parsed data. A typical function looks like this:
async function loadProducts() {
try {
const response = await fetch('https://example.com/api/products');
if (!response.ok) { // e.g. 404 or 500
throw new Error(`Server error: ${response.status}`);
}
const products = await response.json(); // parse the JSON body
console.log(products);
} catch (error) {
console.error('Could not load products:', error.message);
}
}
// Sending data: method, headers and a JSON body
async function saveEnquiry(enquiry) {
const response = await fetch('https://example.com/api/enquiries', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(enquiry)
});
return response.json();
}Watch out: fetch() only rejects when the request cannot be made at all, for example when the user is offline. An HTTP error such as 404 Not Found or 500 Server Error still counts as a completed request! Always check response.ok (true for status codes 200–299) before using the data.
Our Try it editor runs in a sandbox without internet access, so the next example fetches a data: URL – a tiny JSON document written into the address itself. The code is exactly what you would write for a real API; only the URL differs.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>fetch demo</title>
<style>
body { font-family: system-ui, sans-serif; padding: 16px; line-height: 1.5; }
</style>
</head>
<body>
<button id="get" type="button">Fetch tour list</button>
<pre id="out"></pre>
<script>
const tours = [
{ name: 'Auroville cycle tour', hours: 3 },
{ name: 'French Quarter heritage walk', hours: 2 }
];
// A data: URL containing JSON – stands in for a real API address
const url = 'data:application/json,' + encodeURIComponent(JSON.stringify(tours));
const out = document.getElementById('out');
document.getElementById('get').addEventListener('click', async () => {
out.textContent = 'Fetching…';
try {
const response = await fetch(url);
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
const data = await response.json();
out.textContent = data
.map((tour) => `${tour.name} – ${tour.hours} hours`)
.join('\n');
} catch (error) {
out.textContent = `Request failed: ${error.message}`;
}
});
</script>
</body>
</html>Things to remember with real APIs
- Show a loading state while waiting and a friendly message on failure – networks are slow and unreliable, especially on mobile.
- Browsers block requests to other domains unless that server allows it with CORS headers. If you see a CORS error, the fix is on the server, not in your
fetch()call. - Never put secret API keys in front-end JavaScript; anyone can read them in DevTools. Keep them on your server, for example in a PHP script.
- Insert data from an API with
textContentorcreateElement(), notinnerHTML, exactly as you learned in DOM Manipulation.
Congratulations – you have completed the JavaScript track. You can now store and process data, update the page, react to users and load data asynchronously with the Fetch API. A good next step is to build a small project that combines everything, such as a to-do list or a product filter, and to style it with what you learned in CSS Flexbox and CSS Grid.
Further reading
- developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch
- developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Using_promises
- developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function
- developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/all
- developer.mozilla.org/en-US/docs/Web/API/Response/ok
