DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Please enter at least three characters to search
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Zones

Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks

Apache Cassandra combines the benefits of major NoSQL databases to support data management needs not covered by traditional RDBMS vendors.

The software you build is only as secure as the code that powers it. Learn how malicious code creeps into your software supply chain.

Generative AI has transformed nearly every industry. How can you leverage GenAI to improve your productivity and efficiency?

Trending

  • Immutable Secrets Management: A Zero-Trust Approach to Sensitive Data in Containers
  • Simplifying Multi-LLM Integration With KubeMQ
  • Integrating Model Context Protocol (MCP) With Microsoft Copilot Studio AI Agents
  • How to Ensure Cross-Time Zone Data Integrity and Consistency in Global Data Pipelines

How JavaScript Generator Functions Work

Let's look at how generator functions work in JavaScript.

By 
Johnny Simpson user avatar
Johnny Simpson
DZone Core CORE ·
Apr. 04, 22 · Tutorial
Likes (9)
Comment
Save
Tweet
Share
6.5K Views

Join the DZone community and get the full member experience.

Join For Free

Generators are functions that can stop halfway through execution, and then continue from where they stopped when you call them again. Even though they act differently from regular functions, they are still callable. Let's look at how they work.

How Generator Functions Work in JavaScript

Let's look at a normal function first. In this example, we run a while loop up to 100, and return its value:

JavaScript
 
function generator() {
    let current = 0;
    while(current < 100) {
        current = current + 1;
    }
    return current;
}

console.log(generator);

If we run this, we will get a return value of 100. If we were to move the return statement into the while() look, it would return 1 instead. In fact, every time we run it, it will return 1.

Use Cases for a Generator Function

This is great for some use cases - but in others, it's not so useful:

  • Imagine you didn't need to go all the way to 100 every time - some users only needed to go to 55. In this case, this function is quite inefficient, since it does more than what is needed.
  • Or maybe we need to pause the while loop when a user does a certain action - with this function, we can't do that. In both cases, a function that could stop when we wanted it to, is more memory efficient.
  • That's where generator functions come in. Instead of writing return, we can use yield, to pause the iteration and return a single value. It also remembers where we left off so that we can continue iterating through each item.

Let's convert our function to a generator:

JavaScript
 
function* generator() {
    let current = 0;
    while(current < 100) {
        current = current + 1;
        yield current;
    }
}

let runGenerator = generator();
console.log(runGenerator.next()); // Returns { value: 1, done: false }
console.log(runGenerator.next()); // Returns { value: 2, done: false }
console.log(runGenerator.next()); // Returns { value: 3, done: false }
console.log(runGenerator.next()); // Returns { value: 4, done: false }
console.log(runGenerator.next()); // Returns { value: 5, done: false }

We've introduced two new concepts to our function: first, we've written function* instead of a function, and when we ran our function, we used a method called next().

Function* and Yield

function* tells JavaScript that this function is a generator. When we define a generator, we have to use the yield keyword, to return any values from it. We've used a while loop above and that ultimately defines 100 yield statements, but we can also manually type yield multiple times, and each time the code will go to the next yield:

JavaScript
 
function* generator() {
    yield 1;
    yield 2;
    yield 3;
}

let runGenerator = generator();
console.log(runGenerator.next()); // Returns { value: 1, done: false }
console.log(runGenerator.next()); // Returns { value: 2, done: false }
console.log(runGenerator.next()); // Returns { value: 3, done: false }

yield can also return objects and arrays, like so:

JavaScript
 
function* generator() {
    let current = 0;
    while(current < 100) {
        let previous = current;
        current = current + 1;
        yield [ current, previous ]
    }
}

let runGenerator = generator();
console.log(runGenerator);
console.log(runGenerator.next()); // Returns { value: [ 1, 0 ], done: false }
console.log(runGenerator.next()); // Returns { value: [ 2, 1 ], done: false }
console.log(runGenerator.next()); // Returns { value: [ 3, 2 ], done: false }
console.log(runGenerator.next()); // Returns { value: [ 4, 3 ], done: false }
console.log(runGenerator.next()); // Returns { value: [ 5, 4 ], done: false }

next()

Any generator function you run will have a next() method attached to it. If you try to run the generator function and console log it without next() you'll get the message generator { <suspended> }.

The next() method returns some data on the current state of the generator, in the form { value: value, done: status }, where value is the current value the generator is returning, and status is whether or not it's completed.

