- Home
- /
- Tutorials
- /
- JavaScript Tutorial
- /
- JavaScript Lazy Sequences
JavaScript Generators & Iterators
JavaScript Lazy Sequences
A generator can represent a sequence that has no end, because it only computes what is asked for.
An array has to exist in memory all at once; a generator does not.
Values are produced one at a time, only when requested.
Example
Example
javascript
function* naturalNumbers() {
let n = 1;
while (true) {
yield n;
n++;
}
}
const numbers = naturalNumbers();
console.log(numbers.next().value);
console.log(numbers.next().value);
console.log(numbers.next().value);Three values were produced; infinitely more remain unpaused.
Why This Is Safe
The while (true) never actually blocks anything.
Execution pauses at each yield until next() is called again.
You only ever compute as many values as you actually ask for.
Syntax
Syntax
javascript
function* infinite() {
while (true) {
yield next();
}
}Never spread or use for of without a way to stop.
A take Helper
This is the safe way to pull a limited number of values out.
Example
Example
javascript
function* naturalNumbers() {
let n = 1;
while (true) {
yield n;
n++;
}
}
function take(iterable, count) {
const result = [];
const iterator = iterable[Symbol.iterator]();
for (let i = 0; i < count; i++) {
result.push(iterator.next().value);
}
return result;
}
console.log(take(naturalNumbers(), 5).join(","));The output is 1,2,3,4,5.
An Infinite Sequence of Anything
The values do not have to be numbers, or even follow a simple pattern.
Example
Example
javascript
function* cycle(values) {
let i = 0;
while (true) {
yield values[i % values.length];
i++;
}
}
function take(iterable, count) {
const result = [];
const iterator = iterable[Symbol.iterator]();
for (let i = 0; i < count; i++) {
result.push(iterator.next().value);
}
return result;
}
console.log(take(cycle(["a", "b", "c"]), 7).join(","));The output is a,b,c,a,b,c,a.
Never Spread an Infinite Generator
Spreading it, or a plain for of with no break, tries to reach the end.
Since there is no end, this hangs the program.
Example
Example
javascript
function* naturalNumbers() {
let n = 1;
while (true) {
yield n;
n++;
}
}
let first = null;
for (const n of naturalNumbers()) {
first = n;
break;
}
console.log(first);A break is what makes this safe here.
Filtering a Lazy Sequence
A second generator can transform values from the first, still lazily.
Example
Example
javascript
function* naturalNumbers() {
let n = 1;
while (true) {
yield n;
n++;
}
}
function* evensOnly(source) {
for (const n of source) {
if (n % 2 === 0) yield n;
}
}
function take(iterable, count) {
const result = [];
const iterator = iterable[Symbol.iterator]();
for (let i = 0; i < count; i++) {
result.push(iterator.next().value);
}
return result;
}
console.log(take(evensOnly(naturalNumbers()), 4).join(","));The output is 2,4,6,8, computed only as far as needed.
Why Not Just Use a Huge Array
A million-element array uses memory whether or not you need all of it.
A generator uses almost none, because nothing is stored ahead of time.
Example
Example
javascript
function* naturalNumbers() {
let n = 1;
while (true) {
yield n;
n++;
}
}
const iterator = naturalNumbers();
console.log(typeof iterator.next);
console.log(iterator.next().value);Only the current position is kept in memory, nothing more.
Complete Example
Complete Example
html
<!DOCTYPE html>
<html>
<head>
<title>JavaScript Lazy Sequences</title>
</head>
<body>
<h1>Lazy Sequences</h1>
<p id="out"></p>
<script>
function* naturalNumbers() {
let n = 1;
while (true) {
yield n;
n++;
}
}
function take(iterable, count) {
const result = [];
const iterator = iterable[Symbol.iterator]();
for (let i = 0; i < count; i++) {
result.push(iterator.next().value);
}
return result;
}
document.getElementById("out").textContent =
"First 10 numbers: " + take(naturalNumbers(), 10).join(", ");
</script>
</body>
</html>Try It Yourself
Run the above example in the Try It Editor.
Try more values:
Change the count to 20 and see the sequence extend safely.
Important Points
- A generator can represent a sequence with no end.
- Nothing runs away, because execution pauses at each
yield. - A
takehelper safely pulls a limited number of values. - Never spread, or loop without
break, over an infinite generator. - A generator uses far less memory than a large precomputed array.
Conclusion
Lazy sequences are one of the strongest reasons to reach for a generator.
The pattern of generating, then filtering, then taking a limited amount is common in real code.
The one rule to hold onto is: always have a way to stop.
