ES6 Iterator in Depth
Explore the new functionality available in ES6 with iterators with some practical examples
Join the DZone community and get the full member experience.
Join For FreeIterator is a pattern for pulling data from a data source one at a time. This pattern has been in programming for a long time. In Javascript, this is a new feature in ES6.
Interfaces
Following are interfaces defined in ES6:
- Iterator: This interface defines 3 methods:
- next(): [required] Returns next IteratorResult object
- return(): [optional] Stops iterator and return IteratorResult object
- throw(): [optional] Indicates an error and return IteratorResult object
- IteratorResult: This interface defines following properties:
- value:[optional] Current iteration value, can be any values
- done: Indicates completion status of iterator, can be truthy / falsy values
- Iterable: This interface defines only one method:
- @@iterator(): [required] Returns an Iterator
Many built-in data structures in Javascript implement Iterable interface. As Javascript doesn’t have interfaces, these interfaces are just more of a convention. That said, we can create our own iterators adhering to these interfaces.
Iterability
Looping through a collection and processing each item of the collection is a very common operation. Javascript provides a number of ways for iterating through a collection such as for loop
, map
, filter
, forEach
… ES6 iterator brings the concept of iterability to the language.
It’s nearly impossible (and unwise) for data consumers to support all data sources. The introduction of iterator interfaces solves the problem: Any data source that wants to be consumed by data consumers just need to implement the interface. Data consumers just use the iterator to get the values from data sources they are consuming.
Iterator Consumption
for..of loop
The ES6 for..of
loop consumes a conforming iterable data sources.
Arrays and Typed Arrays are iterable.
let arr = [1, 2, 3];
for(let item of arr) {
console.log(item);//1 2 3
}
String
Strings are also iterables.
for(let c of "hello") {
console.log(c);//h e l l o
}
Map
Maps are iterables over their entries. Each entry is an array [key, value].
const m = new Map();
m.set('a', 'A');
m.set('b', 'B');
for(let pair of m) {
console.log(pair); //['a', 'A']
//['b', 'B']
}
Set
Sets are iterables over their elements.
const set = new Set();
set.add('a');
set.add('b');
set.add('c');
for(let e of set) {
console.log(e); //a
//b
//c
}
Arguments
function test(){
for(let param of arguments){
console.log(param);
}
}
test('a', 'b', 'c'); //a b c
Iterable Computed Data
Some ES6 data structures such as Map, Set, Arrays have following methods that return iterables:
const set = new Set();
set.add('a');
set.add('b');
set.add('c');
for(let e of set.entries()) {
console.log(e); //['a', 'a']
//['b', 'b']
//['c', 'c']
}
const m = new Map();
m.set('a', 'A');
m.set('b', 'B');
for(let pair of m.entries()) {
console.log(pair); //['a', 'A']
//['b', 'B']
}
const set = new Set();
set.add('a');
set.add('b');
set.add('c');
for(let e of set.keys()) {
console.log(e); //['a' ]
//['b']
//['c']
}
const m = new Map();
m.set('a', 'A');
m.set('b', 'B');
for(let pair of m.keys()) {
console.log(pair); //['a']
//['b']
}
const set = new Set();
set.add('a');
set.add('b');
set.add('c');
for(let e of set.values()) {
console.log(e); //['a']
//['b']
//['c']
}
const m = new Map();
m.set('a', 'A');
m.set('b', 'B');
for(let pair of m.values()) {
console.log(pair); //['A']
//['B']
}
Destructuring (via Array Pattern)
Destructuring via Array patterns works with iterables.
const set = new Set();
set.add('a');
set.add('b');
set.add('c');
set.add('d');
let [a, b, ...cd] = set;
//a='a'
//b='b'
//cd=['c', 'd']
const m = new Map();
m.set('a', 'A');
m.set('b', 'B');
let [e1, e2] = m;
//e1: ['a', 'A']
//e2: ['b', 'B']
Spread Operator
Spread operator can be used to insert iterables into an array.
const set = new Set();
set.add('b');
set.add('c');
let items = ['a', ...set, 'd'];//["a", "b", "c", "d"]
And spread operator can be used to convert an iterable to an array
const map = new Map();
map.set('b', 'B');
map.set('c', 'C');
let keys= [...map.keys()];//["b", "c"]
APIs Accepting Iterables
There are APIs accepting iterables:
- Map([iterable]), WeakMap([iterable]), Set([iterable]), WeakSet([iterable])
- Array.from([iterable]), Promise.all([iterable]), Promise.race([iterable])
let set = new Set("abca");
let map = new Map([[1,"a"],[2,"b"],[3,"c"]]);
Custom Iterator
In addition to the built-in iterators, we can create our own. All we need to do is adhere to above interfaces. That means we need to implement a method whose key is [Symbol.iterator]. That method must return an iterator, an object that iterate over items of the iterable.
let idGen = {
[Symbol.iterator](){
return this;
},
next(){
return {value: Math.random(), done: false}
}
}
let count = 0;
for(let id of idGen) {
console.log(id);
if (count > 4) {
break;
}
count++;
}
Array-like objects are not iterable by default
let arrayLikeObj = {
0: 'a',
1: 'b',
2: 'c',
3: 'd',
length: 4
}
for(var e of arrayLikeObj) {
console.log(e);//TypeError: arrayLikeObj[Symbol.iterator] is not a function
}
We can make array-like objects iterable by implementing the method @@iterator:
let arrayLikeObj = {
0: 'a',
1: 'b',
2: 'c',
3: 'd',
length: 4
[Symbol.iterator]: Array.prototype[Symbol.iterator]
}
for(var e of arrayLikeObj) {
console.log(e);
}
//Output:
//a
//b
//c
//d
Optional return(..) and throw(..)
The optional methods return(..)
and throw(..)
are not implemented in built in iterators.return(..)
is a signal to an iterator that the consumer code is complete and will not pull any value from it. This signal can be used to notified the producer to perform clean up step such as closing file handle, database, etc.
throw(..)
is used to signal an exception/error to an iterator. We will take a deep look at this in Generators topic.
In for..of
loops, the termination can be caused by:
- break
- return
- throw
- continue
In these cases, for..of
loops let the iterator know about termination.
var Fib = {
[Symbol.iterator]() {
var n1 = 1, n2 = 1;
return {
next() {
var current = n2;
n2 = n1;
n1 = n1 + current;
return { value: current, done: false };
},
return(v) {
console.log("Fibonacci sequence terminated.");
return { value: v, done: true };
}
}
}
};
for (let n of Fib) {
console.log(n);
if (n > 20) {
break;
}
}
//1
//2
//3
//5
//8
//13
//21
//Fibonacci sequence terminated.
Published at DZone with permission of Can Ho, DZone MVB. See the original article here.
Opinions expressed by DZone contributors are their own.
Comments