Generators in JavaScript - SkillBakery Studios

Breaking

Post Top Ad

Post Top Ad

Saturday, October 31, 2020

Generators in JavaScript

 A generator is a process that can be paused and resumed and can yield multiple values. A generator in JavaScript consists of a generator function, which returns an iterable Generator object.

In other words, we can say that generators can be used to manage flow control in a code. Cancel asynchronous operations easily since execution can be paused anytime.

Regular functions return only one, single value (or nothing).

Generators can return (“yield”) multiple values, one after another, on-demand. They work great with iterables, allowing creating data streams with ease.

Generator functions

To create a generator, we need a special syntax construct: function*, so-called “generator function”.

It looks like this:

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

Generator functions behave differently from regular ones. When such a function is called, it doesn’t run its code. Instead, it returns a special object, called “generator object”, to manage the execution.

function* generateSequence() {
  yield 1;
  yield 2;
  return 3;
}
 
// "generator function" creates "generator object"
let generator = generateSequence();
alert(generator); // [object Generator]

 

The function code execution hasn’t started yet:

The main method of a generator is next(). When called, it runs the execution until the nearest yield <value> statement (value can be omitted, then it’s undefined). Then the function execution pauses, and the yielded value is returned to the outer code.

The result of next() is always an object with two properties:

·        value: the yielded value.

·        done: true if the function code has finished, otherwise false.

For instance, here we create the generator and get its first yielded value:

function* generateSequence() {
  yield 1;
  yield 2;
  return 3;
}
 
let generator = generateSequence();
 
let one = generator.next();
 
alert(JSON.stringify(one)); // {value: 1, done: false}

As of now, we got the first value only, and the function execution is on the second line:

Let’s call generator.next() again. It resumes the code execution and returns the next yield:

let two = generator.next();
 
alert(JSON.stringify(two)); // {value: 2, done: false}

And, if we call it a third time, the execution reaches the return a statement that finishes the function:

let three = generator.next();
 
alert(JSON.stringify(three)); // {value: 3, done: true}

Now the a generator is done. We should see it from done:true and process value:3 as the final result.

function* f(…) or function *f(…)?

Both syntaxes are correct.

But usually the first syntax is preferred, as the star * denotes that it’s a generator function, it describes the kind, not the name, so it should stick with the function keyword.

Generators are iterable

As you probably already guessed looking at the next() a method, generators are iterable.

We can loop over their values using for..of:

function* generateSequence() {
  yield 1;
  yield 2;
  return 3;
}
 
let generator = generateSequence();
 
for(let value of generator) {
  alert(value); // 1, then 2
}

Looks a lot nicer than calling .next().value, right?

…But please note: the example above shows 1, then 2, and that’s all. It doesn’t show 3!

It’s because for..of iteration ignores the last value, when done: true. So, if we want all results to be shown by for..of, we must return them with yield:

function* generateSequence() {
  yield 1;
  yield 2;
  yield 3;
}
 
let generator = generateSequence();
 
for(let value of generator) {
  alert(value); // 1, then 2, then 3
}

As generators are iterable, we can call all related functionality, e.g. the spread syntax ...:

function* generateSequence() {
  yield 1;
  yield 2;
  yield 3;
}
 
let sequence = [0, ...generateSequence()];
 
alert(sequence); // 0, 1, 2, 3

In the code above, ...generateSequence() turns the iterable generator object into an array of items (read more about the spread syntax in the chapter Rest parameters and spread syntax)

Generator composition

Generator the composition is a special feature of generators that allows to transparently “embed” generators in each other.

For instance, we have a function that generates a sequence of numbers:

function* generateSequence(start, end) {
  for (let i = start; i <= end; i++) yield i;
}

Now we’d like to reuse it to generate a more complex sequence:

·        first, digits 0..9 (with character codes 48…57),

·        followed by uppercase alphabet letters A..Z (character codes 65…90)

·        followed by lowercase alphabet letters a..z (character codes 97…122)

We can use this sequence e.g. to create passwords by selecting characters from it (could add syntax characters as well), but let’s generate it first.

In a regular function, to combine results from multiple other functions, we call them, store the results, and then join at the end.

Summary

·        Generators are created by generator functions function* f(…) {…}.

·        Inside generators (only) there exists a yield operator.

·        The outer code and the generator may exchange results via next/yield calls.

In modern JavaScript, generators are rarely used. But sometimes they come in handy because the ability of a function to exchange data with the calling code during the execution is quite unique. And, surely, they are great for making iterable objects.

 

No comments:

Post a Comment

Post Top Ad