Skip to main content

Promises and Async/Await

Answer

Promises are objects representing the eventual completion or failure of an asynchronous operation. Async/await is syntactic sugar that makes working with Promises easier and more readable.

Promise States

Creating a Promise

const promise = new Promise((resolve, reject) => {
// Async operation
setTimeout(() => {
const success = true;

if (success) {
resolve("Data loaded!");
} else {
reject(new Error("Failed to load"));
}
}, 1000);
});

// Consuming the promise
promise
.then((result) => console.log(result)) // "Data loaded!"
.catch((error) => console.error(error))
.finally(() => console.log("Done"));

Promise Chaining

fetch("/api/user")
.then((response) => response.json())
.then((user) => fetch(`/api/posts/${user.id}`))
.then((response) => response.json())
.then((posts) => {
console.log("Posts:", posts);
})
.catch((error) => {
console.error("Error:", error);
});

Async/Await

// The same logic, but cleaner
async function loadUserPosts() {
try {
const userResponse = await fetch("/api/user");
const user = await userResponse.json();

const postsResponse = await fetch(`/api/posts/${user.id}`);
const posts = await postsResponse.json();

console.log("Posts:", posts);
return posts;
} catch (error) {
console.error("Error:", error);
throw error;
}
}

// Calling async function
loadUserPosts().then((posts) => console.log("Done!"));

// Or with await (inside another async function)
const posts = await loadUserPosts();

Promise Utility Methods

// Promise.all - Wait for all (fails if any fails)
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()),
]);

// Promise.allSettled - Wait for all (never rejects)
const results = await Promise.allSettled([
fetch("/api/a"),
fetch("/api/b"),
fetch("/api/c"),
]);
results.forEach((result) => {
if (result.status === "fulfilled") {
console.log("Success:", result.value);
} else {
console.log("Failed:", result.reason);
}
});

// Promise.race - First to complete wins
const first = await Promise.race([fetch("/api/primary"), fetch("/api/backup")]);

// Promise.any - First fulfilled wins (ignores rejections)
const firstSuccess = await Promise.any([
fetch("/api/server1"),
fetch("/api/server2"),
fetch("/api/server3"),
]);

Error Handling

// With Promises
fetchData().then(handleSuccess).catch(handleError); // Catches any error above

// With async/await
async function getData() {
try {
const data = await fetchData();
return handleSuccess(data);
} catch (error) {
handleError(error);
}
}

// Per-promise error handling
async function getDataSafely() {
const result = await fetchData().catch((err) => null);
if (!result) {
// Handle missing data
}
}

Common Patterns

Sequential Execution

// Each waits for previous
async function sequential() {
const user = await fetchUser();
const posts = await fetchPosts(user.id);
const comments = await fetchComments(posts[0].id);
return { user, posts, comments };
}

Parallel Execution

// All start at once
async function parallel() {
const [users, posts, comments] = await Promise.all([
fetchUsers(),
fetchPosts(),
fetchComments(),
]);
return { users, posts, comments };
}

With Timeout

function timeout(ms) {
return new Promise((_, reject) =>
setTimeout(() => reject(new Error("Timeout")), ms)
);
}

async function fetchWithTimeout(url, ms = 5000) {
return Promise.race([fetch(url), timeout(ms)]);
}

Retry Logic

async function fetchWithRetry(url, retries = 3) {
for (let i = 0; i < retries; i++) {
try {
return await fetch(url);
} catch (error) {
if (i === retries - 1) throw error;
await new Promise((r) => setTimeout(r, 1000 * (i + 1)));
}
}
}

Converting Callbacks to Promises

// Callback-based function
function readFileCallback(path, callback) {
fs.readFile(path, (err, data) => {
if (err) callback(err);
else callback(null, data);
});
}

// Promisified version
function readFilePromise(path) {
return new Promise((resolve, reject) => {
fs.readFile(path, (err, data) => {
if (err) reject(err);
else resolve(data);
});
});
}

// Or use util.promisify (Node.js)
const { promisify } = require("util");
const readFile = promisify(fs.readFile);

Comparison

FeatureCallbacksPromisesAsync/Await
ReadabilityPoorGoodExcellent
Error handlingDifficultBetterBest
ChainingNestedFlatSequential
DebuggingHardEasierEasiest

Key Points

  • Promises represent future values
  • Three states: pending, fulfilled, rejected
  • Use .then(), .catch(), .finally() for handling
  • async/await makes async code look synchronous
  • await only works inside async functions
  • Use Promise.all() for parallel operations
  • Always handle errors with try/catch or .catch()