# What Are Generators in JS For?

Generators aren't a recent JavaScript API, but they're still not very well known. Let's learn what you can use generators for and how you can take your skills to another level with this feature

- URL: https://blog.lsantos.dev/en/what-are-generators-in-js-for/
- Published: 2023-01-05
- Updated: 2026-07-16
- Section: javascript
- Tags: javascript, ecmascript
- Language: en
- Author: Lucas Santos

---
Two of the most interesting, and also most complex, structures in JavaScript are **iterators** and **generators**. Neither of these structures is new, in fact I wrote about both [generators](https://medium.com/trainingcenter/javascript-entendendo-generators-408cbce9aee) and [iterators](https://medium.com/trainingcenter/iterators-em-javascript-880adef14495) back in 2017, but even though both structures (mainly iterators) are heavily used in several frameworks and even in core constructs of the language itself, like Promises and our beloved async/await, generators still aren't very well known or widely used.

This article is exactly for that! You're going to understand how generators can be used in your applications in a more practical way!

## What are generators?

First, let's do a quick recap of what generators are. They're a low-level construct, generally used to build other tools. A generator is a function that returns a _generator object_, which is an object that implements the `iterable` protocol, meaning it has a `Symbol.iterator` that's used to run loops.

The difference is that a _generator function_ returns a special kind of iterator that can suspend its own execution, while keeping its own state and internal context. A generator is declared with a `*` in front of a function, like this:

```js
function *generator() {
    yield 1
    yield 2
    yield 3
}
```

Or like this:

```js
function* generator() {
    yield 1
    yield 2
    yield 3
}   
```

Both are the same thing, and they can be used interchangeably. These two generators are returning an iterator, which means you can call `.next()` on each of these iterators and, when it runs, the value after `yield` gets returned on each call, for example:

```js
function* generator () {
    yield 1
    yield 2
    yield 3
}

const g = generator()
console.log(g.next()) // 1
console.log(g.next()) // 2
console.log(g.next()) // 3
```

We can also use generators with the _spread_ operator, for example:

```js
function* generator () {
    yield 1
    yield 2
    yield 3
}

const g = generator()
console.log([...g]) // [1, 2, 3]
```

But I won't go on for too long here. If you want to know what generators are, I recommend reading [the article I mentioned](https://medium.com/trainingcenter/javascript-entendendo-generators-408cbce9aee), which will give you a pretty solid base for what all of this means.

## Uses for generators

Enough understanding what generators are, let's talk about the reason they exist!

### Lazy iterators

This is probably the most common use for a generator. When I said generators are a special case of iterators, I also said they have a very unusual trait. They can **pause** their own execution.

What does that mean? Basically, calls to a generator, whether through a loop like `for .. of` or through `.next()`, only run at that specific moment, for example:

```js
function* jsFacts() {
    yield 'Linguagem mais presente na Web'
    yield 'Criada em 1995 por Brendan Eich'
    yield 'Pode ser usada no backend com Node, Deno e outros'
}

for (let fact of jsFacts()) {
    console.log(`JS: ${fact}`)
}

// JS: Linguagem mais presente na Web
// JS: Criada em 1995 por Brendan Eich
// JS: Pode ser usada no backend com Node, Deno e outros
```

What's happening here is that the strings don't exist until the next item is called. While that might seem kind of pointless for strings, generators as lazy iterators can be widely used to build `recordSets`, which are structures that fetch data, for example, from a database, and instead of returning all the values at once, return them one by one so we don't fill up memory.

For example, let's imagine we have a database with terabytes of information. We want to grab all the data, but we don't want it all at once, so we can build a _lazy iterator_ that calls the database to return only one value at a time:

```js
const linha = [{
  nome: 'Alan Turing', 
  id: 1,
  idade: 42,
  titulo: 'Pai da computação'
}, {
  nome: 'Ada Lovelace',
  id: 2,
  idade: 36,
  titulo: 'Primeira programadora'
}, {
  nome: 'Grace Hopper',
  id: 3,
  idade: 85,
  titulo: 'Inventora do compilador'
}]

const findInDatabase = (skip, limit) => {
  return linha.slice(skip, skip + limit)
}

function* recordSet() {
  let skip = 0
  const limit = 1
  let currentRecord = findInDatabase(skip, limit)

  while (currentRecord.length > 0) {
    skip += limit
    yield currentRecord[0]
    currentRecord = findInDatabase(skip, limit)
  }
}
```

If we pay attention to what we're doing, we'll see we're defining an internal state that has a `skip` variable, which is the amount of records we want to skip in the array (our stand-in database). We run the first iteration and save the current record in `currentRecord`, which is another part of the internal state. Then we can build a loop that checks whether the result we got is valid, meaning whether there are still records in the database. If so, we add the limit of data we want to `skip` (in this case just 1 at a time), return the current result, and then pause execution.

Every time we start our generator with:

```js
const records = recordSet()
```

We run all the code up to `yield currentRecord[0]`, then, when we do:

```js
const records = recordSet()
console.log(records.next()) // {value: { nome: 'Alan Turing', id: 1, idade: 42, titulo: 'Pai da computação' }, done: false }
```

That's when we actually run our generator and get the value coming from `yield`, then we run the loop again until the next `yield`.

We can also use another variation of the generator so we don't have internal state, and define the stop condition inside the loop instead, like this:

```js
function* recordSet() {
  let skip = 0
  const limit = 1

  while (record.length > 0) {
    const record = findInDatabase(skip, limit)
    if (record.length === 0) return
    skip += limit
    yield record[0]
  }
}
```

The values we get from `next` are objects of the shape `{value: any, done: boolean}`, which come from the iterator we're accessing.

### Infinite ranges

Another thing we can do is create an infinite counter:

```js
function* infiniteSequence() {
  var i = 0;
  while (true) {
    yield i++;
  }
}
```

Which might not seem like much, but it's a powerful tool for global monitoring. For instance, if we want to count how many promises an application created over its lifetime, we can do something like this:

```js
function* sequence() {
  var i = 1
  while (true) {
    yield i++
  }
}

const promisesCreatedCounter = sequence()
let promisesCreatedTotal = 0

const proxyPromise = new Proxy(Promise, {
  get(target, prop) {
    if (prop === 'prototype') {
      promisesCreatedTotal = promisesCreatedCounter.next().value
    }
    return target[prop]
  }
})

const p = new proxyPromise((resolve) => {
  resolve('done')
})

p.then(() => {
  console.log('Promise resolved')
})

console.log('Promises created: ' + promisesCreatedTotal) // Promises Created: 1
```

On top of that, every instance of a sequence like this is unique, which means you can reuse the same counter in several different places and it will always start from 1.

### Utility functions

As I said before, it's possible to build many tools on top of generators, because they're a low-level tool. Some of the functions we can build with them are, for example:

#### take

A function that, given an iterable, grabs `n` elements from it and returns them:

```js
const take = (n) => function*(iteravel) {
    let i = 0
    for (let elemento of iteravel) {
        if (i >= n) return
        yield elemento
        i++
    }
}
```

If we have an array like `[1,2,3,4]` and we run `take(4)([1,2,3,4,5,6])` we get `[1,2,3,4]`.

#### repeat

A variation of the sequence function where we get the same value repeated infinitely:

```js
function* repeat(valor) {
    while (true) {
        yield valor
    }
}
```

#### scan

We can define a function similar to `Array.prototype.reduce`, except instead of a single final value, we get each intermediate step of the values as an array:

```js
function* scan(reducer, valorInicial, iteravel) {
  let resultado = valorInicial;
  yield resultado;
  for (const atual of iteravel) {
    resultado = reducer(resultado, atual);
    yield resultado;
  }
}
```

And plenty of others. The point is that these functions might not look like much on their own, but they let us build more complex functions and sequences when combined. And that's what generators are about, building small functions that can serve a bigger purpose.

> By the way, a lot of these functions are described in a proposal I'll write about here at some point, called [Iterator Helpers](\<https://www.proposals.es/proposals/Iterator helpers>).

## Conclusion

Generators will probably always be underused because they have very specific use cases, but it's always worth understanding that these tools exist and that you can take advantage of them.

Keep an eye out for the next articles about Iterators, and if you don't want to miss any of the content I put out, subscribe to the Newsletter here!

https://news.lsantos.dev
