Be the first user to complete this post

  • 0
Add to List

Generators and Yield in es6

This article is the fifth installment of our ES6 tutorial series. In this article, we will continue where we left off from our previous article on generators.


Two way communication using yield and next

Yield is a special keyword. Not only does it let you pause a function's execution, it also 'emits' a value which can be consumed by its caller. This way, the yield keyword can be used as both - a data consumer and a data producer. Take a look at the following example.
function* doSomething() {
  // This sends 'hello' to the caller and pauses execution
  // of this function until next() is invoked
  yield 'hello';

  // This sends 'world' to the caller and pauses execution
  // of this function until next() is invoked
  // Notice the two-way communication happening at this point
  var lastInput = yield 'world';

  console.log(lastInput);
}

var gen2 = doSomething();
// This prints out the value returned at the first yield and pauses
console.log(gen2.next().value); // Prints 'hello'

// This resumes execution after the first yield until the next
// yield is encountered
console.log(gen2.next().value); // Prints 'world'

// This resumes execution after the second yield but there is
// nothing more to execute
gen2.next('The end.');

----

Yield multiple values

Within a generator function yield* can be used to recursively iterate over an iterable.
function* doSomething() {
  yield* ['hello', 'world'];
}

var gen = doSomething();

console.log(gen.next().value); // Prints 'hello'
console.log(gen.next().value); // Prints 'world'
In fact, not only can you yield primitives, you can also yield other generators. That primarily because a generator object is an iterable in itself. For example.
function* generatorFoo() {
  yield 1;
  yield 2;
}

function* generatorBar() {
  // First Create the generator object by invoking the function
  // then yield it.
  yield* generatorFoo();
  yield 'I am bar!'
}

var genObj1 = generatorFoo();
// Iterate on the generator object directly
console.log(genObj1.next().value); // Prints 1
console.log(genObj1.next().value); // Prints 2

var genObj2 = generatorBar();
// Iterate on the generator object directly that in itself yields
// the values of another generator
console.log(genObj2.next().value); // Prints 1
console.log(genObj2.next().value); // Prints 2
console.log(genObj2.next().value); // Prints I am bar!
You can also use the regular for-of loop to do the same.
function* greet() {
  yield 'hello';
  yield 'world';
}

for (let message of greet()) {
  console.log(message);
}

Using the 'return' statement from within a generator.

The value of a return statement is usually unusable inside of a generator constructs except when used with yield*. For example.
function* greet() {
  yield 'hello';
  yield 'world';
  return 1;
}

// The for-of construct doesnt print the value thats returned
for (let message of greet()) {
  console.log(message);
}
Now take a look at the following example
function* greet1() {
  yield 'hello';
  yield 'world';
  return 1;
}

function* greet2() {
  // The result of invoking yield* is the return value.
  var returnValue = yield* greet1();
  console.log(returnValue);
}

for (let message of greet2()) {
  console.log(message);
}

Generator invocation and arguments

There are 2 tricky aspects when dealing with generators that lend it some special behaviour.
-Invoking a generator function doesn't execute the function right away. -The first invocatin of next() on a generator object does not accept any arguments.
These two aspect directly impact how and when you pass arguments to a generator.
function* player(name) {
  console.log('name received'); // (A)
  var life = 1000;
  yield 'Hello' + name; // (B)
  yield 'You have a life of a ' + life + ' years'; // (C)
}

// Lets create our generator object
// Since our function acceps a name, we need
// to pass this name right away
var p = player('Goku');
// Notice that although it seems like you executed
// the function above, it does not print the console statement
// at line (A)


// Invoking next() for the first time causes
// line (A) to be printed and then
// pauses at line (B).
// Notice how the first next() does not take any argument
console.log(p.next().value); // Prints 'name received' followed by 'Hello Goku'(which is this console log)

// Line (C) gets printed then paused
console.log(p.next().value); // Prints 'You have a life of a 1000 years'

// Nothing more to execute
console.log(p.next().value); // Prints undefined because there isint any next value

'Yield' and generator scope

Last but not the least, yield can only be used when its in the scope of generator. For example, the following code will not work.
function* generatorFoo() {
  [1, 2].forEach(function (item) {
    yield item; // This wont work because the scope has changed
  });
}

Hope you found the tiny experiments in the examples useful. I would like add more tiny experiments to this post, so if you have any more use cases that could be included here, just drop me a comment.



Also Read:

  1. es6 iterators and iterables - creating custom iterators
  2. Getting started with localStorage vs sessionStorage in html5
  3. css : center element vertically and horizontally
  4. Applying floats and clearfix to block elements