Generators are based on iterators, every generator is an iterator, basically they are just an easier and cleaner way to write iterators، as you don't need to keep state of value, or done.
Generator is just a function that has slightly different behaviour, that give the function the ability to pause and then continue execution later.
- Generator function is created by adding
*
in function signature example:function* myGenerator() { /* body */ }
- Generator function will pause execution whenever
yield
is reached - When Generator function is executed it will return iterator that we can use to iterate all values.
- We can use iterable based constructs on generators example [
for-of
,[] array destrcuturing
,...
]
We will write Example 14.2 using generators
let numbersGenerator = function*(max) {
let num = 0;
while(num < max) {
yield ++num;
}
}
const numbersIterator = numbersGenerator(6);
while (true) {
const current = numbersIterator.next();
console.log(current.value);
if (current.done) {
break;
}
}
// 1 2 3 4 5 6 undefined
Generator will simplify the code of iterator, and when a generator function is executed it will just return an iterator, and we can start calling next
the same way we did it for iterator implemenation
We can delegate another generator or iterable Object to do the work of current generator using yield*
expression.
function* delegateGenerator() {
yield 1;
yield 2;
yield 3;
}
function* numberGenerator() {
yield* delegateGenerator();
}
const myIterator = numberGenerator();
console.log(myIterator.next().value);
console.log(myIterator.next().value);
console.log(myIterator.next().value);
// 1 2 3
When we declare a generator function with argument this argument will be passed only once when we execute the function and it returns iterator, and will be available during all calls of next
function* myGeneratorFunction(x) {
console.log(x);
yield x;
}
const myIterable1 = myGeneratorFunction();
const myIterable2 = myGeneratorFunction(22);
myIterable1.next(); // undefined { value: undefined, done: false }
myIterable2.next(); // 22 { value: 22, done: false}
The rule of arguments passed to next
method is that, the argument will replace the previous yield
expression, this means that passing an argument to the first next
call will simply be ignored!
function* myGeneratorFunction() {
const x = yield 1;
console.log(`x = ${x}`);
const y = yield 2;
console.log(`y = ${y}`);
}
const myIterable = myGeneratorFunction();
console.log(myIterable.next());
console.log(myIterable.next());
console.log(myIterable.next());
// { value: 1, done: false}
// x = undefined
// { value: 2, done: false}
// y = undefined
// { value: undefined, done: true}
As we can see from previous example, both values x
and y
are undefined
because whenever we call next
(starting from second call of next
) the previous yield will be set to that argument, and since we don't pass anything the previous yield is set to undefined
This is a copy of example 15.3 but we will make sure to pass values while calling next
method
function* myGeneratorFunction() {
const x = yield 1;
console.log(`x = ${x}`);
const y = yield 2;
console.log(`y = ${y}`);
}
const myIterable = myGeneratorFunction();
console.log(myIterable.next(22));
console.log(myIterable.next(33));
console.log(myIterable.next(44));
// { value: 1, done: false}
// x = 33
// { value: 2, done: false}
// y = 44
// { value: undefined, done: true}
As we can see from previous example
- 22 passed in first
next
call is completely ignored - 33 replaced the entire previous
yield 1
expression which means x is set to 33 - 44 replaced the entire previous
yield 2
expression which means y is set to 44
From previous example we can also track easily the behaviour of generator function execution and where exactly it will pause.
- on execution of this line
myIterable.next(22)
generator function willyield 1
and pause execution, even the assignment of x is not done - on execution of this line
myIterable.next(33)
generator function will assign33
tox
and willyield 2
and pause, even the assignment of y is not done - on execution of this line
myIterable.next(44)
generator function will assign44
toy
and will return{ value: undefined, done : true}
In this example we will explicitly yield
yield
function* myGeneratorFunction() {
yield yield yield;
}
const myIterable = myGeneratorFunction();
console.log(myIterable.next(22));
console.log(myIterable.next(33));
console.log(myIterable.next(44));
// { value: undefined, done: false}
// { value: 33, done: false}
// { value: 44, done: false}
// { value: undefined, done: true}
Notice how 22
is just ignored and first yield is returning undefined