Generator Functions in JavaScript

Generator Function

Generator Function:

  • Its a function but a bit different from the normal functions,
  • Normal functions run their entire code at once and return a value,
  • But a generator function has breakpoints in between marked by "yield",
  • And you can pause the code execution at yield,
  • To create a generator function, you need to add a (*) after function keyword,
function* generateSequence() {
  yield 1;
  yield 2;
  return 3;
}

let generator = generateSequence();

Generator Function

Generator Function:

  • When you call a generator function, you get an iterable object instead of function's return value,
  • the iterable object has the same ".next()" method,
  • to get any value from generator function, you need to call the ".next()" method,
  • on every call to ".next()", the code runs till it encounters a yield in the function,
  • it returns the value at yield, and then execution pauses, till "next" is called again
function* generateSequence() {
  yield 1;
  yield 2;
  return 3;
}

const generator = generateSequence();

const one = generator.next();
console.log(JSON.stringify(one));
// {value: 1, done: false}

Generator Function

Generator Function:

  • inside the function you only define the value with yield that you want at next call,
  • but yield returns an object of shape:
    {value: 1, done: false},
  • the return statement of a generator function returns an object like this:
    {value: 3, done: true},
  • once the done: true is returned, you cannot call .next anymore
function* generateSequence() {
  yield 1;
  yield 2;
  return 3;
}

const generator = generateSequence();

const one = generator.next();
console.log(JSON.stringify(one));
// {value: 1, done: false}

const two = generator.next();
console.log(JSON.stringify(two));
// {value: 2, done: false}

const three = generator.next();
console.log(JSON.stringify(three));
// {value: 3, done: true}

Generator Function

Real use-cases:

  • you can write a for loop and control it from outside
function* name() {
    for(let value = 0; value <= 10; value++) {
      yield value;
    }
}
const iter = name()
iter.next()
// {value: 0, done: false}

Generator Function

Generators are iterable

  • calling a generator function returns an iterable object,
  • so you can write for...of loop on that iterable object
  • Issue: for loop will not get the value in return statement, as the loop ends when iterable returns done: true
function* generateSequence() {
  yield 1;
  yield 2;
  return 3;
}

let generator = generateSequence();

for(let value of generator) {
  alert(value); // 1, then 2
}

function* generateSequence() {
  yield 1;
  yield 2;
  yield 3;
}

let generator = generateSequence();

for(let value of generator) {
  alert(value); // 1, then 2, then 3
}

Generator Function

Using generator functions to create iterables:

const range = {
  from: 1,
  to: 5,
  
  [Symbol.iterator]() {
    return {
      current: this.from,
      last: this.to,
      
      next() {
        if (this.current <= this.last) {
          return { done: false, value: this.current++ };
        } else {
          return { done: true };
        }
      }
    };
  }
};

console.log([...range]);
const range = {
  from: 1,
  to: 5,

  *[Symbol.iterator]() { 
// a shorthand for [Symbol.iterator]: function*()
      yield this.from++
      yield this.from++
      yield this.from++
      yield this.from++
      yield this.from++
  }
};
  • Any function that's a generator function, will return an object with ".next" method,
  • calling that ".next" method, will return an object: {value: , done: }

Generator Function

Generator composition:

  • yield*,
  • a generator function that yields another generator function,
  • inside a generator function, you can use yield* to call another generator function,
  • when generatePasswordCodes is called, it returns the iterable object,
  • the execution pauses at first yield* (line 8), till generateSequence generator function is not done with it's for...of loop
  • when line 8 is done, the code moves to next yield* on line 11
function* generateSequence(start, end) {
  for (let i = start; i <= end; i++) yield i;
}

function* generatePasswordCodes() {

  // 0..9
  yield* generateSequence(48, 57);

  // A..Z
  yield* generateSequence(65, 90);

  // a..z
  yield* generateSequence(97, 122);

}

let str = '';

for(let code of generatePasswordCodes()) {
  str += String.fromCharCode(code);
}

console.log(str);

Generator Function

Passing through the yield:

  • when you use assignment operator (=) on yield,
  • you can call the .next method twice for each yield
  • the first .next call yields the right hand side value of yield
  • the second .next call can be made with an argument, that argument is assigned as value to the variable assigned to yield,
  • when you call .next method second, with argument, it yields the value passed as argument
function* gen() {
  // Pass a question to the outer 
  // code and wait for an answer
  let result = yield "2 + 2 = ?";

  console.log({result});
}

let generator = gen();

let question = generator.next().value;

generator.next(4);

Generator Function

// ANother more elaborate example
function* gen() {
  let ask1 = yield "2 + 2 = ?";

  alert(ask1); // 4

  let ask2 = yield "3 * 3 = ?"

  alert(ask2); // 9
}

let generator = gen();

alert( generator.next().value ); // "2 + 2 = ?"

alert( generator.next(4).value ); // "3 * 3 = ?"

alert( generator.next(9).done ); // true

Generator Function

Stopping the generator in between:

  • the iterable object, has a method on it named "return",

  • you can call the "return" method that returns the object with

    {done: true},

  • you can also call the return method with an argument, and that argument is returned in the object value key,

  • calling the .next afterward will keep returning the same object

function* gen() {
  yield 1;
  yield 2;
  yield 3;
}

const g = gen();

g.next();        // { value: 1, done: false }
g.return('foo'); // { value: "foo", done: true }
g.next();        // { value: undefined, done: true }

Generator Function

Throwing error from generator

  • the iterable object, has a method on it named ".throw",

  • you can call the ".throw" method that throws error from generator,

  • the generator function stops when the error is thrown

function* generate() {
  let result = yield "2 + 2 = ?"; // Error in this line
}

let generator = generate();

let question = generator.next().value;

try {
  generator.throw(new Error("The answer is not found in my database"));
} catch(e) {
  alert(e); // shows the error
}

Generator Functions in JavaScript

By Yash Priyam

Generator Functions in JavaScript

  • 94