If we had a smaller generator, where we only checked for numbers below 5, done would eventually return true:

JavaScript
 
function* generator() {
    let current = 0;
    while(current < 5) {
        let previous = current;
        current = current + 1;
        yield [ current, previous ]
    }
}

let runGenerator = generator();
console.log(runGenerator);
console.log(runGenerator.next()); // Returns { value: [ 1, 0 ], done: false }
console.log(runGenerator.next()); // Returns { value: [ 2, 1 ], done: false }
console.log(runGenerator.next()); // Returns { value: [ 3, 2 ], done: false }
console.log(runGenerator.next()); // Returns { value: [ 4, 3 ], done: false }
console.log(runGenerator.next()); // Returns { value: [ 5, 4 ], done: false }
console.log(runGenerator.next()); // Returns { value: undefined, done: true }

This lets us easily check if a generator is complete or not.

Changing the Yield Value

If we pass a value to next(), it uses that value in the place of an yield expression. For example, consider the following:

JavaScript
 
function* generator() {
    let current = 0;
    while(current < 5) {
        current = yield current + 1;
    }
}

let runGenerator = generator();
console.log(runGenerator.next(3)); // Returns { value: 1, done: false }
console.log(runGenerator.next(3)); // Returns { value: 4, done: false }
console.log(runGenerator.next(3)); // Returns { value: 4, done: false }
console.log(runGenerator.next(3)); // Returns { value: 4, done: false }

Interestingly, next() only passes this value to yield after the first run. So in the first run, we get the value current + 1. After that, the yield current is replaced by 3 - so every value after is equivalent to 4. This is quite useful for selecting specific items in an iteration.

Consider Another Example

JavaScript
 
function* generator() {
    yield yield yield 5 * 2
}

let runGenerator = generator();
console.log(runGenerator.next(3)); // Returns { value: 10, done: false }
console.log(runGenerator.next(3)); // Returns { value: 3, done: false }
console.log(runGenerator.next(3)); // Returns { value: 3, done: false }
console.log(runGenerator.next(3)); // Returns { value: undefined, done: false }

In this example, the first number runs fine, as before. Then after yield 5 * 2 is replaced by our next() value, 3, meaning yield yield yield 5 * 2 becomes yield yield 3.

After that, we replace it again, so yield yield 3 becomes yield 3.

Finally, we replace it again - yield 3 becomes 3. Since we have no more yields left

Generators Are Iterable

Generators differ from normal functions and objects in that they are iterable. That means they can be used with for(... of ...), allowing us to iterate over them and further control when and where we stop using them. For example, to iterate over each item in an iterator, and return only values, we can do this:

For example:

JavaScript
 
function* generator() {
    let current = 0;
    while(current < 5) {
        let previous = current;
        current = current + 1;
        yield [ current, previous ]
    }
}

for(const i of generator()) {
    console.log(i);
}
// console logs: 
// [ 1, 0 ]
// [ 2, 1 ]
// [ 3, 2 ]
// [ 4, 3 ]
// [ 5, 4 ]

Example: Defining an Infinite Data Structure

Since generators only run when we call them, we can define a function that returns numbers up to infinity, but will only generate one when it is called. You can easily see how this could be useful for defining unique user IDs:

JavaScript
 
function* generator() {
    let current = 0;
    while(true) {
        yield ++current;
    }
}

let runGenerator = generator();
console.log(runGenerator.next()); // Returns { value: 1, done: false }
console.log(runGenerator.next()); // Returns { value: 2, done: false }
console.log(runGenerator.next()); // Returns { value: 3, done: false }
console.log(runGenerator.next()); // Returns { value: 4, done: false }

Conclusion

Generator functions provide a great, memory-efficient way to iterate through items, whether that be in a calculation, or from an API. With generators, you can make memory-efficient functions that can be incredibly useful in complex applications.

Published at DZone with permission of Johnny Simpson, DZone MVB. See the original article here.

Opinions expressed by DZone contributors are their own.

Partner Resources

×

Comments
Oops! Something Went Wrong

The likes didn't load as expected. Please refresh the page and try again.

ABOUT US

  • About DZone
  • Support and feedback
  • Community research
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 100
  • Nashville, TN 37211
  • support@dzone.com

Let's be friends:

Likes
There are no likes...yet! 👀
Be the first to like this post!
It looks like you're not logged in.
Sign in to see who liked this post!