Generators
Generators, Advanced Iteration: Generators
What is a generator in JavaScript?
View Answer:
// Full Implementation
function* generateSequence() {
yield 1;
yield 2;
yield 3;
}
// "generator function" creates "generator object"
let generator = generateSequence();
console.log(generator); // creates: [object Generator]
How do you define a generator function?
View Answer:
// Full Implementation
function* generateSequence() {
yield 1;
yield 2;
yield 3;
}
// "generator function" creates "generator object"
let generator = generateSequence();
console.log(generator); // creates: [object Generator]
What is the distinction between a generator and a regular function?
View Answer:
// Full Implementation
function* generateSequence() {
yield 1;
yield 2;
yield 3;
}
// "generator function" creates "generator object"
let generator = generateSequence();
console.log(generator); // creates: [object Generator]
Can you explain the function of the generator “next” method in JavaScript?
View Answer:
Syntax: generator.next(value);
function* gen() {
yield 1;
yield 2;
yield 3;
}
const g = gen(); // "Generator { }"
g.next(); // "Object { value: 1, done: false }"
g.next(); // "Object { value: 2, done: false }"
g.next(); // "Object { value: 3, done: false }"
g.next(); // "Object { value: undefined, done: true }"
What generator function creation syntax is acceptable in JavaScript?
View Answer:
// Pre-generator function
function* gen() {
yield 1;
yield 2;
yield 3;
}
// Post-generator function
// function *gen() {
// yield 1;
// yield 2;
// yield 3;
// }
What differentiates a generator from an iterator?
View Answer:
// Iterator
function makeRangeIterator(start = 0, end = Infinity, step = 1) {
let nextIndex = start;
let iterationCount = 0;
const rangeIterator = {
next: function () {
let result;
if (nextIndex < end) {
result = { value: nextIndex, done: false };
nextIndex += step;
iterationCount++;
return result;
}
return { value: iterationCount, done: true };
},
};
return rangeIterator;
}
const it = makeRangeIterator(1, 10, 2);
let result = it.next();
while (!result.done) {
console.log(result.value); // 1 3 5 7 9
result = it.next();
}
console.log('Iterated over sequence of size: ', result.value);
// [5 numbers returned, that took interval in between: 0 to 10]
////////// GENERATOR EXAMPLE ////////////////
function* makeRangeIterator(start = 0, end = 100, step = 1) {
let iterationCount = 0;
for (let i = start; i < end; i += step) {
iterationCount++;
yield i;
}
return iterationCount;
}
let generator = makeRangeIterator(1, 10, 2);
for (let num of generator) {
console.log(num); // 1 3 5 7 9
}
If you attempt to use the return keyword to return a value at the end of a generator. What happens when you iterate over the values with a for…of loop?
View Answer:
function* generateSequence() {
yield 1;
yield 2;
return 3; // { value: 3, done: true} does not yield
}
let generator = generateSequence();
for (let value of generator) {
console.log(value); // 1, then 2, no 3
}
Are function generators iterable in JavaScript?
View Answer:
function* makeIterator() {
yield 1;
yield 2;
}
const it = makeIterator();
for (const itItem of it) {
console.log(itItem);
}
console.log(it[Symbol.iterator]() === it); // true;
// This example show us generator(iterator) is iterable object,
// which has the @@iterator method return the it (itself),
// and consequently, the it object can iterate only _once_.
// If we change it's @@iterator method to a function/generator
// which returns a new iterator/generator object, (it)
// can iterate many times
it[Symbol.iterator] = function* () {
yield 2;
yield 1;
};
What is the purpose of the "yield" keyword in generators?
View Answer:
Can you explain the power of the yield keyword in JavaScript generator functions?
View Answer:
function* gen() {
// Pass a question to the outer code and wait for an answer
let result = yield '2 + 2 = ?'; // (*)
console.log(result);
}
let generator = gen();
let question = generator.next().value; // <-- yield returns the value
generator.next(4); // --> pass the result into the generator
What is the main difference between a regular function and a generator function?
View Answer:
How do you call a generator function?
View Answer:
What is a generator object?
View Answer:
function* idGenerator() {
let id = 1;
while (true) {
yield id++;
}
}
const gen = idGenerator();
console.log(gen.next().value); // 1
console.log(gen.next().value); // 2
console.log(gen.next().value); // 3
console.log(typeof idGenerator); // function
What is the "next" method in generator objects?
View Answer:
Can generators be used with "for...of" loops?
View Answer:
function* idGenerator() {
let id = 1;
while (id <= 5) {
yield id++;
}
}
for (let value of idGenerator()) {
console.log(value); // Logs 1, 2, 3, 4, 5
}
What is generator delegation and how is it useful?
View Answer:
function* gen1() {
yield 1;
yield 2;
}
function* gen2() {
yield* gen1();
yield 3;
}
let generator = gen2();
console.log(generator.next().value); // 1
console.log(generator.next().value); // 2
console.log(generator.next().value); // 3
In this example, gen2
delegates to gen1
using yield*
. So when we start iterating over gen2
, the first two values come from gen1
, and the third value comes from gen2
itself.
Can you use "return" in a generator function?
View Answer:
function* numberGenerator() {
yield 1;
yield 2;
return 3;
}
const gen = numberGenerator();
console.log(gen.next()); // { value: 1, done: false }
console.log(gen.next()); // { value: 2, done: false }
console.log(gen.next()); // { value: 3, done: true }
In this example, after yielding 1 and 2, the generator function returns 3 and ends its execution.
How can you catch errors in a generator function?
View Answer:
1. Using a try/catch block inside the generator function:
function* generatorFunction() {
try {
yield "Start";
throw new Error("Error occurred");
} catch (error) {
console.log(error.message); // Logs "Error occurred"
}
yield "End";
}
const gen = generatorFunction();
console.log(gen.next()); // { value: "Start", done: false }
console.log(gen.next()); // Logs "Error occurred", then { value: "End", done: false }
2. Using the generator's throw()
method:
function* generatorFunction() {
try {
yield "Start";
} catch (error) {
console.log(error.message); // Logs "Error thrown into generator"
yield "Caught";
}
yield "End";
}
const gen = generatorFunction();
console.log(gen.next()); // { value: "Start", done: false }
console.log(gen.throw(new Error("Error thrown into generator"))); // Logs "Error thrown into generator", then { value: "Caught", done: false }
console.log(gen.next()); // { value: "End", done: false }
In the first example, the error is thrown and caught within the generator. In the second, the error is thrown from outside the generator and caught within it.
Can generators be used with async/await?
View Answer:
Here's an example using async iterators (for-await-of) to handle asynchronous operations in a generator.
async function* asyncGenerator() {
const promises = [Promise.resolve(1), Promise.resolve(2), Promise.resolve(3)];
for (const promise of promises) {
yield promise;
}
}
(async function() {
for await (const value of asyncGenerator()) {
console.log(value); // Logs 1, 2, 3
}
})();
In this example, asyncGenerator
is an asynchronous generator that yields promises. The for await...of
loop then waits for each of these promises to resolve before logging the resolved value. This allows you to handle asynchronous operations within a generator in a linear, easy-to-understand way.
Can you create an infinite sequence with generators?
View Answer:
Here's a simple example of an infinite sequence...
function* infiniteSequence() {
let i = 0;
while (true) {
yield i++;
}
}
const gen = infiniteSequence();
console.log(gen.next().value); // 0
console.log(gen.next().value); // 1
console.log(gen.next().value); // 2
// This can go on indefinitely
In this example, the infiniteSequence
generator will yield an infinite sequence of numbers. Because generators only calculate their yielded value when .next()
is called, this won't cause any performance issues as long as you don't try to iterate over the entire sequence at once (which would be impossible, as it's infinite).
How can you use generators for lazy evaluation?
View Answer:
Here's an example of using a generator for lazy evaluation...
function* fibonacci() {
let [prev, curr] = [0, 1];
while (true) {
yield curr;
[prev, curr] = [curr, prev + curr];
}
}
const gen = fibonacci();
console.log(gen.next().value); // 1
console.log(gen.next().value); // 1
console.log(gen.next().value); // 2
console.log(gen.next().value); // 3
console.log(gen.next().value); // 5
// And so on...
In this example, the fibonacci
generator represents the infinite sequence of Fibonacci numbers. But even though this sequence is infinite, the generator only uses a constant amount of memory because it only computes the next Fibonacci number when .next()
is called. This is the essence of lazy evaluation: only computing values as they are needed.
What is the difference between "yield" and "yield*"?
View Answer:
function* generatorA() {
yield 1;
yield 2;
}
function* generatorB() {
yield* generatorA();
yield 3;
}
const gen = generatorB();
console.log(gen.next().value); // 1
console.log(gen.next().value); // 2
console.log(gen.next().value); // 3
In the above code, yield*
in generatorB
is delegating to generatorA
.
How can you use generators to implement coroutines?
View Answer:
function* coroutine() {
console.log('Coroutine started');
yield 'Yield 1';
console.log('Coroutine resumed');
yield 'Yield 2';
console.log('Coroutine ended');
}
const gen = coroutine();
console.log(gen.next().value); // Logs "Coroutine started", then "Yield 1"
console.log(gen.next().value); // Logs "Coroutine resumed", then "Yield 2"
In this example, the coroutine
function is a generator that yields two values. The .next()
method is used to transfer control back and forth between the "main" routine (the sequence of console.log statements outside the generator) and the coroutine. This allows the coroutine and the main routine to cooperatively control the flow of the program.
Can you use "this" within a generator function?
View Answer:
What are some use cases for generators in JavaScript?
View Answer:
Pipelines Example
function* multiplyByTwo(iterable) {
for (const num of iterable) {
yield num * 2;
}
}
function* addOne(iterable) {
for (const num of iterable) {
yield num + 1;
}
}
const numbers = [1, 2, 3, 4];
const pipeline = addOne(multiplyByTwo(numbers));
console.log(Array.from(pipeline)); // [3, 5, 7, 9]
Can you combine generators with other ES6 features, like arrow functions or classes?
View Answer:
class MyClass {
constructor(data) {
this.data = data;
}
*dataGenerator() {
for (let item of this.data) {
yield item;
}
}
}
const myInstance = new MyClass([1, 2, 3, 4, 5]);
const gen = myInstance.dataGenerator();
console.log(gen.next().value); // 1
console.log(gen.next().value); // 2
console.log(gen.next().value); // 3
// And so on...
In this example, dataGenerator
is a generator method inside the MyClass
class. It yields the items in the data
array one by one.