Monday, 28 November, 2016 UTC


Summary

This article is a section from the workbook of ES6 in Practice. I created this course during the last couple of months, because there is an evident need for a resource that helps JavaScript developers put theory into practice.
This course won’t waste your time with long hours of video, or long pages of theory that you will never use in practice. We will instead focus on learning just enough theory to solve some exercises. Once you are done with the exercises, you can check the reference solutions, and conclude some lessons. In fact, some of the theory are sometimes placed in the reference solutions.
If you like this article, check out the course here.
In my last article, I gave you six exercises. In this article, you can check the reference solutions.
Exercise 1. What happens if we use a string iterator instead of an iterable object in a for-of loop?
let message = 'ok';
let messageIterator = message[Symbol.iterator]();

messageIterator.next();

for ( let item of messageIterator ) {
    console.log( item );
}

Solution: Similarly to generators, in case of strings, arrays, DOM elements, sets, and maps, an iterator object is also an iterable.
Therefore, in the for-of loop, the remaining k letter is printed out.

Exercise 2. Create a countdown iterator that counts from 9 to 1. Use generator functions!
let getCountdownIterator = // Your code comes here

console.log( [ ...getCountdownIterator() ] );
> [9, 8, 7, 6, 5, 4, 3, 2, 1]

Solution:
let getCountdownIterator = function *() {
    let i = 10;
    while( i > 1 ) {
        yield --i;
    }
}

console.log( [ ...getCountdownIterator() ] );
> [9, 8, 7, 6, 5, 4, 3, 2, 1]
The generator function yields the numbers 9 to 1. The spread (...) operator consumes all values. Then the generator function returns undefined, ending the iteration process.

Exercise 3. Make the following object iterable:
let todoList = {
    todoItems: [],
    addItem( description ) {
        this.todoItems.push( { description, done: false } );
        return this;
    },
    crossOutItem( index ) {
        if ( index < this.todoItems.length ) {
            this.todoItems[index].done = true;
        }
        return this;
    }
};

todoList.addItem( 'task 1' ).addItem( 'task 2' ).crossOutItem( 0 );

let iterableTodoList = // ???;

for ( let item of iterableTodoList ) {
    console.log( item );
}

// Without your code, you get the following error:
// Uncaught TypeError: todoList[Symbol.iterator] is not a function

First Solution (well known symbol):
We could use well known symbols to make todoList iterable. We can add a *[Symbol.iterator] generator function that yields the elements of the array. This will make the todoList object iterable, yielding the elements of todoItems one by one.
let todoList = {
    todoItems: [],
    *[Symbol.iterator]() {
        yield* this.todoItems;
    }
    addItem( description ) {
        this.todoItems.push( { description, done: false } );
        return this;
    },
    crossOutItem( index ) {
        if ( index < this.todoItems.length ) {
            this.todoItems[index].done = true;
        }
        return this;
    }
};

let iterableTodoList = todoList;

Second solution (generator function): If you prefer staying away from well known symbols, it is possible to make your code more semantic:
let todoList = {
    todoItems: [],
    addItem( description ) {
        this.todoItems.push( { description, done: false } );
        return this;
    },
    crossOutItem( index ) {
        if ( index < this.todoItems.length ) {
            this.todoItems[index].done = true;
        }
        return this;
    }
};

todoList.addItem( 'task 1' ).addItem( 'task 2' ).crossOutItem( 0 );

let todoListGenerator = function *() {
    yield* todoList.todoItems;
}

let iterableTodoList = todoListGenerator();
The first solution reads a bit like a hack. The second solution looks cleaner even if you have to type more characters.

Exercise 4. Determine the values logged to the console without running the code. Instead of just writing down the values, formulate your thought process and explain to yourself how the code runs line by line.
let errorDemo = function *() {
    yield 1;
    throw 'Error yielding the next result';
    yield 2;
}

let it = errorDemo();

// Execute one statement at a time to avoid
// skipping lines after the first thrown error.

console.log( it.next() );

console.log( it.next() );

console.log( [...errorDemo()] );

for ( let element of errorDemo() ) {
    console.log( element );
}

Solution:
console.log( it.next() );
> Object {value: 1, done: false}

console.log( it.next() );
> Uncaught Error yielding the next result

console.log( [...errorDemo()] );
> Uncaught Error yielding the next result

for ( let element of errorDemo() ) {
    console.log( element );
}
> Object {value: 1, done: false}
> Uncaught Error yielding the next result
We created three iterables in total: it, one in the statement in the spread operator, and one in the for-of loop.
In the example with the next calls, the second call results in a thrown error.
In the spread operator example, the expression cannot be evaluated, because an error is thrown.
In the for-of example, the first element is printed out, then the error stopped the execution of the loop.

Exercise 5. Create an infinite sequence that generates the next value of the Fibonacci sequence.
The Fibonacci sequence is defined as follows:
  • fib( 0 ) = 0
  • fib( 1 ) = 1
  • for n > 1, fib( n ) = fib( n - 1 ) + fib( n - 2 )

Solution:
function *fibonacci() {
    let a = 0, b = 1;
    yield a;
    yield b;
    while( true ) {
        [a, b] = [b, a+b];
        yield b;
    }
}
Note that you only want to get the next() element of an infinite sequence. Executing [...fibonacci()] will skyrocket your CPU usage, speed up your CPU fan, and then crash your browser.

Exercise 6. Create a lazy filter generator function. Filter the elements of the Fibonacci sequence by keeping the even values only.
function *filter( iterable, filterFunction ) {
    // insert code here
}

Solution:
function *filter( iterable, filterFunction ) {
    for( let element of iterable ) {
        if ( filterFunction( element ) ) yield element;
    }
}

let evenFibonacci = filter( fibonacci(), x => x%2 === 0 );
Notice how easy it is to combine generators and lazily evaluate them.
evenFibonacci.next()
> {value: 0, done: false}
evenFibonacci.next()
> {value: 2, done: false}
evenFibonacci.next()
> {value: 8, done: false}
evenFibonacci.next()
> {value: 34, done: false}
evenFibonacci.next()
> {value: 144, done: false}
Lazy evaluation is essential when we work on a large set of data. For instance, if you have 1000 accounts, chances are that you don’t want to transform all of them if you just want to render the first ten on screen. This is when lazy evaluation comes into play.
If you would like to get five more chapters of this book, click YES below, and sign up.
Would you like to learn ES6?
Strengthen your JavaScript knowledge with marketable skills!
YesORNo
Close
Get the Course "ES6 in Practice"!
Learn Marketable Skills.

Verify your knowledge with real world exercises.
I'm In!
Close
Thank you for your subscription.
Please check your inbox to access the first lesson.
Close
If you would prefer getting all the exercises and the book, visit this link, or click the image below.