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.
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!
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!
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.
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!
Using the next()
method ensures that the code inside the generator function is executed and its output is printed to the console.
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 istrue
, it means there are no more yield statements to execute. Iffalse
, 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.
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.
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:
Another powerful use case for generators is to handle asynchronous operations, such as fetching data from the internet.
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.
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.
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)