DEV Community

Renato Rocha
Renato Rocha

Posted on

JavaScript Generator Functions

When starting the studying about React, we need learning some concepts like: components, properties and state. So, we see that a component has a internaly state and when that state change the component is rerendered. However some times we need share some information between our components, like user information or a shoopping cart and, for that we can use a manager state like Redux.

Redux is a state container for JavaScript apps, that to lead with assyncrounous operations needs a middleware, Like Redux Thank or Redux Sagas as example.

In the Redux Sagas getting start we can see an example of a saga:

export function* helloSaga() {
  console.log("Hello Sagas!");
}

As a documentation say: the only thing that can be weird is *. This symbol is a way we can creating a Generator.

Generator

Generators are functions that their processing can be stoped and resumed. When we create and execute a generator function, that do not work as a normmal function. The normal, when executed can return a single value or nothing.

function normalFunction() {
  return singleValue;
}

However when we call a generator function we will receive an object that can be used to control the process.

function* generateFunction() {
  yield "first";
  yield "second";
  return "third";
}

const objectGeneratorControl = generateFunction();

The object to control the generator processes has a method called next, this method when called initialize the generator flow:

function* generateFunction() { /* (INIT) Call of generateFunction() */
  console.log("First next() call");  
  yield "first"; /* (1) First call objectGeneratorControl.next() */
  yield "second"; /* (2) Sencond call objectGeneratorControl.next() */
}

const objectGeneratorControl = generateFunction(); /* Generator creating*/

console.log(objectGeneratorControl.next());
console.log(objectGeneratorControl.next());
console.log(objectGeneratorControl.next());

In the initial moment, when we call generateFunction(), the generator flow is initialized and a pointer is stoped at top (INIT mark). Thus, the console.log("First next() call") is not executed yet.

The console.log inside the generator function will be executed when we call for the first time the next method. So, the execution will runs until the "first" yield and return an object with the shape:

{
  "value": "first",
  "done": false
}

In this object, the property value, represents the yielded value and the done property say that the function not yet finished. Calling the next method again, it will produce:

{
  "value": "second",
  "done": false
}

However, whether we call next method again the result will be:

{
  "value": undefined,
  "done": true
}

This object tells us that the function has come to the end and without any value. However, we can specify a value by include a return statement:

function* generateFunction() {
  yield "first";
  yield "second";
  return "end";
}

Generator as Iterables

Iterables objects, in JavaScript, are objects that have a next() method returning an object like:

{
  "value": any
  "done": boolean
}

We can iterate over this objects easily using the for ... of statement, as an example we have that arrays are iterables.

const it = [1, 2, 3, 4, 5];

for (const iterator of it) {
  console.log(iterator);
}

So when we need iterate over a object, its iterator method is called, however only some built-in objects have the iterator method. Thus, if we need iterate over an object that do not has the iterator method, we can add it easily with generator.

const object = {
  begin: 1,
  end: 5,

  [Symbol.iterator]: function*() {
    for (; this.begin <= this.end; this.begin++) {
      yield this.begin;
    }
  }
};

for (const iterator of object) {
  console.log(iterator);
}

Generator as Data producer and consumer

As we see previously, a generator can produce values with the yield. However, for consume some data, this is made through the next method call. That is, it method accept a paramater wich will be the internal return of yield statement.

objectGeneratorControl.next("value");

However, the first call of next method will only "initialize" the generator flow. That is, this value passed to generator will be verified from the second next method calling.

function* generateFunction() {
  let valuePassedCameFromNextMethod;

  console.log(`First next method call:`);
  valuePassedCameFromNextMethod = yield "first";
  console.log(`Second next method call: ${valuePassedCameFromNextMethod}`);
  valuePassedCameFromNextMethod = yield "second";
  console.log(`Third next method call: ${valuePassedCameFromNextMethod}`);
  return "end";
}

const objectGeneratorControl = generateFunction();

console.log(objectGeneratorControl.next("Initializing"));

Executing the above code we will see the yielded object: { value: 'first', done: false }. But, the flow will stop at this point and thus the value passed to next method will be viewd inside the generator from the second call.

console.log(objectGeneratorControl.next("Now go"));

Will result:

Second next method call: Now go
{ value: 'second', done: false }

In this post, sought to carry out a little introduction about Generators that is a powerful tool to lead with multitasks due the possibilite of pause and resume the Generator. So, that feature gives the possibility of greater control of tasks, as we can see the reason of Redux Saga uses Generator.

Sources

https://github.com/gajus/gajus.com-blog/blob/master/posts/the-definitive-guide-to-the-javascript-generators/index.md
https://exploringjs.com/es6/ch_generators.html#sec_generators-as-observers
https://2ality.com/2015/03/es6-generators.html
https://javascript.info/generators
https://davidwalsh.name/es6-generators
https://github.com/redux-saga/redux-saga/issues/1373
https://github.com/redux-saga/redux-saga/issues/987#issuecomment-301039792
https://tc39.es/ecma262/#sec-generator-function-definitions

Top comments (0)