An Introduction to JavaScript Generators

Alice Kallaugher

October 28, 2022


One of the fundamentals of JavaScript is that it is single-threaded, meaning that two pieces of code cannot run at the same time. If we call a function, we expect it to run to completion, blocking any other code from running. This presents challenges for any task where you need to wait for something to happen (for example, waiting for an API response). We have different tools at our disposal to help with this, including callback functions, promises, and more recently async/await, introduced with ES8.

A lesser known, but still very powerful tool was introduced earlier, with ES6: generators. These are similar to async/await in that they let us write asynchronous code in a linear, straightforward fashion. However, they also provide the ability to pause and restart a function, without blocking the execution of other code — exactly what we’re used to not being able to do in JavaScript!

I first encountered generators through redux-saga, an excellent library for handling side effects in Redux. I was curious to learn about how they worked, and found them a little unintuitive at first. I spent some time digging into them, and in this post I’ll share what I found.

You may recognize them from their somewhat unique syntax, with a star after the function declaration and the use of the yield keyword (which can only be used within a generator function):

function* generatorFunc() {

As their name suggests, generators generate a sequence of values. Each time a generator is paused, it returns a new value, and each time it’s restarted it can take in a new argument. Following how the input and output are used can be a little tricky, so I’m going to focus on these two aspects, breaking down how generators both generate and consume data.

Generating data

Generators are a type of iterator, which are objects that define a sequence (one example is the array iterator). Iterators must have a next() method, which is used to traverse the sequence. Each time next() is called it returns an iterator response, which specifies whether the sequence is done as well as the next value in the sequence (or the return value if the sequence is done).

const iterator = {
next: () => ({
value: any,
done: boolean

Learn more about the iterator protocol.

Generators have additional behavior: they are a specific kind of iterator, returned by a generator function. When the iterator’s next() method is called, the generator function will execute until it reaches one of the following:

  • yield keyword (pauses the execution)
  • return statement (ends the execution)
  • end of the generator function (ends the execution)
  • throw keyword (throws an exception)

Here’s an example (with throw omitted for simplicity):

function* generatorFunc() {
yield 1 + 1;
return 2 + 2;
}// 1.
const generatorObj = generatorFunc();// 2.;
// returns { value: 2, done: false };// 3.;
// returns { value: 4, done: true };

View code in a jsfiddle

Let’s break down what’s happening:

  1. The generator is created
  2. next() is called for the first time:
  • The generator function evaluates up to the first yield, and then pauses
  • value is the result of the expression following yield
  • done is false because we haven’t reached a return statement or the end of the generator function

3. next() is called for a second time:

  • The generator function evaluation resumes
  • The return statement is reached
  • value is the result of the return statement
  • done is true, and the generator object has been consumed

The sequence of values can also be retrieved without calling next() explicitly, using array destructuring, the spread operator, or a simple for loop:

function* generatorFunc() {
yield 1 + 1;
yield 1 + 2;

return 2 + 2;
}const [a, b, c] = generatorFunc();
// a = 2, b = 3, c = undefinedconst values = [...generatorFunc()];
// values = [2, 3];const vals = [];
for (const val of generatorFunc()) {
// vals = [2, 3]

View code in a jsfiddle

One important note here is that these three ways of retrieving values from a generator only take into account the yield expressions, ignoring the value from the return statement.

Consuming data

So far we’ve looked at how generators passively generate a sequence of values; now, let’s focus on how they take in data. Most standard iterators cannot accept arguments (e.g. array iterators or set iterators), but generators can, by passing an argument to next().

function* generatorFunc() {
const a = yield 1 + 1;
const b = yield 1 + 2;

return 2 + 2;
}const generatorObj = generatorFunc();// 1.‘value 1’);
// returns { value: 2, done: false }// 2.‘value 2’);
// returns { value: 3, done: false }
// a = ‘value 2’// 3.;
// returns { value: 4, done: true}
// b = undefined

View code in a jsfiddle

Let’s break down the order of execution in a more granular way. We’ll start by focusing on the value of the variables assigned to the yield expression, and the value from the iterator response returned from next():

  1. next() is called for the first time, with an argument of 'value 1'
  • It reaches the first yield and pauses
  • The value returned by next() is the result of the expression following the first yield

2. next() is called for the second time, with an argument of 'value 2'

  • The argument provides the value of the constant assigned to the first yield statement (therefore a = 'value 2')
  • It reaches the second yield and pauses
  • The value returned by next() is the result of the expression following the second yield

3. next() is called for the second time, with no argument

  • There is no argument to provide the value of the constant assigned to the second yield statement (therefore b = undefined)
  • It reaches the return statement and ends
  • The value returned by next() is the result of the return statement

The most important thing to grasp here is that the argument to next() provides the value for the yield that had previously paused execution of the generator function. The argument passed to the first next() call is ignored.


Diagram of the input and output of a generator. All information included below.
The input and output of a generator.

Here’s a quick summary of the main takeaways from this post.


  • pause with yield and restart with next()
  • return a new value each time the function pauses or ends
  • set each return value based on the expression following the yield that paused the function
  • take in data through arguments passed to next()
  • set the value of the variable assigned to a yield statement based on the arguments passed to the next() call that restarted the function

I hope you’ve enjoyed this quick dive into generators! If you want to dig in deeper, I recommend reading the Generators chapter of ‘Exploring ES6’ by Axel Rauschmayer, which was very helpful in writing this article. If you want to see generators in use, redux-saga is definitely worth checking out as well.

Let me know in the comments how you’ve used generators, or if you have any questions!

About the author

Alice Kallaugher

Alice is a Senior Software Engineer at Giant Machines.


Stay in the loop

Keep up to date with our newest products and all the latest
in technology and design.
Keep up to date with our newest products
and all the latest
in technology and design.

Other blog posts

Building With ChatGPT & LLMs: Two Common Pitfalls & One Way To Win

What does it mean for your organization? Should AI materially change your headcount projections? Could AI help your team work more effectively and efficiently? Most importantly, is the AI good enough? Should you trust it?

That’s what Giant Machines aimed to figure out when we launched our own AI/ML project in early 2023.

The Opportunity Gap and its $160 Billion Impact

At Giant Machines, we believe making tech that matters includes creating opportunities, cultivating talent, and making spaces for everyone.

Improve Your Debugging Approach for Better Software Applications (& Sounder Sleep 😴)

In the process of developing applications, bugs will inevitably be introduced (“I code, therefore I create bugs” ~ Descartes, probably). Bugs can be introduced for a variety of reasons, such as logical errors, misunderstanding of requirements, lack of tests, tight deadlines, or something as simple as having an off day as a fallible human. However, knowing that bugs are inevitable, we can arm ourselves with tools to quickly identify and address software bugs before they are released in the wild.


Learn more about us here at Giant Machines and how you can work with us.

What we do

We leverage best-in-class talent to create leading edge digital solutions.


Know your next move


Develop beautiful products


Enrich your tech knowledge

Our work

Learn more about our partnerships and collaborations.

Our perspective

Stay up to date with the latest in technology and design.