Node.js
Summary - Async patterns
Combine for await, retry and debounce to write an API client.
Summary: API client
In this lesson we combine what we learned into an API client: retry, async generator and for await together.
The client works like this: given a list of URLs, each URL is fetched with retry (resilient to transient errors). The async generator yields each successfully fetched piece in turn. With for await each page is consumed. This is the skeleton of a real API client: reliability through retry, streaming through an async generator, consumption through for await. Together they form an error-resilient and memory-efficient data stream.
Combining the pieces
The client has three parts. First retry: async function retry(fn, maxAttempts) { ... }. Second the async generator: async function* fetchPages(urls) { for (const url of urls) { const data = await retry(async function(attempt) { ... }, 3); yield data; } }.
Third consumption with for await: for await (const page of fetchPages(urls)) { console.log(page); }. Each URL is fetched with retry; if the first attempt fails the second succeeds. retry passes the attempt number to the function, so if (attempt < 2) throw ... simulates a first-try failure. The generator yields the fetched data; for await takes each piece in turn and prints it. This is the complete pattern of resilient streaming.
Exercise Write the API client
Write async function retry(fn, maxAttempts) (with try/catch). Write async function* fetchPages(urls): for each url const data = await retry(async function(attempt) { if (attempt < 2) throw new Error("Failed"); return url + ": attempt " + attempt; }, 3); yield data;. Inside async function main() write for await (const page of fetchPages(["a", "b"])) { console.log("Page: " + page); }. Call main().
Show example solution
async function retry(fn, maxAttempts) {
let lastError;
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
try {
return await fn(attempt);
} catch (e) {
lastError = e;
}
}
throw lastError;
}
async function* fetchPages(urls) {
for (const url of urls) {
const data = await retry(async function(attempt) {
if (attempt < 2) throw new Error("Failed");
return url + ": attempt " + attempt;
}, 3);
yield data;
}
}
async function main() {
const urls = ["a", "b"];
for await (const page of fetchPages(urls)) {
console.log("Page: " + page);
}
}
main();
Test yourself
-
Which pattern provides resilience to transient errors?
await retry(fn, 3)
- setTimeout
- debounce
- throttle
- retry
Show answer
retry — retry provides resilience by retrying a failed async job.
-
Match each piece to its role
- retry
- Resilience to transient errors
- async function*
- Streams data
- for await
- Consumes the streamed data
- yield
- Produces a value
Next lesson: What is Buffer?