Normal functions vs Generator functions :
functions in javascript "run until return/end".
Generator Functions " run until yield/return/end".
Generator functions :
======================
A generator is a function that can stop midway and then continue from where it stopped.
Generator functions once called,returns the 'Generator Object',which holds the
entire 'Generator Iterable' that can be iterated using next() method or for..of loop.
" Every next() call on the generator executes every line of code until the next yield it encounters and suspends its execution temporarily".
Syntactically generators are identified with a *, either 'function* X' or 'function *X' -- both mean the same thing.
Note :
Once created, calling the generator function returns the Generator Object.
This generator object needs to be assigned to a variable to keep track of the subsequent next() methods called on itself.
If the generator is not assigned to a variable then it will always yield only till first yield expression on every next().
The generator functions are normally built using yield expressions.
Each yield inside the generator function is a stopping point before the next execution cycle starts.
Each execution cycle is triggered by means of next() method on the generator.
On each next() call, the yield expression returns its value in the form of an object containing the following parameters.
{ value: 10, done: false } // assuming that 10 is the value of yield
Value — is everything that is written on the right side of the yield keyword, it can be a function call,
object or practically anything. For empty yields this value is undefined.
Done — indicates the status of the generator, whether it can be executed further or not.
When done returns true, it means that the function has finished its run.
1.Assign the generator to a variable.
ex : function* generatorFunction(i){
yield i;
yield i+1;
}
let generator = generatorFunction(5);
console.log(generator.next()); //{value: 5, done: false}
console.log(generator.next()); //{value: 6, done: false}
//Accessing generatorFunction directly
console.log(generatorFunction(5).next()); //{value: 5, done: false}
console.log(generatorFunction(5).next()); //{value: 5, done: false}
the generator function accessed directly without a wrapper always executes only until the first yield.
Hence , by definition you need to assign the Generator to a variable to properly iterate over it.
Lifecycle of a Generator Function
Each time a yield encountered the generator function returns an object containing the value of the
encountered yield and the done status.
Similarly, when a return is encountered, we get the return value and also done status as true.
Whenever, done status is returned as true,is essentially means that the generator function has
completed its run, and no further yield is possible.
Note: Everything after the first 'return' is ignored,including other 'yield' expressions.
2.Assigning Yield to a Variables
ex :
function *generatorFunction(){
const x=yield 1;
console.log(x);
const y=yield 2;
console.log(y);
}
let generator=generatorFunction();
console.log(generator.next());
//{value: 1, done: false}
console.log(generator.next());
// {value: 1, done: false}, x undefined
console.log(generator.next());
//{value: undefined, done: true}, y undefined
Note :
Starting from second next(), the previous yield is replaced with arguments passed in the next function.
Since, we do not pass anything here in the next method, its assumed that the entire ‘previous-yield expression’ as undefined.
3.Passing Arguments to the next() method :
ex :
function *generatorFunction(i){
console.log(i);
const j = 5 * (yield (i * 10));
console.log(j);
const k = yield (2 * j / 4);
console.log(k);
return (i + j + k);
}
var generator1 = generatorFunction(10);
// i is 10
console.log( generator1.next(20));
// {value: 100, done: false}
console.log( generator1.next(10));
// j is 50
// {value: 25, done: false}
console.log( generator1.next(5));
// k is 5
// {value: 65, done: true}
4. Passing Yield as an Argument of a function
ex :
function *generatorFunction(){
yield; // pause here and returned undefined
foo( yield "I am useless"); // pause and wait for next() to finish.
}
function foo(x){
console.log(" Just printing argument passed ", x);
}
let generator3 = generatorFunction();
console.log( generator3.next());
// {value: undefined, done: false}
console.log( generator3.next());
// {value: "I am useless", done: false}
console.log( generator3.next());
// Just printing argument passed undefined
// {value: undefined, done: true}
5. Yield with a Function Call
Apart from returning values yield can also call functions and return the value or print the same.
function *fetchUser(){
const user = yield getData();
console.log(user) // undefined
}
function getData(){
return { name:"Jim Cooper", dob:'1991'};
}
let fetchGen = fetchUser();
console.log(fetchGen.next());
// { value: {name: "Jim Cooper", dob: "1991"} , done: false}
console.log(fetchGen.next());
// undefined
//{value: undefined, done: true}
6. Yield with Promises
Yield with promises follows the same approach as the function call,
instead of returning a value from the function, it returns a promise
which can be evaluated further for success or failure.
ex :
function * fetchUser(action){
const user = yield apiCall();
}
function apiCall(){
return new Promise(resolve => {
setTimeout(() => {
resolve ( { name:'Jim Cooper', dob:'1991'});
},2000);
})
}
let fetchgen2 = fetchUser();
// prints user data after 2 seconds
console.log( fetchgen2.next().value.then( n => console.log(n)));
// Promise
// { name: "Jim Cooper", dob:"1991"}
The apicall returns the promises as the yield value, when resolved after 2 seconds prints the value we need.
7. Yield*
Yield* when used inside a generator function delegates another generator function.
function* g1(){
yield 2;
yield 3;
yield 4;
}
function* g2(){
yield 1;
yield* g1();
yield 5;
}
let iterator = g2();
console.log( iterator.next()); // {value: 1, done: false}
console.log( iterator.next()); // {value: 2, done: false}
console.log( iterator.next()); // {value: 3, done: false}
console.log( iterator.next()); // {value: 4, done: false}
console.log( iterator.next()); // {value: 5, done: false}
console.log( iterator.next()); // {value: undefined, done: true}
8. Yield* with Return
Yield* with a return behaves a bit differently than the normal yield*.
When yield* is used with a return statement it evaluates to that value,
meaning the entire yield* function() becomes equal to the value returned
from the associated generator function.
ex :
function* genFuncChild(){
yield 1;
yield 2;
return 'foo';
yield 3; // note that this is ignored
}
function* genFuncMain(){
const result = yield* genFuncChild();
console.log(result);
yield 'the end';
}
let generator = genFunction();
console.log(generator.next()); // yield 1
console.log(generator.next()); // yield 2
console.log(generator.next()); // returns 'foo' and yields 'the end'
console.log(generator.next()); // finishes run
9. Yield* with a Built-in iterable Object
yield* can also iterate over iterable objects like Array,String and Map.
function* genFunc(){
yield* [1,2];
yield* 'HI';
yield* arguments;
}
let generator = genFunc(5,6);
console.log(generator.next()); // prints 1
console.log(generator.next()); // prints 2
console.log(generator.next()); // prints H
console.log(generator.next()); // prints I
console.log(generator.next()); // prints 5
console.log(generator.next()); // prints 6
yield* iterates over every possible iterable object that is
passed as its expression.
10.yield with for..of
Every Iterator/generator can be iterated over a for..of loop.
similar to our next() method which is called explicitly , for..of loop
internally moves on to the next iteration based on the 'yield keyword'.
And it iterates only till the 'last yield' and doesn't process the return
statement like next() method.
ex :
function* genFuncChild(){
yield 1;
yield 2;
return 'foo';
yield 3; // note that this is ignored
}
function* genFuncMain(){
const result = yield* genFuncChild();
console.log(result);
yield 'the end';
}
let generatorforof=genFuncMain();
// using for..of to print the generator
for(let i of generatorforof){
console.log(i);
}
//result
// 1
// 2
// the end
The final return in not printed, because for..of loop iterates only till the last yield.
So,it comes under best practice to avoid return statements inside a generator function as
it would affect the reusability of the function when iterated over a for..of.
No comments:
Post a Comment