Summary - Async patterns
- Node.js course
- Intermediate
- Lesson 52/116
- 5 min
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.
Practice 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().
Starter code
// Write a retry function (try/catch, maxAttempts loop)
// Yield pages in turn with an async generator
// Consume in a loop and print
// write hereSample 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();Expected output
Sayfa: a: deneme 2 Sayfa: b: deneme 2
Questions and answers
Review the lesson questions together with their answers. To test yourself, solve the lesson interactively.
1 Which pattern provides resilience to transient errors?
await retry(fn, 3)- setTimeout
- debounce
- throttle
- retry Correct answer
Explanation: retry provides resilience by retrying a failed async job.
2 Match each piece to its role
| Term | Meaning |
|---|---|
retry |
Resilience to transient errors |
async function* |
Streams data |
for await |
Consumes the streamed data |
yield |
Produces a value |