Async & Promises
Callbacks, Promises, async/await, and fetch — handling things that take time in JavaScript.
Each .then() passes on whatever its callback returns. An arrow function with braces returns nothing unless you write return, so the next step in the chain receives undefined. It also doesn't wait: the inner async work still runs, but nothing is connected to it anymore.
const getCity = () => new Promise(r => setTimeout(() => r("Mumbai"), 50));
const getForecast = (city) => new Promise(r => setTimeout(() => r(`${city}: 28°C`), 50));
getCity()
.then(city => {
getForecast(city); // braces: no automatic return
})
.then(forecast => console.log("forecast:", forecast));
// forecast: undefinedCode that waits — and why JavaScript needs it
Imagine you're building a weather app. When the user opens it, you need to fetch the current temperature from a server. That takes time — maybe half a second. If JavaScript stopped everything and waited, the entire page would freeze. Buttons wouldn't work. Nothing would respond. That's not acceptable.
JavaScript's answer: start the fetch, keep doing other things, and when the data arrives, handle it. This is asynchronous programming.
The old way: callbacks
// A callback is a function you pass to another function, to be called "when ready"
setTimeout(function() {
console.log("This runs after 1 second");
}, 1000);
console.log("This runs immediately");
// Output:
// "This runs immediately"
// "This runs after 1 second" ← one second laterCallbacks work, but they get messy fast — "callback hell" happens when you need multiple async operations in sequence:
// Callback hell — hard to read, hard to maintain:
getUser(1, function(user) {
getPosts(user.id, function(posts) {
getComments(posts[0].id, function(comments) {
// now we can actually do something...
});
});
});Promises — a cleaner approach
A Promise is an object that represents a value that will be available in the future. It's either pending (not done yet), fulfilled (succeeded), or rejected (failed).
// A function that returns a Promise:
function getWeather(city) {
return new Promise((resolve, reject) => {
// Simulate a network request (takes 500ms):
setTimeout(() => {
if (city === "Unknown") {
reject(new Error(`City "${city}" not found`));
} else {
resolve({ city, temp: 28, condition: "Sunny" });
}
}, 500);
});
}
// Use .then() to handle success, .catch() for errors:
getWeather("Mumbai")
.then(data => {
console.log(`${data.city}: ${data.temp}°C, ${data.condition}`);
})
.catch(error => {
console.error("Error:", error.message);
});Promise chains work by returning: when a .then callback returns a Promise, the next .then waits for it and receives its result. Add the return (or drop the braces, so the arrow returns automatically). Writing the same flow with async/await, below, avoids this class of mistake entirely:
getCity()
.then(city => {
return getForecast(city); // the next .then waits for this Promise
})
.then(forecast => console.log("forecast:", forecast));
// forecast: Mumbai: 28°Casync/await — Promises made readable
async/await is syntactic sugar over Promises. It lets you write
async code that reads like synchronous code — no chains of .then().
// Mark the function as async:
async function showWeather() {
try {
// 'await' pauses this function until the Promise resolves:
const weather = await getWeather("Mumbai");
console.log(`${weather.city}: ${weather.temp}°C`);
// You can await multiple things in sequence:
const weather2 = await getWeather("Delhi");
console.log(`${weather2.city}: ${weather2.temp}°C`);
} catch (error) {
// If any await throws, execution jumps here:
console.error("Failed:", error.message);
}
}
showWeather();
// Important: the code AFTER calling an async function runs immediately:
console.log("Getting weather..."); // this prints FIRSTawait only pauses the function it's in,
not the whole program. JavaScript keeps running other code while waiting.
That's what makes it non-blocking.
fetch — the most common real-world async operation
// fetch() returns a Promise — use await with it:
async function getUser(id) {
try {
const response = await fetch(`https://jsonplaceholder.typicode.com/users/${id}`);
if (!response.ok) {
throw new Error(`HTTP error: ${response.status}`);
}
const user = await response.json(); // also a Promise!
console.log(`Name: ${user.name}`);
console.log(`Email: ${user.email}`);
} catch (error) {
console.error("Request failed:", error.message);
}
}
getUser(1);Note: fetch() works in browsers. In Node.js it's built-in from v18+. The Try It editor above runs in the browser, so fetch to external URLs works there.
Running multiple Promises in parallel
async function getMultipleUsers() {
// Sequential — slow (waits for each before starting next):
const user1 = await getWeather("Mumbai");
const user2 = await getWeather("Delhi"); // starts after Mumbai finishes
// Parallel — fast (all start at the same time):
const [mumbai, delhi] = await Promise.all([
getWeather("Mumbai"),
getWeather("Delhi")
]);
console.log(mumbai.city, delhi.city); // both arrive together
}Official Sources
- MDN Web Docs — Using Promises
- MDN Web Docs — async function
- MDN Web Docs — Fetch API
- ECMAScript 2024 — Promise Objects (§27.2)
- MDN Web Docs — Promise
The Codex links only to official documentation and the ECMAScript specification. No third-party tutorials.