Advanced JavaScript · 2 min read
Async/Await and Fetch in Depth
Promises, async/await, error handling with fetch, running requests in parallel, timeouts and cancellation.
Network calls, wallet signatures and timers all take time. JavaScript handles them with promises: objects that will settle later as either fulfilled (with a value) or rejected (with an error). async/await is syntax that lets you write promise code top to bottom.
fetch has two failure modes
js
async function getJson(url) {
const res = await fetch(url); // rejects only on network failure / CORS
if (!res.ok) { // 404 and 500 still FULFILL the promise
throw new Error("HTTP " + res.status);
}
return res.json(); // also returns a promise
}Sequential vs parallel
js
// Sequential: total time = a + b
const a = await getJson("/a");
const b = await getJson("/b");
// Parallel: total time = max(a, b)
const [a2, b2] = await Promise.all([getJson("/a"), getJson("/b")]);| Combinator | Settles when | Rejects when |
|---|---|---|
Promise.all | all fulfil | any one rejects (fail fast) |
Promise.allSettled | all settle | never; you inspect each result |
Promise.race | the first settles | the first settles with an error |
Promise.any | the first fulfils | all reject (AggregateError) |
Timeouts and cancellation
AbortController cancels a fetch. AbortSignal.timeout(ms) creates a signal that aborts on its own.
<button id="go">Fetch from 2 RPC endpoints</button>
<pre id="out">Click the button.</pre>Edit this example in the Playground.
Retrying with backoff
js
async function withRetry(fn, attempts = 3) {
for (let i = 0; i < attempts; i++) {
try { return await fn(); }
catch (e) {
if (i === attempts - 1) throw e;
await new Promise((r) => setTimeout(r, 2 ** i * 300)); // 300, 600, 1200 ms
}
}
}Summary
Check res.ok, wrap awaits in try/catch, run independent requests with Promise.all or allSettled, and always bound network calls with a timeout. This is how a dApp stays responsive when an RPC node is slow.
Saved in this browser.