ES-6 Generators


Code for this and several other ES-6 features can be found in my github
Introduction:
Generators have been introduced in Java script as a part of the latest ES6 release. This feature offers significant advantages in asynchronous programming.
The Legacy stuff:
Functions in Java script (or in most languages) have been a bunch of instructions which start with a single motive- which is to execute the instructions and race to completion.
Let us consider for example:

var add = function (a, b){ 
if(a < 0) a = a*-1; 
if(b < 0) b = b *-1; 
return a+b;
}
The add function declared above is a simple function which takes two input parameters. Checks if any of them is negative. If it is negative it makes it positive. Adds the two numbers and returns their sum. Basically, once the add function starts execution, it just runs. Any change that happens around it can influence it in no way. If a developer wants the add function to behave differently, the developer has to modify the add function.
Let’s think differently:
What if, we had the ability to decide what a function can do, from outside the function. What if, in the add method defined previously, we always don’t want to make a negative number positive. Instead we want it to be zero in some cases. What if, the add function’s caller should be the one that determines this logic? Caller determining how the called function should work in the middle of its run?
That would mean the add function should pause in the middle of its execution and return the control back to caller and wait for the caller to call the add function again.
Function Generators- Opening new possibilities:
Java script has always been a language that does the unthinkable. Tell me about running on every browser and web view in the world
One of the new features is the ability to run a function like a record player. Functions can literally be paused (Note: not by calling another function from the current function) and then resumed at a later time.
Welcome to the world of Function Generators.
Syntax (or sugar):
The syntax is very simple. All it takes to create a generator function is an asterisk(*) symbol.

var add = function * (a,b) {
}

The above line makes the normal add function a generator function.
When you want to instruct the function to pause at a certain point, you can do so by using the yield keyword.
I will try to explain the syntax using an example.
Example:
Case:

var compute = function (a,b) { 
var x = a-10; 
var y = b+10; 
return x+y; 
}; 
var callCompute = function() { 
compute(7,-3) //returns 4 
};
callCompute();
The callCompute function calls the compute function with some parameters. The compute function, takes two parameters. Negates 10 from the first parameter. Adds 10 to the second parameter. Finds the sum of the two and returns it.
Now, we have a new requirement. The values used for summation in the compute function (variables x and y), should be between the range-5 and 10. If it is beyond that range, it should be set to 0.
Range checking is not with in the scope of the compute function (let us assume). All that the compute function should do is negate 10 from the first parameter, add 10 to the second and return their summation. Basically it is the role of the callCompute to determine if the number is with in the range.
In current scenario, we will have to create a new function, which will house the logic to check the range. Let us assume for the sake of this example, we have a scenario where it would be ideal to do it from with in the callCompute function. That would be something we could never do. Because, when the value of X is calculated, we are already with in the compute function, and unless or until we call a different function, we can pause the execution of compute. But per our scenario, it is best to have the logic to compute the range with in the callCompute function. So the only way is to pause after negating 10 from a, go to the callCompute function, check if the value is within the range. Else set 0 to x. Then do the same thing for y.
This is where generators come in handy.
Let us do the following steps:
1) Let us convert our compute function to a generator function. As discussed earlier, it is as simple as adding a *. 
2) Pause when 10 is negated and added to a and b. This as discussed earlier, is easy as well. It is just a matter of adding the yield keyword.

var compute = function* (a,b) { 
var x = yield a-10; 
var y = yield b+10; 
return x+y; 
} 
3) Now let us modify the callCompute function (I will explain what is happening here in a minute. Please bear with me). 

var callCompute = function () { 
var gen = compute(7, -3); 
var a1 = gen.next().value; 
if (a1 < -5 || a1 > 10) { 
a1 = 0; 
} 
var b1 = gen.next(a1).value; 
console.log(b1) 
if (b1 < -5 || b1 > 10) { 
b1 = 0; 
} 
var c1 = gen.next(b1); 
console.log(c1); 
}
Let us yield(pause :D) a bit and look at what is happening.
Yield:
The yield keyword, as discussed earlier, pauses the execution of the function and returns the control to the caller. Along with it, it also returns an object: {value: , done: }
a-10 and b-10 If the generator function has completed its run or not.
So, we are passing the following parameters to the compute function: a=7 and b = -3
yield a-10 will return {value:-3, done:false} yield b+10 will return {value:7, done:false} . It returns false because the compute function is not complete yet.
next():
The next() function is the driving force that drives the generator function. When a normal function is called like we do in the following line: var gen = compute(7, -3) The compute function will be executed completely.
But in the case of generator functions, it is not executed until the next is called. So when the first next is called as shown in the following line: var a = gen.next().value;//returns -3 the compute function is executed till the first yield. And pauses. It returns the value to the right of yield which is computed as 7-10 = -3. How ever it should be noted that x will not be set to -3. Rather -3 is set to the variable a1 in callCompute function.
Then the callCompute function checks if a1 falls with in the range, if not assigns 0 and passes it to the next function. The value that is passed to the next function is what is set to x.
Similarly, compute yields a value for b1 and y is calculated.
Generators in Asynchronous programming:
This might not sound like a big deal in normal circumstances, since you might have seen from my above example, I might have to enforce weirdest of conditions to make it sound useful.
But java script is asynchronous. It finds a lot of use cases in asynchronous programming.
Case:
We get first name of a person from a service, last name from a different service and we need concatenate both of these, to get the full name.
Instead of making a http request to a different service, I have called the setTImeOut method, just to replicate the async nature of http requests.
With Call back:
Let us see how we will do it with a call back function.

