Chapter 3: Asynchronous Programming
Introduction
In JavaScript, many operations are asynchronous:
- API calls (fetch)
- File reading
- Timers (setTimeout)
- Local database access (IndexedDB)
Unlike PHP where code executes line by line, JavaScript can start an operation and continue without waiting for the result.
The Problem
// This code doesn't do what you think!
console.log("1. Start");
setTimeout(() => {
console.log("2. Inside timeout");
}, 1000);
console.log("3. End");
// Output:
// "1. Start"
// "3. End"
// "2. Inside timeout" (1 second later)
Promises
A Promise represents a value that will be available in the future.
Promise States
- pending: waiting
- fulfilled: successfully resolved (contains a value)
- rejected: rejected (contains an error)
Creating a Promise
const myPromise = new Promise((resolve, reject) => {
// Simulated asynchronous operation
setTimeout(() => {
const success = true;
if (success) {
resolve("Data received"); // Success
} else {
reject(new Error("Failure")); // Error
}
}, 1000);
});
Consuming a Promise with .then() and .catch()
myPromise
.then(result => {
console.log("Success:", result);
})
.catch(error => {
console.log("Error:", error.message);
});
// Chaining .then()
fetchUser(1)
.then(user => fetchPosts(user.id))
.then(posts => console.log(posts))
.catch(error => console.log("Error:", error));
async/await
async/await is a more readable syntax for working with Promises.
Basic Syntax
// Async function
async function fetchData() {
const response = await fetch("https://api.example.com/data");
const data = await response.json();
return data;
}
// Async arrow function
const fetchData = async () => {
const response = await fetch("https://api.example.com/data");
const data = await response.json();
return data;
};
Important Rules
awaitcan only be used inside anasyncfunction- An
asyncfunction always returns a Promise await"pauses" execution until the Promise is resolved
Comparative Example
// With .then()
function getUserPosts(userId) {
return fetchUser(userId)
.then(user => fetchPosts(user.id))
.then(posts => {
console.log(posts);
return posts;
});
}
// With async/await (more readable)
async function getUserPosts(userId) {
const user = await fetchUser(userId);
const posts = await fetchPosts(user.id);
console.log(posts);
return posts;
}
Error Handling
With try/catch
async function fetchData() {
try {
const response = await fetch("https://api.example.com/data");
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
const data = await response.json();
return data;
} catch (error) {
console.error("Error:", error.message);
// Handle error (display message, return default value, etc.)
return null;
}
}
Common Pattern in React
const [data, setData] = useState(null);
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
const loadData = async () => {
setLoading(true);
setError(null);
try {
const response = await fetch("/api/items");
const result = await response.json();
setData(result);
} catch (err) {
setError(err.message);
} finally {
setLoading(false);
}
};
Sequential vs Parallel Execution
Sequential (one after another)
async function sequential() {
const user = await fetchUser(1); // Wait...
const posts = await fetchPosts(1); // Then wait...
const comments = await fetchComments(1); // Then wait...
// Total time = time1 + time2 + time3
}
Parallel (at the same time)
async function parallel() {
// Start all 3 requests at the same time
const [user, posts, comments] = await Promise.all([
fetchUser(1),
fetchPosts(1),
fetchComments(1)
]);
// Total time = max(time1, time2, time3)
}
Promise.all vs Promise.allSettled
// Promise.all - fails if ONE promise fails
try {
const results = await Promise.all([promise1, promise2, promise3]);
} catch (error) {
// One of the promises failed
}
// Promise.allSettled - waits for all, even if some fail
const results = await Promise.allSettled([promise1, promise2, promise3]);
// results = [
// { status: "fulfilled", value: ... },
// { status: "rejected", reason: Error },
// { status: "fulfilled", value: ... }
// ]
Comparison with PHP
// PHP - synchronous by default
$user = file_get_contents("https://api.example.com/user/1");
$posts = file_get_contents("https://api.example.com/posts/1");
// Each line waits for the previous one to finish
// JavaScript - asynchronous
const user = await fetch("https://api.example.com/user/1");
const posts = await fetch("https://api.example.com/posts/1");
// Similar thanks to await, but under the hood it's asynchronous
Common Pitfalls
1. Forgetting await
// INCORRECT
async function getData() {
const data = fetch("/api/data"); // Forgot await!
console.log(data); // Promise { <pending> } - not the data!
}
// CORRECT
async function getData() {
const response = await fetch("/api/data");
const data = await response.json();
console.log(data);
}
2. await in a loop (unintended sequential)
// SLOW - each iteration waits for the previous one
async function slow() {
for (const id of ids) {
const data = await fetchItem(id); // Sequential!
}
}
// FAST - all requests in parallel
async function fast() {
const promises = ids.map(id => fetchItem(id));
const results = await Promise.all(promises);
}
3. Not handling errors
// DANGEROUS - unhandled error
async function riskyCode() {
const data = await fetchData(); // What if it fails?
}
// SAFE - error handled
async function safeCode() {
try {
const data = await fetchData();
} catch (error) {
console.error("Handled error:", error);
}
}
Exercises
Exercise 1: Convert to async/await
Convert this code using .then():
function getUser(id) {
return fetch(`/api/users/${id}`)
.then(response => response.json())
.then(user => {
console.log(user);
return user;
})
.catch(error => {
console.error("Error:", error);
return null;
});
}
Solution:
async function getUser(id) {
try {
const response = await fetch(`/api/users/${id}`);
const user = await response.json();
console.log(user);
return user;
} catch (error) {
console.error("Error:", error);
return null;
}
}
Exercise 2: Parallelization
Optimize this code to execute requests in parallel:
async function loadDashboard(userId) {
const user = await fetchUser(userId);
const orders = await fetchOrders(userId);
const notifications = await fetchNotifications(userId);
return { user, orders, notifications };
}
Solution:
async function loadDashboard(userId) {
const [user, orders, notifications] = await Promise.all([
fetchUser(userId),
fetchOrders(userId),
fetchNotifications(userId)
]);
return { user, orders, notifications };
}
Key Points to Remember
- Promise: represents a future value (pending -> fulfilled/rejected)
- async/await: readable syntax for Promises
- await: "pauses" execution until resolution
- try/catch: error handling with async/await
- Promise.all: execute multiple Promises in parallel
- Always handle errors with try/catch
<- Previous Chapter | Back to Module | Next Chapter: ES6 Modules ->