JavaScript Generators Tutorial - Section 1: Introduction
A generator is a special type of function that can pause its execution and resume later. Unlike regular functions that run to completion, generators can yield multiple values over time.
// Regular function - runs to completion
function regularFunction() {
console.log('Start');
console.log('Middle');
console.log('End');
return 'Done';
}
regularFunction(); // Logs all three, returns 'Done'
// Generator function - can pause and resume
function* generatorFunction() {
console.log('Start');
yield 1;
console.log('Middle');
yield 2;
console.log('End');
return 'Done';
}
const gen = generatorFunction();
gen.next(); // Logs 'Start', returns { value: 1, done: false }
gen.next(); // Logs 'Middle', returns { value: 2, done: false }
gen.next(); // Logs 'End', returns { value: 'Done', done: true }
Generators are defined using the function* syntax (note the asterisk):
// Function declaration
function* myGenerator() {
yield 1;
yield 2;
yield 3;
}
// Function expression
const myGen = function* () {
yield 1;
yield 2;
};
// Method in object
const obj = {
*myGenerator() {
yield 1;
yield 2;
}
};
// Class method
class MyClass {
*myGenerator() {
yield 1;
yield 2;
}
}
When you call a generator function, it doesn't execute immediately. Instead, it returns a generator object:
function* simpleGenerator() {
yield 'First';
yield 'Second';
yield 'Third';
}
const gen = simpleGenerator();
console.log(gen); // Object [Generator] {}
// Generator object has a next() method
console.log(gen.next()); // { value: 'First', done: false }
console.log(gen.next()); // { value: 'Second', done: false }
console.log(gen.next()); // { value: 'Third', done: false }
console.log(gen.next()); // { value: undefined, done: true }
The yield keyword pauses the generator and returns a value:
function* counter() {
let count = 0;
while (true) {
yield count;
count++;
}
}
const gen = counter();
console.log(gen.next().value); // 0
console.log(gen.next().value); // 1
console.log(gen.next().value); // 2
console.log(gen.next().value); // 3
Each call to next() returns an object with two properties:
value: The yielded valuedone: Boolean indicating if the generator is finishedfunction* example() {
yield 1;
yield 2;
return 3;
}
const gen = example();
console.log(gen.next()); // { value: 1, done: false }
console.log(gen.next()); // { value: 2, done: false }
console.log(gen.next()); // { value: 3, done: true }
console.log(gen.next()); // { value: undefined, done: true }
Generators implement the iterator protocol, making them compatible with for...of loops:
function* colors() {
yield 'red';
yield 'green';
yield 'blue';
}
// Using for...of
for (const color of colors()) {
console.log(color);
}
// Output:
// red
// green
// blue
// Using spread operator
const colorArray = [...colors()];
console.log(colorArray); // ['red', 'green', 'blue']
// Using Array.from()
const colorArray2 = Array.from(colors());
console.log(colorArray2); // ['red', 'green', 'blue']
// Generate values on demand
function* fibonacci() {
let [a, b] = [0, 1];
while (true) {
yield a;
[a, b] = [b, a + b];
}
}
const fib = fibonacci();
console.log(fib.next().value); // 0
console.log(fib.next().value); // 1
console.log(fib.next().value); // 1
console.log(fib.next().value); // 2
console.log(fib.next().value); // 3
// Instead of creating a large array
function createLargeArray(n) {
const arr = [];
for (let i = 0; i < n; i++) {
arr.push(i);
}
return arr; // Uses lots of memory
}
// Use a generator
function* generateNumbers(n) {
for (let i = 0; i < n; i++) {
yield i; // One value at a time
}
}
// Process one million numbers without storing them all
for (const num of generateNumbers(1000000)) {
if (num > 10) break; // Can stop early
}
class Range {
constructor(start, end) {
this.start = start;
this.end = end;
}
*[Symbol.iterator]() {
for (let i = this.start; i <= this.end; i++) {
yield i;
}
}
}
const range = new Range(1, 5);
console.log([...range]); // [1, 2, 3, 4, 5]
for (const num of range) {
console.log(num); // 1, 2, 3, 4, 5
}
function* paginate(items, pageSize) {
for (let i = 0; i < items.length; i += pageSize) {
yield items.slice(i, i + pageSize);
}
}
const data = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
const pages = paginate(data, 3);
console.log(pages.next().value); // [1, 2, 3]
console.log(pages.next().value); // [4, 5, 6]
console.log(pages.next().value); // [7, 8, 9]
console.log(pages.next().value); // [10]
function* syntaxyield to return valuesnext() methodNow that you understand what generators are, let's dive deeper into the iterator protocol!