Iterators are data structures that allow you process a sequence of elements more efficiently. This tutorial will help you learn about what JavaScript iterators are and how to find out if something is an iterator. You will also learn about existing types of iterators, their consumers and how to work with them.
A quick introduction to JavaScript iterators
An iterator is type of a data structure. It is a collection of elements. Two examples of such collections are strings and arrays. The first, string is a collection of characters. The second, array is a collection items. That said, not every collection is an iterator.
For a collection to be an iterator, it has to conform to specification of Iterable
interface. This interface says that the collections must implement a method Symbol.iterator. This means that this method has to be available on the collection object. This method when invoked returns an Iterator object.
This Iterator
object contains one method called next()
. This method returns an object with two properties, value
and done
. The value
property contains item from the collection that is currently in the iteration sequence. The done
is a boolean that says if the iteration is at the end.
We can use this next()
method to iterate over the collection in a controlled manner. Unlike with for loop or map method where we can't stop and resume the iteration, JavaScript iterators allow us to do this. They allow us to get each item in the collection, to resume the iteration, when we want.
// Create an array:
const list = [1, 3, 5, 7, 9]
// Create iterator for "list" array:
const listIterator = list[Symbol.iterator]()
// Log the iterator object:
console.log(listIterator)
// Output:
// Iterator [Array Iterator] { __proto__: { next: ƒ next() } }
// Try next() method:
listIterator.next()
// Output:
// { value: 1, done: false }
listIterator.next()
// Output:
// { value: 3, done: false }
listIterator.next()
// Output:
// { value: 5, done: false }
listIterator.next()
// Output:
// { value: 7, done: false }
listIterator.next()
// Output:
// { value: 9, done: false }
listIterator.next()
// Output:
// { value: undefined, done: true }
Types of JavaScript iterators
There are currently four data types in JavaScript that are iterable. These types that are iterable are strings, arrays, maps and sets.
Strings
The idea that string may be iterable may sound weird. However, it is true. We can validate this with a simple test. If string is an iterable, it should have the Symbol.iterator method. If we invoke this method, we should get the iterator object. With this object, we should also get the next()
method.
// Create a string and iterator object for it:
const str = 'It worked'
const strIterator = str[Symbol.iterator]()
// Iterate over individual characters with next():
strIterator.next()
// Output:
// { value: 'I', done: false }
strIterator.next()
// Output:
// { value: 't', done: false }
strIterator.next()
// Output:
// { value: ' ', done: false }
strIterator.next()
// Output:
// { value: 'w', done: false }
// Iterate over the string using for...of loop:
for (const char of str) {
console.log(char);
}
// Output:
// 'I'
// 't'
// ' '
// 'w'
// 'o'
// 'r'
// 'k'
// 'e'
// 'd'
Arrays
Arrays are the second type that is iterable. Again, we can test this by using the Symbol.iterator method and for...of
loop.
// Create an array and iterator object for it:
const names = ['Josh', 'Howard', 'Lucy', 'Victoria']
const namesIterator = names[Symbol.iterator]()
// Iterate over individual items with next():
namesIterator.next()
// Output:
// { value: 'Josh', done: false }
namesIterator.next()
// Output:
// { value: 'Howard', done: false }
namesIterator.next()
// Output:
// { value: 'Lucy', done: false }
namesIterator.next()
// Output:
// { value: 'Victoria', done: false }
// Iterate over the array using for...of loop:
for (const name of names) {
console.log(name);
}
// Output:
'Josh'
'Howard'
'Lucy'
'Victoria'
Maps
The third iterable type is the Map object. With Maps, we can iterate over their key and value pairs.
// Create a Map and iterator object for it:
const map = new Map()
map.set('name', 'Tony Stark')
map.set('alias', 'Iron Man')
map.set('reality', 'Earth-616')
map.set('education', 'MIT')
const mapIterator = map[Symbol.iterator]()
// Iterate over individual items with next():
mapIterator.next()
// Output:
// { value: [ 'name', 'Tony Stark' ], done: false }
mapIterator.next()
// Output:
// { value: [ 'alias', 'Iron Man' ], done: false }
mapIterator.next()
// Output:
// { value: [ 'reality', 'Earth-616' ], done: false }
mapIterator.next()
// Output:
// { value: [ 'education', 'MIT' ], done: false }
// Iterate over the Map using for...of loop:
for (const [key, value] of map) {
console.log(`${key}: ${value}`);
}
// Output:
'name: Tony Stark'
'alias: Iron Man'
'reality: Earth-616'
'education: MIT'
Sets
The fourth and last iterable type is the Set object. Set
objects are similar to arrays. The main difference between a Set
and an array is that Set
doesn't allow duplicate values. When you try to add duplicate value, Set
will keep only the first occurrence of the value and ignore the second.
// Create a map and iterator object for it:
const set = new Set(['north', 'east', 'west', 'south'])
const setIterator = set[Symbol.iterator]()
// Iterate over individual items with next():
setIterator.next()
// Output:
// { value: 'north', done: false }
setIterator.next()
// Output:
// { value: 'east', done: false }
setIterator.next()
// Output:
// { value: 'west', done: false }
setIterator.next()
// Output:
// { value: 'south', done: false }
// Iterate over the Set using for...of loop:
for (const item of set) {
console.log(item);
}
// Output:
'north'
'east'
'west'
'south'
Iterable consumers and working with iterable types
These were the four iterable types we can work with in JavaScript. The next question is, how can we use them, or consume them. There are four popular consumers that allow us to "consume" iterables. These consumers are: for...of
loop, destructuring assignment, spread operator and Array.from()
.
for...of loop
The first way to iterate over JavaScript iterators is by using the for...of
loop. The downside of for...of
loop is that it doesn't give us much of the control over the iteration. However, if all we need is to retrieve each item in the collection, it will do the job.
// Array:
const numbers = [2, 4, 6]
for (const num of numbers) {
console.log(num)
}
// Output:
// 2
// 4
// 6
// String:
const word = 'Root'
for (const char of word) {
console.log(char)
}
// Output:
// 'R'
// 'o'
// 'o'
// 't'
// Map:
const map = new Map([
['name', 'Joe'],
['age', 33],
])
for (const [key, val] of map) {
console.log(`${key}: ${val}`)
}
// Output:
// 'name: Joe'
// 'age: 33'
// Set:
const set = new Set(['C++', 'Assembly', 'JavaScript', 'C++'])
for (const language of set) {
console.log(language)
}
// Output:
// 'C++'
// 'Assembly'
// 'JavaScript'
Destructuring assignment
One quick way to retrieve items from JavaScript iterators is by using destructuring assignment. With destructuring, we can retrieve any item we need, single item at the time or multiple items at once.
// Array:
const genres = ['rock', 'hip hop', 'r&b', 'metal', 'soul']
// Destructuring assignment:
const [ first, second, ...rest ] = genres
console.log(first)
// Output:
// 'rock'
console.log(second)
// Output:
// 'hip hop'
console.log(rest)
// Output:
// [ 'r&b', 'metal', 'soul' ]
// String:
const word = 'Recursion'
// Destructuring assignment:
const [first, second, third, ...rest] = word
console.log(first)
// Output:
// 'R'
console.log(second)
// Output:
// 'e'
console.log(third)
// Output:
// 'c'
console.log(rest)
// Output:
// [ 'u', 'r', 's', 'i', 'o', 'n' ]
// Map:
const map = new Map([
['water', 'fire'],
['white', 'black'],
['left', 'right'],
])
// Destructuring assignment:
const [start, middle, end] = map
console.log(start)
// Output:
// [ 'water', 'fire' ]
console.log(middle)
// Output:
// [ 'white', 'black' ]
console.log(end)
// Output:
// [ 'left', 'right' ]
// Set:
const set = new Set([1, 33, 777, 9999])
// Destructuring assignment:
const [ first, second, ...rest ] = set
console.log(first)
// Output:
// 1
console.log(second)
// Output:
// 33
console.log(rest)
// Output:
// [ 777, 9999 ]
Spread operator
Spread operator offers a quick and simple way to iterate over iterable type and transform it into an array. This will not be useful when working with arrays. It can still be handy when dealing with maps, strings and also sets.
// String:
const word = 'closure'
// Spread:
const wordSpread = [...word]
console.log(wordSpread)
// Output:
// [
// 'c', 'l', 'o',
// 's', 'u', 'r',
// 'e'
// ]
// Map:
const map = new Map([
['fruit', 'apple'],
['thatGreenThing', 'kale'],
['beverage', 'tea']
])
// Spread:
const mapSpread = [...map]
console.log(mapSpread)
// Output:
// [
// [ 'fruit', 'apple' ],
// [ 'thatGreenThing', 'kale' ],
// [ 'beverage', 'tea' ]
// ]
// Set:
const set = new Set(['Halo', 'Quake', 'NFS', 'C&C'])
// Spread:
const setSpread = [...set]
console.log(setSpread)
// Output:
// [ 'Halo', 'Quake', 'NFS', 'C&C' ]
Array.from()
Along with spread operator, Array.from()
also allows us to transform any iterable to an array. All we have to do is to pass the iterable as an argument to the from()
method.
// String:
const word = 'iterable'
// Spread:
const wordArray = Array.from(word)
console.log(wordArray)
// Output:
// [
// 'i', 't', 'e',
// 'r', 'a', 'b',
// 'l', 'e'
// ]
// Map:
const map = new Map([
[1, 1],
[2, 10],
[3, 11],
[4, 100]
])
// Spread:
const mapArray = Array.from(map)
console.log(mapArray)
// Output:
// [ [ 1, 1 ], [ 2, 10 ], [ 3, 11 ], [ 4, 100 ] ]
// Set:
const set = new Set(['BTC', 'ETH', 'ADA', 'EOS'])
// Spread:
const setArray = [...set]
console.log(setArray)
// Output:
// [ 'BTC', 'ETH', 'ADA', 'EOS' ]
Conclusion: A simple introduction to JavaScript iterators
Iterators and iterables can be handy when we need collection over which we can iterate in a controlled fashion. In this tutorial, we've looked at what JavaScript iterators are, what types of iterators are available and how work with them, using the for...of
loop, destructuring assignment, spread operator and Array.from()
.
Top comments (1)
I'm still very salty that Svelte doesn't really support iterators in its
#each
loops; I constantly find myself writing things like{#each [...someSet] as item}
when it could be so much simpler :DOh and it gets worse when you have methods like
Map.prototype.values()
, where the extra function call makes it seem even more cluttered:{#each [...someMap.values()] as value}