Be the first user to complete this post

  • 0
Add to List

Getting started with es6 generator functions

This article is the fourth installment of our Learning ES6 series. In this article, we will introduce you to generators in ES6 using a number of examples.


Generators - a function apart

I found generator functions to be the most interesting among all the other new features introduced in ES6. Their uniqueness lies in the way they alter the control flow of a program. Lets first see how control flow takes place in regular functions - Synchronous execution: You would pass the function a set or arguments, it would act upon them and finally return a value(or not). - Asynchronous execution: You would pass the function a callback function as one of the arguments and at some point in the function's execution flow, it would invoke the callback that was passed. The above points indicate that until now there a caller always has just one opportunity to communicate with a function - and thats at invocation time. There has never been a way for an invoking program to step through a function while it was executing and pass it values as and when needed. And this, my firend is exactly what generators functions aim to do.

So, what exactly is a generator function

At first glance, a generator function might seem like an ordinary function. It is declared with the a prefix of * for e.g.
function* myFirstGenerator() {
  ....
}
So far so good. But this is where its similarity to regular functions ends. Unlike reguar functions, when you invoke a generator function, not even a single line of code inside of it gets executed. For e.g.
function* myFirstGenerator() {
  console.log('Hi!');
}

myFirstGenerator(); // This will not print anything
Strange isint it? Well, not so much once you understand what happened.
When you invoke a generator function, all it does is return a generator object. This generator object is ready to begin execution of the function body at the first invocation of next() on the generator object itself.
Here's the example from above, but this time we make it print 'Hi' by invoking next() on the generator object.
function* myFirstGenerator() {
  console.log('Hi!'); // (A)
}

var gen = myFirstGenerator(); // Save a reference to the generator object
gen.next(); // This executes line (A)
Pretty interesting right? Well, interesting isint enough. It has got to be useful as well. We will get to that, step by step, since what you saw above is just one piece of the puzzle.

Using generators with yield: An interleaved execution order

Perhaps the only way to derive meaningful use of from generator functions is when they are used in conjunction with the yield keyword. The yield keyword, which was also introduced in ES6 allows you to pause execution of a function and optionally emit a value and/or receive a value in its place. To start things on a simpler note, you can think of yield as a placeholder in a line of code that pauses function execution.
function* doSomething() {
    yield; // This just pauses execution but does not emit anything
}
The above function can be invoked as follows
// Create a generator object as usual
var gen1 = doSomething();

// As we saw earlier,
// function execution begins on the first invocation
// of next() but since this function has a yield,
// execution pauses the moment the keyword 'yield' is encountered
console.log(gen1.next());

// A second invocation attempts to resume execution of the
// function until it is complete or another yield is encountered
console.log(gen1.next());

Lets take another look at this example, this time with a few console.logs interleaved between the code.
function* doSomething() {
    console.log('1');
    yield; // Line (A)
    console.log('2');
}

var gen1 = doSomething();

gen1.next(); // Prints 1 then pauses at line (A)
gen1.next(); // resumes execution at line (A), then prints 2

Now, I know that the two examples above didnt really showcase the 'placeholder' concept that we talked about in the preceeding paragraph. Therefore, in the following example, we will modify the above code a bit more to demonstrate the usage of yield as a placeholder.
function* doSomething() {
    var v = yield; // Pause execution, wait for the next() invocation.
    console.log(v);
}

var gen1 = doSomething();

// This makes the generator pause at the yield
gen1.next();

// This resumes execution and replaces the 'yield' placeholder
// with the argument being passed - 'Hola'
gen1.next('Hola');
Perhaps the most interesting aspect of the above example is the usage of next() to pass values into a function, right in the middle of its execution flow. And this is only possible because yield pauses function execution until the next next() !

Using multiple yields

The ability to have multiple yields within a function is exactly what makes the whole concept of generators all the more useful. Check out the following example in which we iteratively print hello world, one yield at a time.
function* printTwoThings() {
  var val = yield;
  console.log(val);

  val = yield;
  console.log(val);
}

var gen = printTwoThings();

gen.next(); // Start the function execution
gen.next('hello'); // First yield
gen.next('world'); // Second yield

Whats next

This article covers the usage of ES6 generator functions with yield acting as a data consumer/placeholder. In the next article, we will discuss the dual role of yield as a data producer and the interesting ways in which it enables 2 way communcation during a function's execution.



Also Read:

  1. How to get the data of a node in d3
  2. Getting the parameters and arguments of a javascript function
  3. JavaScript Objects and Arrays Useful Methods