Introduction
In JavaScript, we have access to many data types (Strings, Arrays, Sets, and Maps) that store collections of data. We also have useful features of the language such as the for…of loop and …spread that help us iterate over them and access each item they contain.
const fruitString = 'Pineapple'
const fruits = ['π', 'π', 'π']
for (const char of fruitString) {
console.log(char)
}
// prints:
// 'P'
// 'i'
// 'n'
// 'e'
// 'a'
// 'p'
// 'p'
// 'l'
// 'e'
for (const fruit of fruits) {
console.log(fruit)
}
// prints:
// 'π'
// 'π'
// 'π'
What do these have in common that makes for…of work with them? Is it possible to create custom objects that work with for…of too?
- They are all iterables
- Yes you can, but first we must learn what exactly is an iterable π€
What is an iterable?
An βiterableβ is any object that meet the following criteria:
- Contains a property with the key Symbol.iterator set to a function
- The iterator function returns an iterator
- The iterator returned is an object with next function
- The iteratorβs next function returns an object with done and value properties
Many built in data types like strings implement this contract, let’s see that in action:
const string = 'abcd'
const stringIterator = string[Symbol.iterator]()
stringIterator.next() // { done: false, value: 'a' }
stringIterator.next() // { done: false, value: 'b' }
stringIterator.next() // { done: false, value: 'c' }
stringIterator.next() // { done: false, value: 'd' }
stringIterator.next() // { done: true, value: undefined }
If you’re not familiar with Symbols, they’re a primitive data type for creating unique values. Weβre required to use the iterator symbol because thatβs what consumers of the iterator (like forβ¦of) will look for.
Each time .next is called, we iterate through the sequence of characters in the string. Once weβve iterated through the entire sequence, the done property comes back as true.
How can I create my own iterable?
Weβve seen how we can use a builtin iterables like strings, but we can also create our own iterables by implementing this contract ourselves:
const iterable = {
[Symbol.iterator]: () => {
const iterator = {
next() {
return { done: false, value: 'π' }
},
}
return iterator
},
}
const iterator = iterable[Symbol.iterator]()
iterator.next() // { done: false, value: 'π' }
iterator.next() // { done: false, value: 'π' }
iterator.next() // { done: false, value: 'π' }
We’ve successfully created an iterable that endlessly gives us pineapples! Technically, we can use the for…of loop on our iterable, but it would create an infinite loop!
If we create a count variable and increment it each time .next is called, we can then return { done: true } after 3 iterations.
const iterable = {
[Symbol.iterator]: () => {
let count = 0
const iterator = {
next() {
count++
if (count > 3) {
return { done: true }
}
return { done: false, value: 'π'.repeat(count) }
},
}
return iterator
},
}
const iterator = iterable[Symbol.iterator]()
iterator.next() // { done: false, value: 'π' }
iterator.next() // { done: false, value: 'ππ' }
iterator.next() // { done: false, value: 'πππ' }
iterator.next() // { done: true }
Iterable Sugar
Now that our iterable ends, we can use a for…of loop to have it do all the work of calling .next for us until the sequence completes.
for (const value of iterable) {
console.log(value)
}
// prints:
// 'π'
// 'ππ'
// 'πππ'
We can also use the spread syntax!
const basket = ['π', ...iterable, 'π'] // ['π', 'π', 'ππ', 'πππ', 'π']
Generators
Making our own custom iterables was pretty neat, but we can also use JavaScript Generators to create custom iterables!
A generator is a special type of function that provides an alternative syntax for creating iterators:
- They must use the function keyword (cannot be an arrow function)
- They must have an asterisk after the function keyword (function* funk() {} or function *funk() {})
- They implicitly return a generator, which follows the same rules as iterators (have next function)
- Values are added to the generator sequence by using the yield syntax
function *fruitGenerator() {
yield 'π'
yield 'π'
yield 'π'
return 'final value'
}
// Cycle through generator with .next
const generator = fruitGenerator()
generator.next() // { done: false, value: 'π' }
generator.next() // { done: false, value: 'π' }
generator.next() // { done: false, value: 'π' }
generator.next() // { done: true, value: 'final value' }
// Since generators are a type of iterator, we can also use for...of and spread with them!
for (const fruit of fruitGenerator()) {
console.log(fruit)
}
// prints:
// 'π'
// 'π'
// 'π'
// Using spread
const basket = ['π', ...fruitGenerator()]
// ['π', 'π', 'π', 'π']
Async Iterables
Iterables are cool, but what happens if we need to perform some asynchronous code to fetch the next value in the sequence? Enter async iterables.
Async iterables are iterables that work with Promises:
- They still require a property for creating an iterator, but this time they use a slightly different key: Symbol.asyncIterator
- That key is set to a function that returns an async iterator
- The async iterator is an object with a .next function
- The async iteatorβs next function returns a Promise
- That promise resolves to an object with done and value properties
// Wait one second and then resolve with requested amount of pineapples
function fetchPineapples(amount) {
return new Promise((resolve) => {
setTimeout(() => {
resolve('π'.repeat(amount))
}, 1000)
})
}
const iterable = {
[Symbol.asyncIterator]: () => {
let count = 0
const asyncIterator = {
async next() {
count++
if (count > 3) {
return { done: true }
}
const pineapples = await fetchPineapples(count)
return { done: false, value: pineapples }
},
}
return asyncIterator
}
}
const asyncIterator = iterable[Symbol.asyncIterator]()
await asyncIterator.next() // waits 1s, then resolves to { done: false, value: 'π' }
await asyncIterator.next() // waits 1s, then resolves to { done: false, value: 'ππ' }
await asyncIterator.next() // waits 1s, then resolves to { done: false, value: 'πππ' }
await asyncIterator.next() // immediately resolves to { done: true }
We can use a for await...of loop with async iterators:
for await (const value of asyncIterator()) {
console.log(value)
}
// waits 1s, then prints 'π'
// waits 1s, then prints 'π'
// waits 1s, then prints 'π'
Async Generators
And of course, there’s a counterpart for performing async code with generators!
An async generator is a generator function that allows you to use async/await to perform asynchronous actions before yielding the next value in the sequence.
async function* fruitGenerator() {
// Pretend these fetchers exist and make some asynchronous call to fetch data
const apple = await fetchApple()
yield apple
const pineapple = await fetchPineapple()
yield pineapple
const banana = await fetchBanana()
yield banana
}
Conclusion
That concludes our walkthrough of iterables in JavaScript. We learned what they are, how to use them, and how to make our own manually or using a generator. We also learned how we can use asynchronous code with iterables through async iterables and async generators. Happy iterating!



