JavaScript Core
Generators
Functions that can pause and resume execution
Advanced
javascript iteration async functions
Definition
Generators are special functions that can be paused and resumed, yielding multiple values over time. They implement the Iterator protocol and provide a powerful mechanism for lazy evaluation, async flow control, and creating infinite sequences.
Basic Syntax
Generator Function Declaration
function* simpleGenerator() {
yield 1;
yield 2;
yield 3;
}
const gen = simpleGenerator();
console.log(gen.next()); // { value: 1, done: false }
console.log(gen.next()); // { value: 2, done: false }
console.log(gen.next()); // { value: 3, done: false }
console.log(gen.next()); // { value: undefined, done: true }
Key Characteristics
function* generator() {
console.log('Start');
yield 'First pause';
console.log('Resumed');
yield 'Second pause';
console.log('End');
}
const gen = generator();
// Nothing logged yet!
gen.next(); // Logs: 'Start', returns first yield
gen.next(); // Logs: 'Resumed', returns second yield
gen.next(); // Logs: 'End', returns { done: true }
The Iterator Protocol
Automatic Iteration
function* countTo(n) {
for (let i = 1; i <= n; i++) {
yield i;
}
}
// for...of loop
for (const num of countTo(5)) {
console.log(num); // 1, 2, 3, 4, 5
}
// Spread operator
const numbers = [...countTo(3)]; // [1, 2, 3]
// Destructuring
const [first, second] = countTo(5); // first=1, second=2
Manual Control
function* example() {
yield 'A';
yield 'B';
yield 'C';
}
const gen = example();
// Each next() returns IteratorResult
let result = gen.next();
while (!result.done) {
console.log(result.value);
result = gen.next();
}
Two-Way Communication
Sending Values to Generator
function* twoWay() {
const x = yield 'Send me a value';
console.log('Received:', x);
const y = yield 'Send another';
console.log('Received:', y);
return x + y;
}
const gen = twoWay();
console.log(gen.next()); // { value: 'Send me a value', done: false }
console.log(gen.next(10)); // Received: 10, { value: 'Send another', done: false }
console.log(gen.next(20)); // Received: 20, { value: 30, done: true }
Error Handling
function* errorHandling() {
try {
yield 'Step 1';
yield 'Step 2';
} catch (error) {
yield `Caught: ${error.message}`;
}
yield 'Step 3';
}
const gen = errorHandling();
gen.next(); // { value: 'Step 1', done: false }
gen.throw(new Error('Something wrong')); // Catches and continues
gen.next(); // { value: 'Step 3', done: false }
Practical Use Cases
Infinite Sequences
function* fibonacci() {
let a = 0, b = 1;
while (true) {
yield a;
[a, b] = [b, a + b];
}
}
const fib = fibonacci();
// Get first 10 fibonacci numbers
for (let i = 0; i < 10; i++) {
console.log(fib.next().value);
}
// 0, 1, 1, 2, 3, 5, 8, 13, 21, 34
Lazy Evaluation
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) return;
yield item;
count++;
}
}
// Compose operations lazily
const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
const result = take(
filter(
map(numbers, x => x * x),
x => x > 10
),
3
);
console.log([...result]); // [16, 25, 36]
// Only processes needed items, not entire array
State Machines
function* trafficLight() {
while (true) {
yield 'GREEN'; // Cars go
yield 'YELLOW'; // Cars slow
yield 'RED'; // Cars stop
}
}
const light = trafficLight();
// Simulate traffic light cycles
setInterval(() => {
console.log(light.next().value);
}, 2000);
Async Generators
Async Generator Functions
async function* fetchPages(url) {
let nextUrl = url;
while (nextUrl) {
const response = await fetch(nextUrl);
const data = await response.json();
yield data.results;
nextUrl = data.next;
}
}
// Usage
for await (const page of fetchPages('/api/items')) {
console.log('Got page:', page.length, 'items');
}
Combining with Regular Generators
function* createAsyncTasks() {
yield fetch('/api/users');
yield fetch('/api/posts');
yield fetch('/api/comments');
}
async function runTasks() {
const tasks = [];
// Collect all tasks
for (const task of createAsyncTasks()) {
tasks.push(task);
}
// Run in parallel
const results = await Promise.all(tasks);
return results;
}
Advanced Patterns
Generator Delegation
function* subGenerator() {
yield 'A';
yield 'B';
}
function* mainGenerator() {
yield 'Start';
yield* subGenerator(); // Delegate to sub-generator
yield 'C';
yield* [1, 2, 3]; // Delegate to any iterable
yield 'End';
}
const gen = mainGenerator();
console.log([...gen]);
// ['Start', 'A', 'B', 'C', 1, 2, 3, 'End']
Generator Composition
function* range(start, end) {
for (let i = start; i <= end; i++) {
yield i;
}
}
function* enumerate(iterable, start = 0) {
let index = start;
for (const item of iterable) {
yield [index++, item];
}
}
function* zip(...iterables) {
const iterators = iterables.map(i => i[Symbol.iterator]());
while (true) {
const results = iterators.map(it => it.next());
if (results.some(r => r.done)) return;
yield results.map(r => r.value);
}
}
// Usage
const numbers = range(1, 5);
const letters = ['a', 'b', 'c', 'd', 'e'];
for (const [num, letter] of zip(numbers, letters)) {
console.log(num, letter);
}
// 1 a, 2 b, 3 c, 4 d, 5 e
Coroutine Pattern
function* coroutine(generatorFn) {
const gen = generatorFn();
function handle(result) {
if (result.done) return Promise.resolve(result.value);
return Promise.resolve(result.value)
.then(res => handle(gen.next(res)))
.catch(err => handle(gen.throw(err)));
}
return handle(gen.next());
}
// Usage
const result = coroutine(function* () {
const user = yield fetchUser(1);
const posts = yield fetchPosts(user.id);
return posts;
});
result.then(posts => console.log(posts));
Real-World Applications
Redux-Saga Pattern
function* fetchUserSaga(action) {
try {
yield put({ type: 'FETCH_USER_START' });
const user = yield call(fetchUserAPI, action.payload);
yield put({ type: 'FETCH_USER_SUCCESS', payload: user });
} catch (error) {
yield put({ type: 'FETCH_USER_ERROR', payload: error.message });
}
}
// Effects are just descriptions
const put = (action) => ({ type: 'PUT', action });
const call = (fn, ...args) => ({ type: 'CALL', fn, args });
Streaming Data Processing
async function* streamProcessor(stream) {
const reader = stream.getReader();
try {
while (true) {
const { done, value } = await reader.read();
if (done) break;
// Process chunks as they arrive
yield processChunk(value);
}
} finally {
reader.releaseLock();
}
}
// Process large file without loading into memory
for await (const chunk of streamProcessor(response.body)) {
await saveToDatabase(chunk);
}
Key Takeaway
Generators provide a powerful mechanism for lazy evaluation, implementing custom iteration logic, and managing complex async flows. They are the foundation for patterns like Redux-Saga and enable efficient handling of infinite sequences and streaming data. Master the yield/next() protocol and consider async generators for modern async iteration needs.