How Generators work in JavaScript

Ashwin Kumar
JavaScript in Plain English
5 min readMar 20, 2021

--

Imagine the possibilities of a function which can be stopped at any point and can return multiple values with valuable inputs from the outer environment.

Generator functions are special functions that can yield multiple values and are especially useful to create data-streams for iterables.

Normally a return statement stops the execution of a normal function and returns a value, however in a generator function, we can pause the execution of the function using yield keyword to return a specific value and can resume it later whenever needed thus returning as many values as there are yields present.

Let’s check the syntax:

A generator function syntax has an * (asterisk) sign after the function and can only be created using a function keyword, so no arrow functions. We can also use the anonymous function syntax.

Coming to executing this function, one of the catch in the flow is that we can only call the function once but how do we resume and make the subsequent calls on the same function?

The answer is the Generator object. Whenever we call the function, it does not execute the function, but rather returns a Generator object with method next(), which is called to execute the Generator function in parts if not whole:

Note that every value returned is wrapped in another object in which the value property has the returned data.

The done property represents if the function is paused or complete. In other words for every yield the done property returns true, and once all yields are over the last call returns false either with return value or undefined .

All subsequent next() calls after the explicit return statement will return {value:undefined, done:true} .

Thank you, next()

One of the amazing things of next() is that it’s a secret gateway to communicate with the Generator function. See for yourself:

Let’s decode the above code execution. The first noticeable thing would be the sum variable passed as argument which remains alive as long as the function is not fully executed by the Generator object.

The arguments of a generator function works the same way as a normal function.

Once we get the generator object, we call the first next() method without any argument . This will always be the case as the argument passed to the next() method is collected from the last yield statement.

This is a bit confusing but bear with me. The next() method executes a certain part of the generator function, which can be passed some value but how do we collect them?

This collection is specifically where the previous yield statement comes in handy which acts as a medium to receive the value passed in the next() method. This also limits us from passing multiple arguments to the next() call as only the first argument will be collected.

As the first next() call executes the part of the generator function which does not have a previous yield statement, the value passed is impossible to collect with the current design.

The second next() call is passed 40 which is collected by the first yield statement and added to sum.

The third next() is passed 20 which is collected by the second yield statement and again added to sum and the final value is returned by the generator function.

the next() method thus is immensely useful in making the function flexible and establishing a two way communication with the outside context.

Yield much?

yield is a flexible part in generator function, so much so that the below generator function is perfectly fine:

the yields are nested making the code run from rightmost yield , the return of which is fed to the one before it and so on until you hit the last yield.

In fact you can invoke the yields of another generator functions using yield* :

yield* basically extends the yields of the passed Generator function to the current Generator function to be executed as its own. Furthermore the return value of the passed Generator function at the end is collected by yield* allowing us to use the return value of the internal generator function as well if required.

How to yield the power of Generators?

Iterables

Generators satisfy the iterator protocols thus making it eligible to use as [Symbol.iterator] in any object to make it an iterable.

[Symbol.iterator] is the method that is internally called when we use a spread operator or for...of loop. And yes, Arrays and Strings have inbuilt [Symbol.iterator] method.

Whenever we use a spread operator on an object, its [Symbol.iterator] is called once which is expected to return an object on which the next() method is invoked up till done resolves to true value, at which point the iteration is ended and we get the final result.

So by theory, the below object is an iterable:

Note that the value returned by the next() method is only accepted as long as {done:false}.

Generators come in handy making it extremely easy for us to write [Symbol.iterator] method as it follows the same protocol. The above object can also be written as:

Custom Sequences

Generator object also has [Symbol.iterator] in its prototype chain which returns its own instance to be iterated over, thus making it very useful to create custom sequences:

Asynchronous Wrappers

Generators functions can be used to manage asynchronous tasks as the function can be paused for the asynchronous task to complete and then can be resumed to do further tasks.

We can write it just like an async await function but it can be more powerful than that as the function resuming control is completely in our hands.

For example, this can be useful to avoid duplicate side effects of the same fetch calls as we can kill the duplicate generator objects by calling the return() method instead of next() method on them as shown below:

Here the control is given to doasync() function which decides whether to complete the asyncgen() execution or to break it depending upon if the text passed includes ‘get’ text inside it.

Calling return() method on a generator object cancels all further yields on the generator function and executes only finally{} block if present to do any cleanup process and exits the function with generator object status closed.

Below is a small React example showing how we can use generator function just like an async await but with more control on the side effects. You can compare functions getData() and processData() in App.js :

In the above example, the generator wrapper takes the last fetch call into account to implement the side effects and kills the other function calls. However we can also change it to not process further calls while a fetch process is already active.

In this way the generator function can be implemented to act as a gateway between the fetch calls and the tasks to be implemented after the response thus giving us the power to customise it however we want.

Conclusion

Normally we don’t need functions to be paused in between however there are scenarios where Generators can really come in handy. The communication established by the Generator Function with Generator object is very unique and can be utilised in certain flows. It can also be very useful in customising the behaviour of iteration in objects and is a great addition to the javascript features.

Thanks for reading!

--

--