Samuel Silva
- What's an Iterable/Iterator.
- How to implement it.
- Why is it important.
Introduction
const myFavouriteAuthors = [
'Neal Stephenson',
'Arthur Clarke',
'Isaac Asimov',
'Robert Heinlein'
];How to get back all elements individually?
for(let i = 0; i < myFavouriteAuthors.length; i++) {
console.log(myFavouriteAuthors[i]);
}
let i = 0;
while (i < myFavouriteAuthors.length) {
console.log(myFavouriteAuthors[i]);
i++;
}
for(const value of myFavouriteAuthors) {
console.log(value);
}
Now, imagine that instead of the previous array, you had a custom data structure to hold all your authors. Like this —
const myFavouriteAuthors = {
allAuthors: {
fiction: [
'Agatha Christie',
'J. K. Rowling',
'Dr. Seus'
],
scienceFiction: [
'Neal Stepenson',
'Arthur Clark',
'Isaac Asimov',
'Samuel Castro'
],
fantasy: [
'J. R. R. Tolkien',
'J. K. Rowling',
'Terry Pratcheet'
]
}
}
for (let author of myFavouriteAuthors) {
console.log(author)
}
// TypeError: {} is not iterableWe can just create a custom method that iterates over our array:
const myFavouriteAuthors = {
allAuthors: {...},
getAllAuthors() {
const authors = [];
for(const author of this.allAuthors.fiction) {
authors.push(author);
}
...
return authors
}
}- It won't work natively with for/of (unless we call getAllAuthors)
- The name getAllAuthors is very specific, I want something generic, that JavaScript would understand, and anyone could use without read the object, a pattern.
- getAllAuthors returns an array of strings of all the authors. What if another developer returns an array of objects in this format.
- An iterable is a data structure that wants to make its elements accessible to the public. It does so by implementing a method whose key is Symbol.iterator. That method is a factory for iterators. That is, it will create iterators.
- An iterator is a pointer for traversing the elements of a data structure.
const iterable = {
[Symbol.iterator]() { // An iterable should implement the Symbol.iterator function
let step = 0;
const iterator = {
next() {
step++;
if (step === 1)
return {value: 'This', done: false};
else if (step === 2)
return {value: 'is', done: false};
else if (step === 3)
return {value: 'iterable', done: false};
return {value: undefined, done: true};
}
}
return iterator;
}
}
const iterator = iterable[Symbol.iterator]();
console.log(iterator.next()); // {value: 'This', done: false}
console.log(iterator.next()); // {value: 'is', done: false}
console.log(iterator.next()); // {value: 'iterable', done: false}
console.log(iterator.next()); // {value: undefined, done: true}Calling iterator directly
for/of
// Using for/of
for(const value of iterable) {
console.log('for/of', value)
}// Using spread operator
console.log('spread operator', ...iterable); // This is iterableSpread Operator
Destructuring Assignment
// Using Destructuring assignment
const [a, b, c] = iterable
console.log('Destructuring assignment', a, b, c) // This is iterable
A lot of things are iterables in JavaScript. It may not be visible immediately, but if you examine closely, iterables will start to show.
These are all iterables —
- Arrays and TypedArrays
- Strings — iterate over each character or Unicode code-points.
- Maps — iterates over its key-value pairs
- Sets — iterates over their elements
- arguments — An array-like special variable in functions
- DOM elements (Work in Progress)
const arr = [1, 2, 3]
const iteratorArray = arr[Symbol.iterator]();
console.log(iteratorArray.next()) // value: 1, done: false
console.log(iteratorArray.next()) // value: 2, done: false
console.log(iteratorArray.next()) // value: 3, done: false
console.log(iteratorArray.next()) // value: undefined, done true
How to directly invoke native iterables
Array
Map
const map = new Map([[1, 'one'], [2, 'two']]);
const mapIterator = map[Symbol.iterator]();
console.log('Map.get', map.get(1)) // one
console.log('next()', mapIterator.next()) // {value: Array(2), done: false}
console.log('next()', mapIterator.next()) // {value: Array(2), done: false}
console.log('next()', mapIterator.next()) // {value: undefined, done: true}
const myFavouriteAuthors = {
allAuthors: {
fiction: [
'Agatha Christie',
'J. K. Rowling',
'Dr. Seuss'
],
scienceFiction: [
'Neal Stephenson',
'Arthur Clarke',
'Isaac Asimov',
'Robert Heinlein'
],
fantasy: [
'J. R. R. Tolkien',
'J. K. Rowling',
'Terry Pratchett'
],
},
[Symbol.iterator]() {
// Get all the authors in an array
const genres = Object.values(this.allAuthors); // [fiction, scienceFiction, fantasy]
// Store the current genre and author index
let currentAuthorIndex = 0;
let currentGenreIndex = 0;
return {
// Implementation of next()
next() {
// authors according to current genre index
const authors = genres[currentGenreIndex];
// doNotHaveMoreAuthors is true when the authors array is exhausted.
// That is, all items are consumed.
const doNothaveMoreAuthors = currentAuthorIndex >= authors.length;
if (doNothaveMoreAuthors) {
// When that happens, we move the genre index to the next genre
currentGenreIndex++;
// and reset the author index to 0 again to get new set of authors
currentAuthorIndex = 0;
}
// if all genres are over, then we need tell the iterator that we
// can not give more values.
const doNotHaveMoreGenres = currentGenreIndex >= genres.length;
if (doNotHaveMoreGenres) {
// Hence, we return done as true.
return {
value: undefined,
done: true
};
}
// if everything is correct, return the author from the
// current genre and incerement the currentAuthorindex
// so next time, the next author can be returned.
return {
value: genres[currentGenreIndex][currentAuthorIndex++],
done: false
}
}
};
}
};
for (const author of myFavouriteAuthors) {
console.log(author);
}
console.log(...myFavouriteAuthors)
const [a, b] = myFavouriteAuthors
console.log(a, b);Making myFavouriteAuthors iterable
- Iterators are a pattern or a contract that JavaScript will use to understand how to iterate on many different data structure without know or understand how it's implemented. (Map, Arrays, Set, String, Custom Objects)
- Iterators will help us understand Generators.
- And we will use Generators a lot when we talk about Redux-Sagas (An alternative side effect model for Redux apps)
Generators
function* idMaker() {
const index = 0;
while(true)
yield index++;
}
var gen = idMaker();
console.log(gen.next().value); // 0
console.log(gen.next().value); // 1
console.log(gen.next().value); // 2