Show Buttons
Share On Facebook
Share On Twitter
Share On Google Plus
Share On Linkdin
Share On Reddit
Share On Stumbleupon
Contact us
Hide Buttons

Getting started with es6 generator functions

This arti­cle is the fourth install­ment of our Learn­ing ES6 series. In this arti­cle, we will intro­duce you to gen­er­a­tors in ES6 using a num­ber of examples.


Gen­er­a­tors — a func­tion apart

I found gen­er­a­tor func­tions to be the most inter­est­ing among all the other new fea­tures intro­duced in ES6. Their unique­ness lies in the way they alter the con­trol flow of a program.

Lets first see how con­trol flow takes place in reg­u­lar func­tions
Syn­chro­nous exe­cu­tion: You would pass the func­tion a set or argu­ments, it would act upon them and finally return a value(or not).
Asyn­chro­nous exe­cu­tion: You would pass the func­tion a call­back func­tion as one of the argu­ments and at some point in the function’s exe­cu­tion flow, it would invoke the call­back that was passed.

The above points indi­cate that until now there a caller always has just one oppor­tu­nity to com­mu­ni­cate with a func­tion — and thats at invo­ca­tion time. There has never been a way for an invok­ing pro­gram to step through a func­tion while it was exe­cut­ing and pass it val­ues as and when needed. And this, my firend is exactly what gen­er­a­tors func­tions aim to do.


So, what exactly is a gen­er­a­tor function

At first glance, a gen­er­a­tor func­tion might seem like an ordi­nary func­tion. It is declared with the a pre­fix of * for e.g.

function* myFirstGenerator() {
  ....
}

So far so good. But this is where its sim­i­lar­ity to reg­u­lar func­tions ends. Unlike reguar func­tions, when you invoke a gen­er­a­tor func­tion, not even a sin­gle line of code inside of it gets exe­cuted. For e.g.

function* myFirstGenerator() {
  console.log('Hi!');
}

myFirstGenerator(); // This will not print anything

Strange isint it? Well, not so much once you under­stand what happened.

When you invoke a gen­er­a­tor func­tion, all it does is return a gen­er­a­tor object. This gen­er­a­tor object is ready to begin exe­cu­tion of the func­tion body at the first invo­ca­tion of next() on the gen­er­a­tor object itself.

Here’s the exam­ple from above, but this time we make it print ‘Hi’ by invok­ing next() on the gen­er­a­tor object.

function* myFirstGenerator() {
  console.log('Hi!'); // (A)
}

var gen = myFirstGenerator(); // Save a reference to the generator object
gen.next(); // This executes line (A)

Pretty inter­est­ing right? Well, inter­est­ing isint enough. It has got to be use­ful as well. We will get to that, step by step, since what you saw above is just one piece of the puzzle.


Using gen­er­a­tors with yield: An inter­leaved exe­cu­tion order

Per­haps the only way to derive mean­ing­ful use of from gen­er­a­tor func­tions is when they are used in con­junc­tion with the yield key­word. The yield key­word, which was also intro­duced in ES6 allows you to pause exe­cu­tion of a func­tion and option­ally emit a value and/or receive a value in its place.

To start things on a sim­pler note, you can think of yield as a place­holder in a line of code that pauses func­tion execution.

function* doSomething() {
    yield; // This just pauses execution but does not emit anything
}

The above func­tion 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 exam­ple, this time with a few console.logs inter­leaved 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 exam­ples above didnt really show­case the ‘place­holder’ con­cept that we talked about in the pre­ceed­ing para­graph. There­fore, in the fol­low­ing exam­ple, we will mod­ify the above code a bit more to demon­strate 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');

Per­haps the most inter­est­ing aspect of the above exam­ple is the usage of next() to pass val­ues into a func­tion, right in the mid­dle of its exe­cu­tion flow. And this is only pos­si­ble because yield pauses func­tion exe­cu­tion until the next next() !


Using mul­ti­ple yields

The abil­ity to have mul­ti­ple yields within a func­tion is exactly what makes the whole con­cept of gen­er­a­tors all the more use­ful. Check out the fol­low­ing exam­ple in which we iter­a­tively 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 arti­cle cov­ers the usage of ES6 gen­er­a­tor func­tions with yield act­ing as a data consumer/placeholder. In the next arti­cle, we will dis­cuss the dual role of yield as a data pro­ducer and the inter­est­ing ways in which it enables 2 way com­munca­tion dur­ing a function’s execution.


You may also like...