Iterators

Iterator itself is not a TypeScript or ES6 feature, Iterator is a
Behavioral Design Pattern common for Object oriented programming languages.
It is, generally, an object which implements the following interface:

  1. interface Iterator<T> {
  2. next(value?: any): IteratorResult<T>;
  3. return?(value?: any): IteratorResult<T>;
  4. throw?(e?: any): IteratorResult<T>;
  5. }

This interface allows to retrieve a value from some collection or sequence
which belongs to the object.

The IteratorResult is simply a value+done pair:

  1. interface IteratorResult<T> {
  2. done: boolean;
  3. value: T;
  4. }

Imagine that there’s an object of some frame, which includes the list of
components of which this frame consists. With Iterator interface it is possible
to retrieve components from this frame object like below:

  1. class Component {
  2. constructor (public name: string) {}
  3. }
  4. class Frame implements Iterator<Component> {
  5. private pointer = 0;
  6. constructor(public name: string, public components: Component[]) {}
  7. public next(): IteratorResult<Component> {
  8. if (this.pointer < this.components.length) {
  9. return {
  10. done: false,
  11. value: this.components[this.pointer++]
  12. }
  13. } else {
  14. return {
  15. done: true
  16. }
  17. }
  18. }
  19. }
  20. let frame = new Frame("Door", [new Component("top"), new Component("bottom"), new Component("left"), new Component("right")]);
  21. let iteratorResult1 = frame.next(); //{ done: false, value: Component { name: 'top' } }
  22. let iteratorResult2 = frame.next(); //{ done: false, value: Component { name: 'bottom' } }
  23. let iteratorResult3 = frame.next(); //{ done: false, value: Component { name: 'left' } }
  24. let iteratorResult4 = frame.next(); //{ done: false, value: Component { name: 'right' } }
  25. let iteratorResult5 = frame.next(); //{ done: true }
  26. //It is possible to access the value of iterator result via the value property:
  27. let component = iteratorResult1.value; //Component { name: 'top' }

Again. Iterator itself is not a TypeScript feature, this code could work without
implementing Iterator and IteratorResult interfaces explicitly.
However it is very helpful to use these common
ES6 interfaces for code consistency.

Ok, Nice, but could be more helpful. ES6 defines the iterable protocol
which includes [Symbol.iterator] symbol if Iterable interface implemented:

  1. //...
  2. class Frame implements Iterable<Component> {
  3. constructor(public name: string, public components: Component[]) {}
  4. [Symbol.iterator]() {
  5. let pointer = 0;
  6. let components = this.components;
  7. return {
  8. next(): IteratorResult<Component> {
  9. if (pointer < components.length) {
  10. return {
  11. done: false,
  12. value: components[pointer++]
  13. }
  14. } else {
  15. return {
  16. done: true,
  17. value: null
  18. }
  19. }
  20. }
  21. }
  22. }
  23. }
  24. let frame = new Frame("Door", [new Component("top"), new Component("bottom"), new Component("left"), new Component("right")]);
  25. for (let cmp of frame) {
  26. console.log(cmp);
  27. }

Unfortunately frame.next() won’t work with this pattern and it also looks
a bit clunky. IterableIterator interface to the rescue!

  1. //...
  2. class Frame implements IterableIterator<Component> {
  3. private pointer = 0;
  4. constructor(public name: string, public components: Component[]) {}
  5. public next(): IteratorResult<Component> {
  6. if (this.pointer < this.components.length) {
  7. return {
  8. done: false,
  9. value: this.components[this.pointer++]
  10. }
  11. } else {
  12. return {
  13. done: true,
  14. value: null
  15. }
  16. }
  17. }
  18. [Symbol.iterator](): IterableIterator<Component> {
  19. return this;
  20. }
  21. }
  22. //...

Both frame.next() and for cycle now work fine with IterableIterator interface.

Iterator does not have to iterate a finite value.
The typical example is a Fibonacci sequence:

  1. class Fib implements IterableIterator<number> {
  2. protected fn1 = 0;
  3. protected fn2 = 1;
  4. constructor(protected maxValue?: number) {}
  5. public next(): IteratorResult<number> {
  6. var current = this.fn1;
  7. this.fn1 = this.fn2;
  8. this.fn2 = current + this.fn1;
  9. if (this.maxValue != null && current >= this.maxValue) {
  10. return {
  11. done: true,
  12. value: null
  13. }
  14. }
  15. return {
  16. done: false,
  17. value: current
  18. }
  19. }
  20. [Symbol.iterator](): IterableIterator<number> {
  21. return this;
  22. }
  23. }
  24. let fib = new Fib();
  25. fib.next() //{ done: false, value: 0 }
  26. fib.next() //{ done: false, value: 1 }
  27. fib.next() //{ done: false, value: 1 }
  28. fib.next() //{ done: false, value: 2 }
  29. fib.next() //{ done: false, value: 3 }
  30. fib.next() //{ done: false, value: 5 }
  31. let fibMax50 = new Fib(50);
  32. console.log(Array.from(fibMax50)); // [ 0, 1, 1, 2, 3, 5, 8, 13, 21, 34 ]
  33. let fibMax21 = new Fib(21);
  34. for(let num of fibMax21) {
  35. console.log(num); //Prints fibonacci sequence 0 to 21
  36. }

Building code with iterators for ES5 target

Code examples above require ES6 target, however it could work
with ES5 target as well if target JS engine supports Symbol.iterator.
This can be achieved by using ES6 lib with ES5 target
(add es6.d.ts to your project) to make it compile.
Compiled code should work in node 4+, Google Chrome and in some other browsers.