TypeScript
Completed
-
Section 1: Getting Started
-
Section 2: Basic Types
-
Section 3: Functions and Objects
-
Section 4: Advanced Types
-
Section 5: Object-Oriented TypeScript
-
Section 6: Working with Modules
-
Section 7: Tooling and Practice
-
Section 8: Type-Level Programming
-
Section 9: TypeScript with Backends
-
Section 10: Testing Typed Code
-
Section 11: Data Validation with Types
-
Section 12: Practical Projects
-
Section 13: Compiler Internals
-
Section 14: Configuration Deep Dive
-
Section 15: Enums, Symbols, and Special Types
-
Section 16: Working with Async Code
-
Section 17: TypeScript and the DOM
-
Section 18: Advanced Generics Practice
-
Section 19: Working with Third-Party Types
-
Section 20: Monorepo and Large-Scale Practices
-
Section 21: Common Pitfalls and Best Practices
-
Section 22: Interview Practice
-
Section 23: Handbook: Narrowing In Depth
-
Section 24: Handbook: Object Types In Depth
-
Section 25: Handbook: Classes In Depth
-
Section 26: Handbook: Modules In Depth
-
Section 27: Handbook: Declaration Files In Depth
-
Section 28: JSX and Namespaces
-
Section 29: Compiler Configuration Reference
-
Section 30: More Practice Exercises
-
Section 31: Handbook: Everyday Types Deep Dive
-
Section 32: Utility Types Full Reference
-
Section 33: Decorators Reference
-
Section 34: Mixins and Advanced OOP Patterns
-
Section 35: Iterators and Generators Typing
-
Section 36: More Type-Level Programming Practice
-
Section 37: TypeScript Ecosystem Tools
-
Section 38: TypeScript with Testing Frameworks
-
Section 39: TypeScript for Library Authors
-
Section 40: More Interview Practice
-
Section 41: Handbook: Functions In Depth
-
Section 42: Handbook: Type Manipulation Deep Dive
-
Section 43: More Real-World Patterns
-
Section 44: TypeScript Release Notes Highlights
-
Section 45: Final Practice and Review
101: Typing Async Generators
I’ve seen this a lot when developers start moving from standard async/await patterns to streams. You want to paginate through a large dataset—say, a list of users from an API—and you think, "An async generator is perfect for this." But then you hit a wall with the types, and suddenly your beautifully typed codebase is leaking any everywhere.
// The "I'll just let TypeScript figure it out" approach
async function* fetchUsers(url: string) {
let page = 1;
while (page < 3) {
const response = await fetch(`${url}?page=${page}`);
const data = await response.json();
for (const user of data.users) {
yield user; // What is 'user' actually?
}
page++;
}
return "Finished fetching all users";
}
async function run() {
for await (const user of fetchUsers("https://api.example.com/users")) {
console.log(user.name); // Error: Property 'name' does not exist on type 'unknown'
}
}
The "Unknown" Leak in your API Stream
If you look at the code above, it feels correct. It runs. But TypeScript is grumpy. Because we didn't explicitly type the generator, it's inferring the yield type as unknown (or any in older configurations). When you try to access user.name, the compiler stops you because it has no idea what the API is actually returning.
You might be tempted to just cast the result inside the for await loop, but that's a band-aid. The logic for what is being streamed belongs inside the generator itself. If you change your API response structure later, you want the error to trigger at the source, not at every single place you consumed the stream.
Taming the AsyncGenerator Generic
To fix this, we need to use the AsyncGenerator type. Now, this is where it gets a bit intimidating because it takes three different generic arguments: AsyncGenerator<T, TReturn, TNext>.
- T: The type of values the generator
yields. This is the one you'll use 90% of the time. - TReturn: The type the generator
returnswhen it's completely finished (the value in the finalreturnstatement). - TNext: The type of value the generator expects to receive if you call
.next(value)manually. Most of us just usevoidhere because we rarely "push" values back into a generator.
Here is how we rewrite that user fetcher to be type-safe:
interface User {
id: string;
name: string;
}
// We tell TS: Yields User, Returns string, Accepts nothing via .next()
async function* fetchUsers(url: string): AsyncGenerator<User, string, void> {
let page = 1;
while (page < 3) {
const response = await fetch(`${url}?page=${page}`);
const data = await response.json();
for (const user of data.users) {
yield user;
}
page++;
}
return "Finished fetching all users";
}
async function run() {
const generator = fetchUsers("https://api.example.com/users");
// Now 'user' is correctly typed as User
for await (const user of generator) {
console.log(user.name);
}
}
Why the Return Type Matters
You might notice that the for await...of loop actually ignores the return value ("Finished fetching all users"). This is a common point of confusion. The loop only cares about what is yielded.
If you actually need that return value, you can't use a for await loop. You have to manually call .next() until the done property is true. I'll be honest: it's rare to need the return value in a generator, but when you're building complex state machines or data pipelines, that final summary value is a lifesaver. By typing it as AsyncGenerator<User, string, void>, you ensure that whoever calls the generator knows exactly what they're getting at the end of the line.
📋 Practical Task
Build a Type-Safe Log File Processor
You are building a system that reads large log files line-by-line to avoid loading the whole file into memory. Your goal is to create an async generator that processes these logs.
Requirements:
- Create an interface
LogEntrywith propertiestimestamp: stringandmessage: string. - Implement an async generator function called
processLogs. - The generator should
yieldLogEntryobjects. - The generator should
returna number representing the total count of lines processed. - Use the
AsyncGeneratortype explicitly in the function signature. - Write a small
mainfunction that consumes the generator and logs themessageof each entry.
Hint: Since you aren't actually reading a file, you can simulate the "async" part by using a simple array of strings and a await new Promise(r => setTimeout(r, 10)) inside your loop.
There are no comments for now.