Thursday, November 5, 2015

JavaScript Generators

In today's blog post I will be discussing what JavaScript generators are and how they can be useful. Generators are part of ES6 and are truly a game changer.

Generators

Usually when we execute any functions, they run till the end (or first return statement). We cannot pause them in between and do something else.
The ES6 generators are special types of functions which can be paused. We can pause them, re-run them, pause them again and re-run them and so on.

Javascript Generators


A "function*" is used to represent a generator function and "yield" keyword marks the pauses in the generator. Let's take a look at an example:

function *foo() {
    yield 'a';
    yield 'b';
    yield 'c';
}

In order to use it, we simply call the next() function on the generator like this:

var func = foo();
console.log(func.next());  // Object {value: "a", done: false}

This return the first yield value i.e. 'a'. Next time, we call func.next(), we get this:

console.log(func.next());  // Object {value: "b", done: false}

So you can see how the function execution gets paused on each yield. And how it re-runs on each next() call.
If we execute it more, we get:

console.log(func.next()); // Object {value: "c", done: false}

console.log(func.next()); 
// Object {value: undefined, done: true}

So once it reaches the end of the generator function, we get the value as undefined and done as true. Once a generator function is done, it will keep returning the same values of undefined and true, no matter how many times we call the next() function on it afterwards.

Now, let's take a look at how we can do two-way communication using generators. Whatever we pass in the yield as argument, we get it back as the Object's value.

function *foo(a) {
    var b = 3 * (yield (a + 2));
    console.log(b);                         //24
    var c = yield (b / 2);
    console.log(c);                         //13
    return (a + b + c);
}
var func = foo(6);
console.log(func.next());   // Object {value: 8, done: false}
console.log(func.next(8));  // Object {value: 12, done: false}
console.log(func.next(13)); // Object {value: 43, done: false}


And if we want to pass anything to the generator function, we pass it as argument to the next() call.
So in the example above when we call foo() function, we pass 6 as argument. That's the value of "a". Then we call func.next() - that returns (6+2=) 8 as the value. The generator is paused at the first yield. Then we call func.next() second time with argument of 8. So the value of b is assigned as (8*3=) 24. So we passed in the value 8 into the generator function and that gets used inside the function.
Then, we called func.next() with argument 13 and that gets assigned to c. Then the last value is returned as  (6+24+13=) 43.

Why useful?

So the reason why these pausable functions can be useful is for asynchronous programming. We can do "yield" followed by asynchronous call. This will fetch the result of that asynchronous call. And whenever we are ready for the next call, we can simply call the next() function and get the result of the next asynchronous call. We can also pass in any arguments we like to the next() call which can be used in the asynchronous request we are making. 

function * () {
    var a = yield async1();
    var b = yield async2(a),
    var c = yield async3(b);
    console.log(a, b, c);
};


Conclusion

JavaScript Generators are a great way to write asynchronous code. We will have to write more and more of asynchronous code as the time progresses. Nesting asynchronous calls and callbacks makes our code look dirty and difficult to debug. With generators, not only the code will look better but will also be easier to debug.

For future updates to my weekly blog, please subscribe to my blog via the "Subscribe To Weekly Post" feature at the right and follow me on Twitter. Until then Happy Coding :)

No comments:

Post a Comment