JavaScript-generatorer (function*) är speciella funktioner som kan pausas och återupptas med yield.
Generator-grunder: function* och yield
En generatorfunktion returnerar ett generatorobjekt som implementerar Iterator-protokollet.
// Generator function syntax: function* with yield
function* counter(start = 0) {
let i = start;
while (true) {
yield i++; // pauses here, returns i, resumes on next()
}
}
const gen = counter(1);
console.log(gen.next()); // { value: 1, done: false }
console.log(gen.next()); // { value: 2, done: false }
console.log(gen.next()); // { value: 3, done: false }
// Finite generator
function* range(start, end, step = 1) {
for (let i = start; i < end; i += step) {
yield i;
}
}
// for...of automatically calls .next() and stops at done: true
for (const num of range(0, 10, 2)) {
console.log(num); // 0, 2, 4, 6, 8
}
// Spread operator works with generators
const nums = [...range(1, 6)]; // [1, 2, 3, 4, 5]
// Destructuring works too
const [a, b, c] = range(10, 20); // a=10, b=11, c=12Tvåvägskommunikation: yield som uttryck
yield är inte bara return — det är ett uttryck som tar emot ett värde via next(value).
// Generators support two-way communication
// yield receives values via next(value)
function* calculator() {
let result = 0;
while (true) {
const input = yield result; // pauses and sends result; receives input
if (input === null) break;
result += input;
}
return result;
}
const calc = calculator();
calc.next(); // start: { value: 0, done: false }
calc.next(10); // add 10: { value: 10, done: false }
calc.next(5); // add 5: { value: 15, done: false }
calc.next(null); // stop: { value: 15, done: true }
// Generator as stateful iterator
function* idGenerator(prefix = 'id') {
let id = 1;
while (true) {
const reset = yield `${prefix}-${id}`;
if (reset) {
id = 1;
} else {
id++;
}
}
}
const ids = idGenerator('user');
console.log(ids.next().value); // 'user-1'
console.log(ids.next().value); // 'user-2'
console.log(ids.next(true).value); // 'user-1' (reset)
console.log(ids.next().value); // 'user-2'yield*: delegera till andra iterables
yield* delegerar till ett annat iterable.
// yield* — delegate to another iterable
function* innerGen() {
yield 'a';
yield 'b';
yield 'c';
}
function* outerGen() {
yield 1;
yield* innerGen(); // delegate: yields 'a', 'b', 'c'
yield* [4, 5, 6]; // works with any iterable
yield 7;
}
console.log([...outerGen()]); // [1, 'a', 'b', 'c', 4, 5, 6, 7]
// Practical: flatten nested arrays
function* flatten(arr) {
for (const item of arr) {
if (Array.isArray(item)) {
yield* flatten(item); // recursive delegation
} else {
yield item;
}
}
}
const nested = [1, [2, [3, 4], 5], [6, 7]];
console.log([...flatten(nested)]); // [1, 2, 3, 4, 5, 6, 7]
// Tree traversal with yield*
function* walkTree(node) {
yield node.value;
for (const child of node.children ?? []) {
yield* walkTree(child); // depth-first traversal
}
}Async-generatorer: streaming av data
Async-generatorer kombinerar generatorer med async/await för streaming.
// Async generators: async function* with yield
async function* streamLines(url) {
const response = await fetch(url);
const reader = response.body.getReader();
const decoder = new TextDecoder();
let buffer = '';
while (true) {
const { done, value } = await reader.read();
if (done) {
if (buffer) yield buffer;
break;
}
buffer += decoder.decode(value, { stream: true });
const lines = buffer.split('\n');
buffer = lines.pop() ?? '';
for (const line of lines) {
yield line; // yield each complete line
}
}
}
// Usage with for await...of
async function processCSV(url) {
let lineNumber = 0;
for await (const line of streamLines(url)) {
lineNumber++;
if (lineNumber === 1) continue; // skip header
const [name, score] = line.split(',');
console.log(`${name}: ${score}`);
}
}
// SSE (Server-Sent Events) as async generator
async function* sseStream(url) {
const response = await fetch(url);
const reader = response.body.getReader();
const decoder = new TextDecoder();
for await (const chunk of readChunks(reader)) {
const text = decoder.decode(chunk);
for (const line of text.split('\n')) {
if (line.startsWith('data: ')) {
yield JSON.parse(line.slice(6));
}
}
}
}Verkliga mönster
Generatorer möjliggör lata pipelines som bara bearbetar nödvändig data.
// Real-world: infinite scroll with generator
function* paginator(fetchPage) {
let page = 1;
let hasMore = true;
while (hasMore) {
const { items, totalPages } = yield fetchPage(page);
hasMore = page < totalPages;
page++;
}
}
// Lazy pipeline with generators
function* map(iterable, fn) {
for (const item of iterable) {
yield fn(item);
}
}
function* filter(iterable, predicate) {
for (const item of iterable) {
if (predicate(item)) yield item;
}
}
function* take(iterable, n) {
let count = 0;
for (const item of iterable) {
if (count++ >= n) break;
yield item;
}
}
// Lazy pipeline — no intermediate arrays created!
const first10EvenSquares = [
...take(
filter(
map(range(1, Infinity), x => x * x),
x => x % 2 === 0
),
10
)
];
// [4, 16, 36, 64, 100, 144, 196, 256, 324, 400]
// Observable-like: cancelable async iteration
async function* withTimeout(asyncIterable, timeoutMs) {
const timeout = setTimeout(() => {
throw new Error('Stream timed out');
}, timeoutMs);
try {
for await (const item of asyncIterable) {
yield item;
}
} finally {
clearTimeout(timeout);
}
}Generatorer vs alternativ
| Feature | Generator | async/await | Promise | Observable |
|---|---|---|---|---|
| Infinite sequences | Perfect | No | No | Yes |
| Lazy evaluation | Yes (pull-based) | No | No | Yes (push) |
| Backpressure | Natural (pull) | No | No | Yes |
| Streaming async | async function* | No | No | Yes (RxJS) |
| Two-way comms | yield expression | No | No | No |
| Browser support | ES2015+ (all) | ES2017+ | ES2015+ | Requires RxJS |
Bästa praxis
- Generatorer för lata sekvenser, async/await för engångsoperationer.
- return för tidigt avslut, try/finally för rensning.
- Async-generatorer perfekta för HTTP-streaming, SSE, WebSocket.
- Skapa återanvändbara pipeline-helpers (map, filter, take).
- TypeScript: annotera med Generator för typsäkerhet.
Vanliga frågor
Skillnad mellan generator och iterator?
En iterator är ett objekt med next(). En generator skapar och hanterar automatiskt en iterator.
Kan generatorer ersätta async/await?
För engångsoperationer nej. För streaming är async-generatorer överlägsna.
Vad är Iterator-protokollet?
Ett objekt med next() som returnerar { value, done }, och [Symbol.iterator]() för iterables.
Generatorer bra för Redux-Saga?
Ja — Redux-Saga använder dem för testbara effekter.
Hur avbryta en generator?
Anropa generator.return(value) eller använd break i for...of.