Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

ES6 Iterator in Depth

DZone's Guide to

ES6 Iterator in Depth

Explore the new functionality available in ES6 with iterators with some practical examples

· Web Dev Zone
Free Resource

Add user login and MFA to your next project in minutes. Create a free Okta developer account, drop in one of our SDKs to your application and get back to building.

Iterator 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 interfaces

  • 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.

data consumers - data sources

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.

Launch your application faster with Okta’s user management API. Register today for the free forever developer edition!

Topics:
data sources ,iterator ,data ,sources ,iterable ,data structures ,interface

Published at DZone with permission of Can Ho, DZone MVB. See the original article here.

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}