var firstName = function(){ 
setTimeout(()=>{ 
var first = 'first'; 
setTimeout(()=>{ 
var last = 'last' 
setTimeout(()=>{ 
console.log(first+last); 
}); 
}); 
}); 
}; 
firstName();
As you could see, the code is least readable. We have to call the lastName and fullName functions from within the firstName function. Basically there is a lot of function nesting going on here which could be complex for some one trying to read your code.
With Promises:
Promises were invented to save us from this callback hell. Let us see how we would do it while using promises. I am using the Q library. But that is not important. Any library can be used.

var Q = require('Q');

var firstNameFn = function() { 
var deferred = Q.defer(); 
setTimeout(()=>{ 
deferred.resolve('first'); 
}); 
return deferred.promise; 
};

var lastNameFn = function() { 
var deferred = Q.defer(); 
setTimeout(()=>{ 
deferred.resolve('last'); 
}); 
return deferred.promise; 
};

var fullName = function(){ 
var firstNamePromise = firstNameFn(); 
var lastNamePromise = lastNameFn(); 
var firstName,lastName; 
firstNamePromise.then((name)=>{ 
firstName = name; 
lastNamePromise.then((name)=>{ 
lastName = name; 
console.log(firstName+lastName); 
});

});
}

fullName();
As you could see, it is better than call back. The firstName and lastName function calls are independent of each other. However, the promise resolution is nested. Much better than callback. But we could do better.
With Generators:
Let us see how we will do it with Generators:

var firstName = function(){ 
setTimeout(()=>{ 
gen.next('first'); 
}); 
};

var lastName = function(){ 
setTimeout(()=>{ 
gen.next('last'); 
}); 
};

var fullName = function * (){ 
var a = yield firstName(); 
var b = yield lastName(); 
console.log(a + b ); 
};

var gen = fullName(); gen.next();
Much more cleaner than promises. It almost looks like synchronous code. What is better, even exception handling can be done like we would do in synchronous code.
Remember: In the case of callbacks and promises, the function waits for none. It basically runs to completion. So error handling should be done separately in each async function, because the async function is not executed as a part of the same event loop.
But in the case of generators, error handling can be done as follows:

var firstName = function(){ 
setTimeout(()=>{ 
gen.next('first'); 
}); 
};

var lastName = function(){ 
setTimeout(()=>{ 
gen.next('last'); 
}); 
};

var fullName = function * (){ 
try{ 
var a = yield firstName(); 
var b = yield lastName(); 
console.log(a + b ); 
} catch(error){ 
//handle error 
} 
};
Since yield makes the function wait till gen.next is called, the event loop is not completed eventhough an async function is called and any error thrown by the async function is caught by the catch block.
We are so close to synchronous style of coding. But we still have an area to improve. The firstName and lastName functions need to be aware that it is being called by a generator function and hence it should call the next method. If a different non-generator method calls this function, this will fail and hence we need a different function for this.
With Generator + promises+ co:
That is where promises come in. Promises when combined with generators and a library like co or the Promise.coroutine of BlueBird, they can be really powerful.
Let us see the code:

var co = require('co'); 
var Q = require('Q');

var firstNameFn = function() { 
var deferred = Q.defer(); 
setTimeout(()=>{ 
deferred.reject('first'); 
}); 
return deferred.promise; 
};

var lastNameFn = function() { 
var deferred = Q.defer(); 
setTimeout(()=>{ 
deferred.resolve('last'); 
}); 
return deferred.promise; 
};

var fullName = function*(){ 
try{ 
var first = yield firstNameFn(); 
var last = yield lastNameFn(); 
console.log(first + last); 
} catch(err){ 
console.log('error = ' + err); 
} 
} co(fullName);
The thing to look here is, the fullName function is called by co and the absence of gen.next. Basically co takes care of calling the gen.next for you and passing in the value returned by yield as parameter to the next method. We don’t have to do any of that.
Isn’t that pretty powerful??
When Not to use Generators:
Generators pause the event loop. The thread sits idle waiting for Yield to return its value. This could be fatal in some scenarios when used to create backend services, because Node.js runs a single thread. Consider a scenario where we are using, generators to make 3HTTP calls, the thread sits there waiting for all the three calls to complete, which other wise would have pushed it to the event loop and went ahead to service the next request .

Comments

Popular posts from this blog

Docker Commands

Dockerfile

ES-6 classes