This TypeScript error occurs when a generator function is incorrectly typed, either returning a non-Generator type or having incompatible type parameters. Generator functions in TypeScript must return a Generator<T, TReturn, TNext> type, which represents both an iterator and iterable object.
Fixes TS2318
// WRONG - using Iterable or Iterator
function* countToThree(): Iterable<number> {
yield 1;
yield 2;
yield 3;
}
// CORRECT - using Generator
function* countToThree(): Generator<number> {
yield 1;
yield 2;
yield 3;
}
// With all three type parameters
function* createCounter(): Generator<number, void, number> {
let count = 0;
while (true) {
const reset = yield count++;
if (reset === 0) {
count = 0;
}
}
}// WRONG - using Iterable or Iteratorfunction* countToThree(): Iterable<number> {yield 1;yield 2;yield 3;}// CORRECT - using Generatorfunction* countToThree(): Generator<number> {yield 1;yield 2;yield 3;}// With all three type parametersfunction* createCounter(): Generator<number, void, number> {let count = 0;while (true) {const reset = yield count++;if (reset === 0) {count = 0;}}}Generator function must return Generator type
The "Generator function must return Generator type" error appears when TypeScript detects that a function declared with the `function*` syntax (a generator function) has an incorrect return type annotation. Generator functions are special functions that can be paused and resumed using the `yield` keyword, and they always return a Generator object. In TypeScript, generator functions must be typed to return a `Generator<T, TReturn, TNext>` type, where: - `T` is the type of values yielded by the generator - `TReturn` is the type of value returned when the generator completes (defaults to `any`) - `TNext` is the type of value that can be passed to `next()` (defaults to `unknown`) This error typically occurs when: 1. You explicitly annotate a generator function with the wrong return type 2. Type inference fails due to complex yield/return patterns 3. You're using older TypeScript versions with different generator type rules 4. The generator function signature doesn't match the actual implementation
Generator functions must return a Generator<T, TReturn, TNext> type. The simplest form is Generator<T>:
// WRONG - using Iterable or Iterator
function* countToThree(): Iterable<number> {
yield 1;
yield 2;
yield 3;
}
// CORRECT - using Generator
function* countToThree(): Generator<number> {
yield 1;
yield 2;
yield 3;
}
// With all three type parameters
function* createCounter(): Generator<number, void, number> {
let count = 0;
while (true) {
const reset = yield count++;
if (reset === 0) {
count = 0;
}
}
}The three type parameters are:
1. T - Type of yielded values (number in the example)
2. TReturn - Type returned when generator completes (void if no return statement)
3. TNext - Type passed to next() method (number for reset value)
TypeScript can usually infer generator return types correctly. Omit explicit annotations unless necessary:
// Let TypeScript infer - usually works fine
function* generateIds() {
let id = 0;
while (true) {
yield id++;
}
}
// TypeScript infers: Generator<number, never, unknown>
// Only annotate when inference fails or for documentation
function* parseLines(text: string): Generator<string> {
for (const line of text.split('
')) {
yield line.trim();
}
}Check the inferred type by hovering over the function name in your IDE. If TypeScript infers correctly, you don't need explicit annotations.
Async generator functions (using async function*) must return AsyncGenerator<T, TReturn, TNext>:
// WRONG - using Generator for async function
async function* fetchPages(): Generator<Promise<string>> {
for (let i = 0; i < 5; i++) {
const response = await fetch(`/api/page/${i}`);
yield response.text();
}
}
// CORRECT - using AsyncGenerator
async function* fetchPages(): AsyncGenerator<string> {
for (let i = 0; i < 5; i++) {
const response = await fetch(`/api/page/${i}`);
yield response.text();
}
}
// With explicit return type
async function* timer(seconds: number): AsyncGenerator<string, void, unknown> {
for (let i = 0; i < seconds; i++) {
await new Promise(resolve => setTimeout(resolve, 1000));
yield `Tick ${i + 1}`;
}
}Async generators yield Promises or values, and the AsyncGenerator type handles the asynchronous nature.
Generators with multiple yield types or conditional returns need proper type annotations:
// WRONG - mixed yields without proper union type
function* mixedGenerator(flag: boolean) {
if (flag) {
yield "string";
} else {
yield 42;
}
return true;
}
// CORRECT - explicit union type for yields
function* mixedGenerator(flag: boolean): Generator<string | number, boolean> {
if (flag) {
yield "string";
} else {
yield 42;
}
return true;
}
// Complex example with different next() input types
function* interactive(): Generator<string, void, string | number> {
let input: string | number;
while ((input = yield "Enter something:") !== "quit") {
console.log(`You entered: ${input}`);
}
}For generators that never return (infinite loops), use never as the return type:
function* infiniteNumbers(): Generator<number, never> {
let n = 0;
while (true) {
yield n++;
}
}Ensure your tsconfig.json supports generator syntax and has appropriate lib files:
{
"compilerOptions": {
"target": "es2015", // or higher for generator support
"lib": ["es2015", "dom"], // includes generator types
"downlevelIteration": true // if targeting ES5
}
}For older TypeScript versions, you might need:
- "target": "es6" or higher for native generator support
- "downlevelIteration": true if targeting ES5 with polyfills
- Appropriate lib files (es2015 or es2015.generator)
Check your TypeScript version:
npx tsc --versionConsider updating if you're below TypeScript 2.3, which improved generator type inference.
For reusable generator patterns, create utility types:
// Type for a generator that yields T and returns R
type SimpleGenerator<T, R = void> = Generator<T, R>;
// Type for an infinite generator
type InfiniteGenerator<T> = Generator<T, never>;
// Factory function with proper typing
function createCounter(start = 0): Generator<number> {
return (function* () {
let n = start;
while (true) {
yield n++;
}
})();
}
// Generic generator wrapper
function* mapGenerator<T, U>(
generator: Generator<T>,
mapper: (value: T) => U
): Generator<U> {
for (const value of generator) {
yield mapper(value);
}
}
// Usage
const numbers = createCounter();
const doubled = mapGenerator(numbers, n => n * 2);These patterns help maintain type safety across generator compositions.
### Generator Type Parameters Deep Dive
The Generator<T, TReturn, TNext> interface has three type parameters:
1. `T` (Yield type): Type of values yielded with yield expression
- Can be a union type for multiple yield types
- Use unknown or any for dynamic yields (not recommended)
2. `TReturn` (Return type): Type returned when generator completes
- Defaults to any if omitted
- Use void if generator has no return statement
- Use never for infinite generators
3. `TNext` (Next type): Type of value passed to next(value)
- Defaults to unknown if omitted
- Usually matches T for simple generators
- Can differ for bidirectional communication
### Generator vs Iterable vs Iterator
Understanding the differences:
- Iterator<T>: Object with next(): IteratorResult<T> method
- Iterable<T>: Object with [Symbol.iterator](): Iterator<T> method
- Generator<T, TReturn, TNext>: Both Iterator<T> and Iterable<T>, plus return() and throw() methods
All generators are iterables, but not all iterables are generators.
### TypeScript Version Differences
Generator type checking has evolved:
- TypeScript 2.3+: Improved inference for generator return types
- TypeScript 3.6+: Better support for generator type parameters
- TypeScript 4.0+: Variadic tuple types can affect generator inference
If you encounter this error in older codebases, consider updating TypeScript or adding explicit type annotations.
### AsyncGenerator and Observables
For reactive programming patterns, consider these alternatives:
// Async generators for streams
async function* webSocketStream(url: string): AsyncGenerator<MessageEvent> {
const ws = new WebSocket(url);
try {
while (ws.readyState !== WebSocket.CLOSED) {
yield await new Promise(resolve => {
ws.onmessage = resolve;
});
}
} finally {
ws.close();
}
}
// RxJS Observables (alternative pattern)
import { Observable } from 'rxjs';
function intervalObservable(ms: number): Observable<number> {
return new Observable(subscriber => {
let count = 0;
const id = setInterval(() => {
subscriber.next(count++);
}, ms);
return () => clearInterval(id);
});
}### Debugging Generator Type Errors
Use these techniques to debug complex generator type issues:
1. Check inferred types: Hover over generator function in IDE
2. Use type assertions temporarily: function* g() { ... } as any
3. Simplify complex generators: Break into smaller, typed functions
4. Test with concrete types: Replace generics with concrete types temporarily
### Performance Considerations
Generator objects have overhead:
- Each generator maintains its execution state
- TypeScript's generator downleveling for ES5 adds polyfill code
- For performance-critical code, consider alternatives like arrays or custom iterators
However, generators excel at:
- Lazy evaluation of large/infinite sequences
- Complex control flow with pause/resume
- Async data streams with backpressure