revision:
ES6 introduced a new way of working with functions and iterators in the form of generators (or generator functions).
A generator is a function that can stop midway and then continue from where it stopped.
A generator appears to be a function but it behaves like an iterator.
In JavaScript, a generator is a function which returns an object, on which you can call next(). Every invocation of next() will return an object of shape.
Generator functions behave differently from regular ones. When such function is called, it doesn't run its code. Instead it returns a special object, called "generator object", to manage the execution.
For creating a generator function, function * syntax is used instead of just function:
function* generateSequence() { yield 1; yield 2; return 3; }
Any number of spaces can exist between the function keyword, the *, and the function name.
Since it is just a function, it can be used anywhere that a function can be used, i.e inside objects, and class methods.
Inside the function body, there is no "return". Instead, another keyword "yield" is used. It's an operator with which a generator can pause itself. Every time a generator encounters a "yield", it "returns" the value specified after it. However, in the context of generators it is said that "the generator has yielded ... ".
There can also be a return from a generator. However, "return" sets the "done" property to true, after which the generator cannot generate any more values.
The main method of a generator is next(). When called, it runs the execution until the nearest yield <value> statement (value can be omitted, then it's undefined). Then the function execution pauses, and the yielded value is returned to the outer code. The result of next() is always an object with two properties: value: the yielded value; done: true if the function code has finished, otherwise false.
see console
code: <script> function * generatorFunction() { // Line 1 console.log('This will be executed first.'); yield 'Hello, '; // Line 2 console.log('I will be printed after the pause'); yield 'World!'; } const generatorObject = generatorFunction(); // Line 3 console.log(generatorObject.next().value); // Line 4 console.log(generatorObject.next().value); // Line 5 console.log(generatorObject.next().value); // Line 6 // This will be executed first. // Hello, // I will be printed after the pause // World! // undefined function* generate() { yield 1; yield 2; return 3; } // "generator function" creates "generator object" let generator_1 = generate(); console.log(generator_1); // [object Generator] </script>
Since generators are iterables, they can help to simplify the code for implementing iterator.
Generators are iterable so you can use them with the for...of loop.
code: <div> <p class="spec" id="generator01"></p> <p class="spec" id="generator01A"></p> </div> <script> function* iterableObj() { yield 'This'; yield 'is'; yield 'iterable.' } for (const val of iterableObj()) { document.getElementById("generator01").innerHTML += val + " "; console.log(val); } let range = { from: 1, to: 5, *[Symbol.iterator]() { // a shorthand for [Symbol.iterator]: function*() for(let value = this.from; value <= this.to; value++) { yield value; } } }; document.getElementById("generator01A").innerHTML = [...range]; </script>
Generators have a more extensive array of capabilities than asynchronous functions, but are capable of replicating similar behavior. Implementing asynchronous programming in this way can increase the flexibility of the code.
Using generators, something can be created that does not use the "async/await" keywords. Instead, it will use a newly created function and "yield" values instead of await promises.
code: <div> <p class="spec" id="generator04"></p> <p class="spec" id="generator04A"></p> <p class="spec" id="generator04B"></p> <p class="spec" id="generator04C"></p> <p class="spec" id="generator04D"></p> </div> <script> function* generatorFunction() { yield 'Neo' yield 'Morpheus' yield 'Trinity' return 'The Oracle' } const generator = generatorFunction() document.getElementById("generator04").innerHTML = generator.next().value; document.getElementById("generator04A").innerHTML = generator.next().value; document.getElementById("generator04B").innerHTML = generator.next().value; document.getElementById("generator04C").innerHTML = generator.next().value; document.getElementById("generator04D").innerHTML = generator.next().value; </script>
One of the useful aspects of generators is the ability to work with infinite data streams and collections. This can be demonstrated by creating an infinite loop inside a generator function that increments a number by one.
With generators, there are no worries about creating an infinite loop, because execution can be halted and resumed at will.
However, be cautious with how to invoke the generator. If "spread" or "for...of" is used on an infinite data stream, iteration over an infinite loop will happen all at once, which will cause the environment to crash.
code: <div> <p class="spec" id="generator05"></p> <p class="spec" id="generator05A"></p> <p class="spec" id="generator05B"></p> <p class="spec" id="generator05C"></p> <p class="spec" id="generator05D"></p> <p class="spec" id="generator05E"></p> </div> <script> function* naturalNumbers() { let num = 1; while (true) { yield num; num = num + 1 } } const numbers = naturalNumbers(); console.log(numbers.next().value) console.log(numbers.next().value) // 1 // 2 document.getElementById("generator05").innerHTML = numbers.next().value; document.getElementById("generator05A").innerHTML = numbers.next().value; // 3 // 4 function* pseudoRandom(seed) { let value = seed; while(true) { value = value * 16807 % 2147483647 yield value; } }; let generator_2 = pseudoRandom(1); document.getElementById("generator05B").innerHTML = generator_2.next().value; document.getElementById("generator05C").innerHTML = generator_2.next().value; document.getElementById("generator05D").innerHTML = generator_2.next().value; document.getElementById("generator05E").innerHTML = generator_2.next().value; </script>
A generator can have its "done property" set to true and its "status" set to closed by iterating through all its values. There are two additional ways to immediately cancel a generator: with the return() method, and with the throw() method.
With return(), the generator can be terminated at any point, just as if a return statement had been in the function body. The return() method forces the generator object to complete and to ignore any other "yield" keywords.
If the body of a generator function has a way to catch and deal with errors, you can use the throw() method to throw an error into the generator.
This starts up the generator, throws the error in, and terminates the generator.
code: <div> <p class="spec" id="generator06"></p> <p class="spec" id="generator06A"></p> <p class="spec" id="generator06B"></p> <p class="spec" id="generator06C"></p> <p class="spec" id="generator06D"></p> <p class="spec" id="generator06E"></p> <p class="spec" id="generator06F"></p> </div> <script> function* genFunction() { yield 'Neo' yield 'Morpheus' yield 'Trinity' } const generator_A = genFunction() document.getElementById("generator06").innerHTML = generator_A.next().value; document.getElementById("generator06A").innerHTML = generator_A.return("There is nothing"); document.getElementById("generator06B").innerHTML = generator_A.next().value; function* generateFunction() { try { yield 'Neo' yield 'Morpheus' } catch (error) { console.log(error); } } // Invoke the generator and throw an error const generator_B = generateFunction(); document.getElementById("generator06D").innerHTML = generator_B.next().value; document.getElementById("generator06E").innerHTML = generator_B.throw(new Error('Agent Smith!')); document.getElementById("generator06F").innerHTML = generator_B.next().value; </script>