(click anywhere to close)
OPEN MENU

[JavaScript] Iterators & Generators

category: Website | course: Advanced Javascript | difficulty:
IN PRINT
QUICK CONTENTS:Intro
1. Iterators
2. Generators

When something has a defined iteration behaviour, we call it iterable. Nearly everything has a defined iterator method, and is therefore a built-in iterable – the only exception are Objects. For example, when you use a for … in loop to iterate over an array, it iterates over all indices in order starting from zero, and ends after it has reached the last index. This may seem very trivial, but this process is also defined in an iterator function somewhere. And now, you’ll learn how to define your own custom functions to iterate over iterables!

The iteration behaviour of an iterable is saved within the Symbol.iterator property. We are going to use it to assign our own iteration behaviour.

Iterators

An iterator is a type of closure, which means it’s a function which returns another function. The outer function is run once, at the start of iteration, and is used for initializing variables you need. The inner function needs to be called next, and is run every time the iteration is finished with the previous item and wants to go the next one.

This next function should return an object with two properties: value and done. The first property is the value we get from iterating, the second property tells us whether we’re done iterating or not. This should return false on all items, except for the last one – which should return true.

var arr = [5,4,3,2,1,0];
/* This iterator mimics the default iteration behaviour of arrays
It starts at index 0, and runs until it reaches the last index, returning the current index every time*/
arr[Symbol.iterator] = function() { 
	var index=-1;
	var that = this;

	return {
		next: function() { 
			index++;
			return { value: index, done: index>=that.length } 
		} 
	}
};

for(var index of arr) { console.log(arr[index]); }

Generators

With these iterators we just learnt to create, we need to maintain the internal state ourselves; at any given moment, we need to know at what index we are, and whether we’re done yet. If we don’t want this, we can use a powerful variation on iterators called generators.

Normally, when we execute a function, it runs through all statements, and only continues executing the rest of the script once it’s finished. With generators, we can pause a function. We can tell a function to stop at a given moment, and later tell it to continue until it stops again.

To create a generator function, use the function* keyword. It’s nothing different from the functions you’re used to, but the asterisk is there to tell JavaScript it’s a generator function. To pause such a function, use the yield keyword inside of it. To tell a generator to continue, we use the next() method.

Awesome, but what does it have to do with iterators? Well, the yield statement can be followed by an expression. When you tell a generator to continue, it starts by resolving that expression and returning the value that comes out of it. Therefore, if we call next() on a generator function, it actually returns an object with two familiar properties: value and done.

The value property is set to whatever was after the yield keyword. The done property is false, until the generator function reaches its end – then it’s set to true.

function* generatorMaker() {
	yield 1;
	yield 2;
	yield 3;
}

var generator = generatorMaker();

console.log(gen.next().value); //Prints 1
console.log(gen.next().value); //Prints 2
console.log(gen.next().done); //Prints true

To keep a generator function running forever, a while(true) loop is usually employed.

function* genMaker(){
  var index = 0;
  while(true) {
  	index += 5;
    yield index;
  }
}

var gen = genMaker();

console.log(gen.next().value); //Prints 5

Never use this outside of a generator function!

Until now I’ve talked about generators a being a one-way street; they send you values when you call the next function. But, in reality, you can send a value to the generator! This is done using the next(argument) method. This argument is treated as the result of the last yield expression that paused the generator, which means you can save yield expressions within the generator, and do something with the values you receive.

//Generator to easily create a fibonacci sequence
function* fibonacci(){
  var fn1 = 1;
  var fn2 = 1;
  while (true){  
    var current = fn2;
    fn2 = fn1;
    fn1 = fn1 + current;
    var reset = yield current;
    if (reset){
        fn1 = 1;
        fn2 = 1;
    }
  }
}

var sequence = fibonacci();
console.log(sequence.next().value);     //Prints 1
console.log(sequence.next().value);     //Prints 1
console.log(sequence.next().value);     //Prints 2
console.log(sequence.next().value);     //Prints 3
console.log(sequence.next(true).value); //Prints 1
console.log(sequence.next().value);     //Prints 1
console.log(sequence.next().value);     //Prints 2
console.log(sequence.next().value);     //Prints 3
CONTINUE WITH THIS COURSE
Do you like my tutorials?
To keep this site running, donate some motivational food!
Crisps
(€2.00)
Chocolate Milk
(€3.50)
Pizza
(€5.00)