Generators: The Key to Understand Redux-Saga

This blog is intended for developers who are familiar with JavaScript development and wants to learn about generator, a special function introduced as part of ECMAScript 6 release and tends to play a key role in redux-saga, a redux-based middleware library to implement asynchronous data flow in Redux.

Note: The crux of this blog is about generator functions and serves as a prelude to my upcoming blog on redux-saga so discussion pertaining to redux-saga will not be made in this blog.

What are generators?

According to MDN,

“Generators are functions that can be exited and later re-entered. Their context (variable bindings) will be saved across re-entrances.”

Consider generators as special functions in JavaScript that do not adhere to the same rules that regular functions need to follow. It is much easier to understand generators by looking at an example and see how they work.

How are they defined?

Generators, when declared via function declaration, they can be declared by attaching an *(asterisk) as a suffix to the function keyword or as a prefix to the function name. Or when declared via function expression, they can be declared by attaching an *(asterisk) as a suffix to the function keyword.

Note: If you are unsure about the difference between function declaration and function expression, please read my blog here: https://ahamedblogs.wordpress.com/2020/02/12/function-expression-vs-function-declaration/

Here are the possible ways they can be declared:

 


function* generator() {
}

view raw

generator.js

hosted with ❤ by GitHub

 


function *generator() {
}

view raw

generator.js

hosted with ❤ by GitHub

 


const generator = function*() {
}

view raw

generator.js

hosted with ❤ by GitHub

How do they work?

Here are some of the examples to gradually understand generators:

Return value

 


function *generator() {
return 'value';
}
console.log(generator()); //=> Object [Generator] {}

view raw

generator.js

hosted with ❤ by GitHub

The return value of a generator function is always a generator object. In the example above, invoking the generator function returned a generator object instead of the custom return value.

Exit and Reenter

 


function *generator() {
yield 'value1';
console.log('I am here');
yield 'value2';
}
const g = generator(); //=> Object [Generator] {}
console.log(g.next()); //=> {value: "value1", done: false}
console.log(g.next());
/**
* I am here
* {value: "value2", done: false}
**/
console.log(g.next()); //=> {value: undefined, done: true}
console.log(g.next()); //=> {value: undefined, done: true}

view raw

generator.js

hosted with ❤ by GitHub

Keywordyieldused within a generator’s body and nextmethod called on the returned generator object help shape the behavior of generator functions. Its behavior can be understood based on the above example:

  1. Calling the generator directly will not execute the function body, rather it returns a generator object.
  2. When nextmethod is invoked on this generator object, then the generator‘s body is executed until the first yieldexpression. This expression returns a generator object with valuewhich contain the value yielded and donewhich indicates whether all generator has yielded its last value. In the example, the expression produced doneflag as false since the generator has not finished its yield.
  3. Calling the nextmethod again on the generator object will resume the generator’s execution(instead of executing the body from the beginning) which then will execute from the last executedyieldexpression, producing again a generator object. Note that the done flag of this object is still false.
  4. Any subsequent calls to nextwill not execute the generator function and simply return generator objects with undefinedyield values and finally marks the completion of the generator function as indicated by the doneflag since there are no more yieldexpressions left to execute.

Finishing yield with return

 


function *generator() {
yield 1;
return null;
yield 2;
}
const g = generator();
console.log(g.next()); //=> {value: 1, done: false}
console.log(g.next()); //=> {value: null, done: true}
console.log(g.next()); //=> {value: undefined, done: true}

view raw

generator.js

hosted with ❤ by GitHub

The generator function is complete when it no longer has anyyieldexpressions left to execute or when it recognizes a return statement. But note in the above example, executing the nextmethod the second time has yielded nullwhich is same as the return value of the generator instead of undefinedlike in the previous example.

Preserving context

 


function *generator(i) {
yield i;
yield i + 1;
}
const g = generator(10);
console.log(g.next()); //=> {value: 10, done: false}
console.log(g.next()); //=> {value: 11, done: false}

view raw

generator.js

hosted with ❤ by GitHub

One of the coolest features about generators is its ability to remember the context across executions. In this example, when calling the nextmethod on the generator object, the function remembers the initial argument value,10 and calculations are made on it.

Why was it introduced?

ECMAScript originally introduced generators to write asynchronous code with Promise as if they are synchronous mainly because at that time, handling asynchronous tasks was complicated as the code became unmanageable due to callback hell. If you are wondering how we can write asynchronous code with generators, here is the code:

 


const AsyncModule = generator => {
return () => {
const iterator = generator();
const resolve = next => {
if (next.done) {
return Promise.resolve(next.value);
}
return Promise.resolve(next.value).then(response => {
//recursively call resolve() until done is `true`
return resolve(iterator.next(response));
});
};
return resolve(iterator.next());
};
};
const asyncGenerator = function*() {
const response = yield fetch('https://pokeapi.co/api/v2/pokemon');
const json = yield response.json();
return json;
};
const getData = AsyncModule(asyncGenerator);
console.log(getData().then(d => console.log(d))); =>//prints api data

Note: Handling asynchronous tasks in JavaScript is much easier with Async/Await now. Please check it out here: https://ahamedblogs.wordpress.com/2020/02/16/async-await-in-javascript/

Conclusion

We have learned about generator functions and understood how they work. Generators really pack a lot of features beyond just handling async code and it is a great addition to the language. Do not worry if you do not understand the use cases of generators as of now. It will make sense when we look at its practical application in redux-saga. Happy coding!

Leave a comment