Where can JS generators help?
Going from the basics of JavaScript to an intermediary level, most of you may have heard of generators and scratch your head to determine when and how to use them. Or if you came from a React background as I did, you may have written generators with redux-saga and wondered why you had to use generator functions when writing sagas.
ES6 was a significant improvement in ECMAScript specification over ES5, which introduced multiple new features. Generators were one of them which allowed developers to define suspendable functions.
While there are not many use cases for generators, it has advantages that allow code blocks to be more decoupled and programs to be more memory efficient in certain scenarios.
Generators vs. Traditional Functions
Due to how Event Loop in JS engines work traditional synchronous functions are never pre-empted. I.e. if a traditional synchronous function starts to run it will run to completion before any other piece of code is run.
Generators change this paradigm by introducing a way to pause and resume even a synchronous code flow.
Generators are functions that have an asterisk (*)at the end of function
the keyword. The following code snippet is an example of such a generator function.
The magic keyword here is yield
which will allow the above generator function to return the intermediate results without the function running to its completion.
Generating Heavy Lists Efficiently
Imagine a scenario where you need to get all the numbers until a certain threshold but you do need them all at once but just the list incremented by a certain step. The generators provide a straightforward way to implement this.
In generators, whenever a yield
keyword is encountered the execution will be halted and the control will be granted back to the caller with the yielded value. The following snippet shows how we would use the above generator in the caller function.
The above may not be a real-world scenario but the same concept be used to generate compute-extensive and memory-exhaustive lists incrementally.
Generating Infinite Sequences
Due to the pause and resume nature of generators, they are a straightforward way to implement infinite sequence generation. The following code snippet generates the famous Fibonacci sequence indefinitely.
Note: Both of the above scenarios can be implemented with closures and higher-order functions in JS. However arguably the implementation using generators is more straightforward to read.
While the above example is a simple one the same pattern can be used to implement scenarios such as infinite scrolling in blogs or news sites.
Two-Way Communication
Another use case for generators can be two-way communication between caller and callee functions.
Generators facilitate this by replacing a yielded value with another value with generator.next
a call. Take a look at the following example.
In the above example, the callee generator function returns a prompt in the yielded value and the call enters a response for the prompt with the .next()
call. If we did not pass any value to the next()
call the yielded value itself will be assigned to the variable name
and car
.
This pattern can be used to replace any intermediate value generated in the generator functions with values that are known only to the caller function.
Conclusion
While there are not many real-world use cases where we need JS generators they do provide more efficient and straightforward solutions in certain scenarios. In this article we discussed three such scenarios which are incrementally generating heady lists, generating infinite sequences and two-way communication between callee and caller functions.
I have not discussed generators with promises in this article as the async/await mechanism provides a more concise way to handle asynchronous code flow. It may be worth noting that async/await is just syntax sugar that uses a generator and promise based mechanism under the hood to provide an elegant way of writing asynchronous code.