Introduction
ES6 brought a lot of useful features to JavaScript, and generator functions are one of the more underused ones. They change how a function executes in a way nothing else in the language does, and they’re worth knowing well.
What are generator functions?
Generator functions are a special type of function in JavaScript that allows the execution to pause and resume later. Unlike traditional functions that run to completion in a single execution, generators yield one value at a time, making them ideal for scenarios where data is large or expensive to compute.
The syntax for a generator function involves the function* keyword:
function* generatorFunction() {
// function body
}
The asterisk after the function keyword might seem unusual, but it denotes that this function is a generator.
Yielding values
The yield keyword, exclusive to generator functions, is used to pause and resume execution. Each time a value is yielded, the function’s context (variable bindings) is saved and can be resumed later on.
function* generatorFunction() {
yield "first";
yield "second";
yield "third";
}
const generator = generatorFunction();
console.log(generator.next()); // { value: 'first', done: false }
console.log(generator.next()); // { value: 'second', done: false }
console.log(generator.next()); // { value: 'third', done: false }
console.log(generator.next()); // { value: undefined, done: true }
In this example, each call to generator.next() resumes the function and returns the next value until there are no more yield statements, at which point done: true is returned.
Generators and asynchronous operations
One of the most compelling use cases for generators is handling asynchronous operations. Traditionally, you would use callbacks, promises, or async/await syntax to handle asynchronous operations in JavaScript. However, these methods can sometimes lead to complex and hard-to-maintain code.
function* generatorFunction() {
const data = yield fetchData(); // fetch data is a function returning a promise
console.log(data);
}
const generator = generatorFunction();
const promise = generator.next().value;
promise.then(data => generator.next(data));
In this example, we first call generator.next(), which starts the generator function and pauses at the yield statement, where fetchData() is called. The fetchData() function returns a promise, which we then resolve using promise.then(). The resolved data is then passed back to the generator using generator.next(data).
Conclusion
Generator functions give you a way to pause and resume execution of a function body, which is handy for asynchronous operations and large data sets alike. Like any tool, they shine when used in the right context. Next time you find yourself dealing with complex asynchronous code or large data streams, give generator functions a shot!