DEV Community

Cover image for Unlocking the Power of Generators in JavaScript
Yusuf Mubaraq
Yusuf Mubaraq

Posted on

Unlocking the Power of Generators in JavaScript

Introduction

Generators! No, JavaScript isn’t powering up physical generators. Instead, we’re talking about a special type of function that can pause and resume execution. These functions are incredibly useful for handling asynchronous code and generating values on demand.

When we say "special type of function," we mean generators are different from the regular functions we’re accustomed to. They have unique syntax and are invoked differently. Let’s take a look at an example to better understand this concept.

example-one
The example above demonstrates how a generator function is written. Notice the asterisk (*) symbol added to the function keyword before the function name. Inside the function, we encounter the yield keyword. What does it do? It acts as a special kind of return keyword, used to return a value each time the function pauses during execution.

The yield keyword enables the function to generate a sequence of values, one at a time, as they are requested. However, just like the await keyword can only be used within an async function, the yield keyword is exclusive to generator functions. It cannot be used outside of one or within a normal function. Now that we understand the syntax, let’s dig deeper!

If we were to log the example code to the console, what do you think would appear? You might expect some output, but in reality, the console remains empty. Why is that? As mentioned earlier, a generator function behaves differently from a regular function. It cannot be invoked or called like a standard function. Instead, it requires a specific approach to execute. Let’s see how to implement that below!

example-two
Let’s check the console again. This time, instead of the expected result, a generator object is printed. However, we still don’t see the actual output yet. Why? Let’s explore further!

example-three
A generator object is what you get when you call a generator function. This object acts as an iterator, allowing you to control the execution of the generator function. If we inspect the returned object, we’ll notice several properties and methods at play. However, for now, let’s focus on its prototype to understand its behavior more clearly.

example-four
Within the prototype, we can observe three key methods: next(), return(), and throw(). For now, let’s focus on the next() method. This method allows us to execute the code inside a generator function and retrieve its next yielded value. Let’s see how it works in practice!

example-five
Using the next() method ensures that the code inside the generator function is executed and its output is printed to the console.

example-six
As we can see, the console logs This is a generator example, followed by the result of the first yield. The latter includes two properties: value and done.

  • The value property represents the result returned by the yield statement.

  • The done property indicates whether the generator function has completed execution. If done is true, it means there are no more yield statements to execute. If false, it means there’s still code to be processed.

In this scenario, the done property is false because only the first yield has been executed. Remember what we mentioned earlier about generators: they give us the flexibility to control when code executes. To access the value of the remaining yield statements, we need to call the next() function again, twice in this case.

example-seven
The code above sequentially logs 1, 2, and 3, with the done property showing false for each call. However, on the final call to the next() method, the generator returns undefined and true. This indicates that the generator has completed execution and there are no more yield statements to process.

example-eight

Generator Use Cases

Here, we’ll explore some practical scenarios where generator functions can be effectively utilized in our day-to-day coding tasks.

Generators are a type of iterable, which means they can be iterated using loops or other iteration methods, such as the for...of loop. They provide a powerful way to handle sequences of data or asynchronous operations. Let’s demonstrate their usage with an example:

example-nine
Another powerful use case for generators is to handle asynchronous operations, such as fetching data from the internet.

example-ten
Here's an explanation of the above code. The fetchData() is an asynchronous generator function that yields the results of two API calls. The first yield fetches data from
"https://jsonplaceholder.typicode.com/posts/1", waits for the fetch promise to resolve and parses the response as JSON. The await ensures the data from the fetch call is resolved before yielding it. The second yield repeats the process for "https://jsonplaceholder.typicode.com/posts/2".

The IIFE(Immediately Invoked Function Expression) ensures the asynchronous function is defined and executed immediately. Inside the function, The for await...of loop is used to iterate over the values yielded by the fetchData generator. The main purpose of generator function for asynchronous call is the freedom to fetch any data whenever we want. We fetched both data above by iterating through them, but we can decide to make just one fetch call out of the two by using the method we implemented earlier.

example-eleven

The Return and Throw Method

Earlier, we examined the Prototype and its returned value. We’ve already discussed the next() method, so now let’s delve into the return method. This function behaves similarly to the standard return in a regular function. Once the return keyword is invoked, execution halts immediately, regardless of any remaining code, and the done property is set to true.

example-twelve

example-thirteen
The throw() methods in a generator acts as if a throw statement is inserted in the generator's body at the current suspended position, which informs the generator of an error condition and allows it to handle the error, or perform cleanup and close itself.

There's something you might have overlooked in my code since the start of this article, I’m guessing you still haven’t figured it out. Let me save you the trouble: it’s the absence of arrow function syntax. And here’s why: we can’t use arrow functions with generators in JavaScript. Arrow functions lack their own this or arguments context and are specifically designed for concise, context-bound expressions.

Conclusion

JavaScript generators are a powerful feature that allows for controlled execution of code, making them invaluable for tasks like managing asynchronous operations, handling large datasets, or creating custom iterators. Their ability to pause and resume execution, coupled with features like yield, next(), and return(), provides a unique level of flexibility in coding. So go ahead, give generators a try, and add some extra flair to your JavaScript journey—happy coding!

Top comments (0)