JavaScript Async/Await
async/await Basics
Syntactic sugar over Promises
// async function always returns a Promise
async function fetchHost(name) {
const response = await fetch(`/api/hosts/${name}`);
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
const data = await response.json();
return data;
}
// Arrow function variant
const fetchHost = async (name) => {
const res = await fetch(`/api/hosts/${name}`);
return res.json();
};
await pauses execution until the Promise resolves. The function returns a Promise to its caller. Rejected promises throw — use try/catch.
Error Handling
try/catch replaces .catch()
async function loadConfig(path) {
try {
const res = await fetch(path);
if (!res.ok) throw new Error(`HTTP ${res.status}`);
const config = await res.json();
return config;
} catch (err) {
console.error("Config load failed:", err.message);
return { port: 8080 }; // fallback defaults
} finally {
console.log("config loading complete");
}
}
try/catch works naturally with await. The catch block handles both network errors and HTTP errors.
Parallel Execution
await Promise.all for concurrent operations
// Sequential — slow (each waits for the previous)
const hosts = await fetchHosts();
const ports = await fetchPorts();
const vlans = await fetchVlans();
// Parallel — fast (all start immediately)
const [hosts, ports, vlans] = await Promise.all([
fetchHosts(),
fetchPorts(),
fetchVlans(),
]);
// Parallel with error tolerance
const results = await Promise.allSettled([
fetchHosts(),
fetchPorts(),
fetchVlans(),
]);
const succeeded = results
.filter(r => r.status === "fulfilled")
.map(r => r.value);
Do not await each call sequentially when they are independent. Use Promise.all to run them concurrently.
Async Iteration
for-await-of — consume async streams
async function* generatePorts(start, end) {
for (let port = start; port <= end; port++) {
await checkPort(port);
yield port;
}
}
for await (const port of generatePorts(80, 443)) {
console.log(`Port ${port} checked`);
}
// Reading a stream
const response = await fetch("/api/large-data");
const reader = response.body.getReader();
const decoder = new TextDecoder();
while (true) {
const { done, value } = await reader.read();
if (done) break;
console.log(decoder.decode(value));
}
Async generators (async function*) combine generators with async/await. for await…of consumes them.
Common Patterns
Retry with exponential backoff
async function fetchWithRetry(url, retries = 3) {
for (let i = 0; i < retries; i++) {
try {
const res = await fetch(url);
if (res.ok) return res.json();
throw new Error(`HTTP ${res.status}`);
} catch (err) {
if (i === retries - 1) throw err;
const delay = Math.pow(2, i) * 1000;
console.log(`Retry ${i + 1} in ${delay}ms`);
await new Promise(r => setTimeout(r, delay));
}
}
}
Timeout wrapper
function withTimeout(promise, ms) {
const timeout = new Promise((_, reject) =>
setTimeout(() => reject(new Error("timeout")), ms)
);
return Promise.race([promise, timeout]);
}
const data = await withTimeout(fetch("/api/slow"), 5000);
Promise.race with a timeout promise gives you a deadline for any async operation.
Top-Level Await
Available in ESM modules
// In an ES module (.mjs or type: "module" in package.json)
const config = await loadConfig();
const db = await connectDB(config.dbUrl);
export { db, config };
Top-level await blocks module loading until the promise resolves. Use it for initialization that must complete before the module is usable.