JavaScript Generators Tutorial - Section 3: Advanced Patterns
function* readCSV(text) {
const lines = text.split('\n');
for (const line of lines) {
yield line.split(',');
}
}
function* parseRecords(rows) {
const headers = rows.next().value;
for (const row of rows) {
const record = {};
headers.forEach((header, i) => {
record[header] = row[i];
});
yield record;
}
}
function* filterRecords(records, predicate) {
for (const record of records) {
if (predicate(record)) {
yield record;
}
}
}
// Usage
const csvData = 'name,age,city\nAlice,30,NYC\nBob,25,LA\nCharlie,35,NYC';
const rows = readCSV(csvData);
const records = parseRecords(rows);
const nycResidents = filterRecords(records, r => r.city === 'NYC');
for (const person of nycResidents) {
console.log(person); // { name: 'Alice', age: '30', city: 'NYC' }, ...
}
class LazyCollection {
constructor(data) {
this.data = data;
}
*[Symbol.iterator]() {
for (const item of this.data) {
yield item;
}
}
*map(fn) {
for (const item of this) {
yield fn(item);
}
}
*filter(predicate) {
for (const item of this) {
if (predicate(item)) {
yield item;
}
}
}
*take(n) {
let count = 0;
for (const item of this) {
if (count++ >= n) break;
yield item;
}
}
toArray() {
return [...this];
}
}
// Usage - processes only what's needed
const numbers = new LazyCollection([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]);
const result = numbers
.filter(n => n % 2 === 0)
.map(n => n * n)
.take(3)
.toArray();
console.log(result); // [4, 16, 36]
class IDGenerator {
constructor(prefix = 'ID', start = 1) {
this.prefix = prefix;
this.current = start;
}
*[Symbol.iterator]() {
while (true) {
yield `${this.prefix}-${String(this.current++).padStart(6, '0')}`;
}
}
next() {
return this[Symbol.iterator]().next().value;
}
}
const userIDs = new IDGenerator('USER', 1000);
console.log(userIDs.next()); // USER-001000
console.log(userIDs.next()); // USER-001001
console.log(userIDs.next()); // USER-001002
class TreeNode {
constructor(value, children = []) {
this.value = value;
this.children = children;
}
// Depth-first traversal
*depthFirst() {
yield this.value;
for (const child of this.children) {
yield* child.depthFirst();
}
}
// Breadth-first traversal
*breadthFirst() {
const queue = [this];
while (queue.length > 0) {
const node = queue.shift();
yield node.value;
queue.push(...node.children);
}
}
}
const tree = new TreeNode(1, [
new TreeNode(2, [
new TreeNode(4),
new TreeNode(5)
]),
new TreeNode(3, [
new TreeNode(6),
new TreeNode(7)
])
]);
console.log([...tree.depthFirst()]); // [1, 2, 4, 5, 3, 6, 7]
console.log([...tree.breadthFirst()]); // [1, 2, 3, 4, 5, 6, 7]
function* batch(iterable, size) {
let batch = [];
for (const item of iterable) {
batch.push(item);
if (batch.length === size) {
yield batch;
batch = [];
}
}
if (batch.length > 0) {
yield batch;
}
}
const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
for (const group of batch(numbers, 3)) {
console.log(group);
}
// [1, 2, 3]
// [4, 5, 6]
// [7, 8, 9]
// [10]
function* permutations(array) {
if (array.length <= 1) {
yield array;
return;
}
for (let i = 0; i < array.length; i++) {
const rest = [...array.slice(0, i), ...array.slice(i + 1)];
for (const perm of permutations(rest)) {
yield [array[i], ...perm];
}
}
}
console.log([...permutations([1, 2, 3])]);
// [[1,2,3], [1,3,2], [2,1,3], [2,3,1], [3,1,2], [3,2,1]]
function* retryBackoff(maxRetries = 3, baseDelay = 1000) {
for (let i = 0; i < maxRetries; i++) {
const delay = baseDelay * Math.pow(2, i);
yield { attempt: i + 1, delay };
}
}
async function fetchWithRetry(url) {
for (const { attempt, delay } of retryBackoff(3, 1000)) {
try {
console.log(`Attempt ${attempt}`);
const response = await fetch(url);
return await response.json();
} catch (error) {
console.log(`Failed, waiting ${delay}ms`);
await new Promise(resolve => setTimeout(resolve, delay));
}
}
throw new Error('All retries failed');
}
function* slidingWindow(iterable, size) {
const window = [];
for (const item of iterable) {
window.push(item);
if (window.length === size) {
yield [...window];
window.shift();
}
}
}
const numbers = [1, 2, 3, 4, 5];
for (const window of slidingWindow(numbers, 3)) {
console.log(window);
}
// [1, 2, 3]
// [2, 3, 4]
// [3, 4, 5]
function* unique(iterable) {
const seen = new Set();
for (const item of iterable) {
if (!seen.has(item)) {
seen.add(item);
yield item;
}
}
}
const numbers = [1, 2, 2, 3, 3, 3, 4, 4, 4, 4, 5];
console.log([...unique(numbers)]); // [1, 2, 3, 4, 5]
function* mergeSorted(...iterables) {
const iterators = iterables.map(it => it[Symbol.iterator]());
const values = iterators.map(it => it.next());
while (values.some(v => !v.done)) {
let minIndex = -1;
let minValue = Infinity;
values.forEach((v, i) => {
if (!v.done && v.value < minValue) {
minValue = v.value;
minIndex = i;
}
});
if (minIndex !== -1) {
yield minValue;
values[minIndex] = iterators[minIndex].next();
}
}
}
const seq1 = [1, 3, 5, 7];
const seq2 = [2, 4, 6, 8];
const seq3 = [0, 9, 10];
console.log([...mergeSorted(seq1, seq2, seq3)]);
// [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
Finally, let's explore performance optimization techniques with generators!