Generator Functions in JavaScript
In this article, we will explain generator functions in JavaScript.
YouTube Video
Generator Functions in JavaScript
JavaScript generator functions are special functions that differ from regular functions in that they can conduct lazy execution and pause and resume. By understanding generator functions, you can efficiently handle asynchronous operations and sequential processing of large data. Here, we will provide a detailed explanation of how generator functions work, how to use them, and practical use cases.
What are Generator Functions?
A generator function is defined with function*
(a function definition with an asterisk) and, unlike regular functions, it can pause execution midway and resume while maintaining its state. Generator functions allow "lazy execution" and return results incrementally, enabling efficient memory management and sequential processing.
Syntax
1function* generatorFunction() {
2 yield 'First value';
3 yield 'Second value';
4 return 'Done';
5}
In this manner, a generator function can have multiple yield
expressions and use yield
to pause its execution. When a generator function is called, the body of the function is not executed immediately, and a generator object is returned. You can call the next()
method on this object to resume the function from the point it was paused.
Basic Usage of Generator Functions
Next, let's look at a basic example of using generator functions.
1function* simpleGenerator() {
2 yield 1;
3 yield 2;
4 yield 3;
5}
6
7const gen = simpleGenerator();
8
9console.log(gen.next()); // { value: 1, done: false }
10console.log(gen.next()); // { value: 2, done: false }
11console.log(gen.next()); // { value: 3, done: false }
12console.log(gen.next()); // { value: undefined, done: true }
A key point to note here is that the generator returns values each time with yield
, and as long as the done
property is false
, it indicates there are more values to come. The last call to next()
returns done: true
, indicating that the generator has completed.
Using the for...of
syntax
Using for...of
allows you to easily retrieve each yield
value from a generator.
1function* simpleGenerator() {
2 yield 1;
3 yield 2;
4 yield 3;
5}
6
7const gen = simpleGenerator();
8
9for (const value of gen) {
10 console.log(value); // 1, 2, 3
11}
for...of
automatically calls.next()
on iterable objects, including generators, and loops untildone
becomestrue
.- Within the loop,
value
is assigned eachyield
value in order. - Using
for...of
is very simple because you don't need to call.next()
manually.
The yield
Keyword and Pausing Values
yield
is a keyword that indicates a pause point within a generator function. The value to the right of yield
is returned when next()
is called. Additionally, yield
allows for bi-directional communication. In other words, when passing a value as an argument to the next()
method, that value is sent to the generator function.
1function* generatorWithYield() {
2 const value1 = yield 'First yield';
3 console.log('Received value:', value1);
4 const value2 = yield 'Second yield';
5 console.log('Received value:', value2);
6}
7
8const gen = generatorWithYield();
9
10console.log(gen.next()); // { value: 'First yield', done: false }
11console.log(gen.next('Apple')); // Received value: Apple
12 // { value: 'Second yield', done: false }
13console.log(gen.next('Banana'));// Received value: Banana
14 // { value: undefined, done: true }
In this example, the call next('Apple')
sends the value 'Apple'
to the generator function, where it is used inside the function.
Managing Generator State
Generators can maintain their execution state, which allows for concise representation of long loops or sequential processing. The following example demonstrates a generator that produces numbers indefinitely.
1function* infiniteGenerator() {
2 let i = 0;
3 while (true) {
4 yield i++;
5 if (i > 10) {
6 break;
7 }
8 }
9}
10
11const gen = infiniteGenerator();
12
13for (const value of gen) {
14 if (value === 3) break;
15 console.log(value); // 0, 1, 2
16}
This generator continuously produces numbers using a while(true)
loop, allowing you to obtain values as needed. This enables efficient processing of large data sets.
Applications of Generator Functions
Generator functions are suitable for executing multiple processes sequentially. For example, they are helpful in processing API requests sequentially or dividing and processing large files.
1function* apiRequestGenerator() {
2 yield fetch('https://codesparklab.com/json/example1.json');
3 yield fetch('https://codesparklab.com/json/example2.json');
4 yield fetch('https://codesparklab.com/json/example3.json');
5}
Thus, generators, which can be used for asynchronous processing, are greatly beneficial for efficient sequential data handling.
Asynchronous Generators
Asynchronous generators introduced in ES2018 allow you to return asynchronous values sequentially by combining async
and yield
. This makes it possible to concisely write asynchronous processing in combination with await
.
Syntax
1async function* asyncGenerator() {
2 yield await Promise.resolve(1);
3 yield await Promise.resolve(2);
4 yield await Promise.resolve(3);
5}
6
7const gen = asyncGenerator();
8
9(async () => {
10 for await (const value of gen) {
11 console.log(value); // 1, 2, 3
12 }
13})();
Asynchronous generators can retrieve values sequentially using a for await...of
loop. This pattern is particularly useful when handling asynchronous data streams.
Practical Example: Simplifying Asynchronous Processing with Generators
Generator functions are also used to simplify the flow of asynchronous processing. For example, by combining yield
and Promise
as shown below, you can write asynchronous operations to appear synchronous.
1function* asyncFlow() {
2 const data1 = yield fetch('https://codesparklab.com/json/example1.json');
3 console.log(data1);
4 const data2 = yield fetch('https://codesparklab.com/json/example2.json');
5 console.log(data2);
6}
7
8const gen = asyncFlow();
9
10function handleAsync(generator) {
11 const next = (promise) => {
12 promise.then((result) => {
13 const { value, done } = generator.next(result);
14 if (!done) {
15 next(value);
16 }
17 });
18 };
19
20 next(generator.next().value);
21}
22
23handleAsync(gen);
This code is sequentially making API requests using a generator and processing the results.
Summary
Generator functions are one of JavaScript's powerful features, possessing the ability to pause and resume the execution of a function. This allows for sequential processing, asynchronous processing, and efficient manipulation of large data sets. Understanding generators is an important step in mastering advanced techniques in JavaScript.
You can follow along with the above article using Visual Studio Code on our YouTube channel. Please also check out the YouTube channel.