advanced
Step 13 of 20
Async/Await and Fetch
JavaScript Programming
Async/Await and Fetch
Async/await is syntactic sugar built on top of Promises that makes asynchronous code look and behave more like synchronous code. Introduced in ES2017, async/await has become the standard way to write asynchronous JavaScript. Combined with the Fetch API — the modern replacement for XMLHttpRequest — you have a clean, powerful toolkit for making HTTP requests and handling responses. Async/await eliminates the need for .then() chains while keeping all the benefits of Promises.
Async Functions
// An async function always returns a Promise
async function greet() {
return "Hello!"; // Automatically wrapped in Promise.resolve()
}
greet().then(msg => console.log(msg)); // "Hello!"
// await pauses execution until the Promise resolves
async function fetchUser(id) {
const response = await fetch(`https://jsonplaceholder.typicode.com/users/${id}`);
const user = await response.json();
return user;
}
// Calling an async function
async function main() {
const user = await fetchUser(1);
console.log(user.name); // "Leanne Graham"
}
main();
// Error handling with try/catch
async function safeFetch(url) {
try {
const response = await fetch(url);
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
const data = await response.json();
return data;
} catch (error) {
console.error("Fetch failed:", error.message);
return null;
}
}
The Fetch API
// GET request
async function getUsers() {
const response = await fetch("https://jsonplaceholder.typicode.com/users");
const users = await response.json();
return users;
}
// POST request
async function createPost(postData) {
const response = await fetch("https://jsonplaceholder.typicode.com/posts", {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify(postData)
});
const newPost = await response.json();
return newPost;
}
// PUT request
async function updatePost(id, data) {
const response = await fetch(`https://jsonplaceholder.typicode.com/posts/${id}`, {
method: "PUT",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(data)
});
return response.json();
}
// DELETE request
async function deletePost(id) {
const response = await fetch(`https://jsonplaceholder.typicode.com/posts/${id}`, {
method: "DELETE"
});
return response.ok;
}
// Usage
const newPost = await createPost({
title: "My Post",
body: "Post content",
userId: 1
});
console.log("Created:", newPost);
Parallel vs Sequential Execution
// SEQUENTIAL — each waits for the previous one (slower)
async function sequential() {
const users = await fetch("/api/users").then(r => r.json());
const posts = await fetch("/api/posts").then(r => r.json());
const comments = await fetch("/api/comments").then(r => r.json());
return { users, posts, comments };
// Total time: users + posts + comments
}
// PARALLEL — all requests start at the same time (faster)
async function parallel() {
const [users, posts, comments] = await Promise.all([
fetch("/api/users").then(r => r.json()),
fetch("/api/posts").then(r => r.json()),
fetch("/api/comments").then(r => r.json())
]);
return { users, posts, comments };
// Total time: max(users, posts, comments)
}
// Parallel with error handling for each
async function parallelSafe() {
const results = await Promise.allSettled([
fetch("/api/users").then(r => r.json()),
fetch("/api/posts").then(r => r.json()),
fetch("/api/maybe-fails").then(r => r.json())
]);
const [users, posts, other] = results.map(r =>
r.status === "fulfilled" ? r.value : null
);
return { users, posts, other };
}
Practical Fetch Wrapper
// Reusable API client
class ApiClient {
constructor(baseURL) {
this.baseURL = baseURL;
}
async request(endpoint, options = {}) {
const url = `${this.baseURL}${endpoint}`;
const config = {
headers: { "Content-Type": "application/json" },
...options,
body: options.body ? JSON.stringify(options.body) : undefined
};
try {
const response = await fetch(url, config);
if (!response.ok) {
throw new Error(`API Error: ${response.status}`);
}
return await response.json();
} catch (error) {
console.error(`Request failed: ${error.message}`);
throw error;
}
}
get(endpoint) { return this.request(endpoint); }
post(endpoint, body) { return this.request(endpoint, { method: "POST", body }); }
put(endpoint, body) { return this.request(endpoint, { method: "PUT", body }); }
delete(endpoint) { return this.request(endpoint, { method: "DELETE" }); }
}
// Usage
const api = new ApiClient("https://jsonplaceholder.typicode.com");
const users = await api.get("/users");
const newPost = await api.post("/posts", { title: "Hello", body: "World", userId: 1 });
Pro tip: Always check response.ok after a fetch call. The Fetch API does NOT reject the promise on HTTP errors (404, 500, etc.) — it only rejects on network failures. You must manually check the status code and throw an error for non-2xx responses.
Key Takeaways
- Async functions return Promises;
awaitpauses execution until the Promise resolves. - Use
try/catchfor error handling in async functions instead of.catch()chains. - The Fetch API returns a Response object; call
.json()to parse the body (also returns a Promise). - Use
Promise.all()for parallel requests when tasks are independent — it is significantly faster than sequential awaits. - Always check
response.okbecause fetch does not throw on HTTP error status codes.