NAZAR ILKIV
Feb 2, 2021
Javascript Iterators & Generators

Iterators
In Javascript an iteratorΒ is an object which defines a sequence and potentially a return value upon its termination. Specifically, an iterator is any object which implements the Iterator protocol
Iterator Protocol
Convention !!!
type IteratorResult = {
value: any,
done: boolean,
};
interface Iterator {
next(): IteratorResult;
}Iterator Example
function createIterator() {
let index = 0;
return {
next() {
return {
value: index++,
done: false,
}
}
}
}
const iterator = createIterator();
console.log(iterator); // => {next: Ζ next()}
console.log(iterator.next()); // => {value: 0, done: false}
console.log(iterator.next()); // => {value: 1, done: false}
console.log(iterator.next()); // => {value: 2, done: false}
console.log(iterator.next()); // => {value: 3, done: false}
Iterator Features
Stateful
Lazy
Ok, what's next ?!
Almost nothing!!! πππ
Iterables
An object is iterableΒ if it defines its iteration behavior
Iterable Protocol
Convention !!!
interface Iterable {
[Symbol.iterator](): Iterator;
}Built-in iterables
String
Array
Map
Set
Object ???
Consuming Iterables
Spread
for ... of
Array.from()
Map() constructor
const str = 'Hello';
const arr = [1, 2, 3];
console.log([...str]) // => ['H', 'e', 'l', 'l', 'o']
console.log([...arr]) // => [1, 2, 3]; arr !== result
for (const it of str) {
console.log(it); // prints 'H', 'e', 'l', 'l', 'o',
}
for (const it of arr) {
console.log(it); // prints 1, 2, 3
}Example
const iterator = [1,2,3][Symbol.iterator]();
console.log(iterator.next); // => Ζ next() {}
console.log(iterator.next()); // => {value: 1, done: false}
console.log(iterator.next()); // => {value: 2, done: false}
console.log(iterator.next()); // => {value: 3, done: false}
console.log(iterator.next()); // => {value: undefined, done: true}Example
Slides are boring...
Let's code!!!
π¨βπ»
Generators
Special syntax for creating iterators
function* customGenerator() {
yield 1;
yield 2;
yield 3;
}
console.log(typeof customGenerator); // => 'function'
How it works?
function* customGenerator() {
yield 'Hello';
yield Math.random();
return [1,2,3];
}
const result = customGenerator();
console.log(result); // => GeneratorFunctionPrototype
console.log(result[Symbol.iterator]);
// => Ζ [Symbol.iterator]() { [native code] }
console.log(result.next); // => Ζ next() { [native code] }
console.log(result.next()) // => {value: "Hello", done: false}
console.log(result.next()) // => {value: 0.35061525498851975, done: false}
console.log(result.next()) // => {value: Array(3), done: true}
console.log(result.next()) // => {value: undefined, done: true}Features
Iterable
Lazy
Composable
Can consume values
Iterate
function* myGenerator() {
yield 1
yield 2
yield 3
return 4
}
const gen = myGenerator()
for (let val of gen) {
console.log(val) // prints 1, 2, 3
}
// if iterable let's try to spread it
const gen2 = myGenerator()
console.log([...gen2]) // prints [1, 2, 3]
Lazy Evaluation
function* random() {
while(true) yield Math.random()
}function* filter(items, predicate) {
for(let item of items) {
if(predicate(item)) yield item
}
}function* take(items, number) {
let count = 0
for(let item of items) {
yield item
count++
if(count >= number) return
}
}const items = [...take(filter(random(), n => n < 0.5), 2)]
// [0.04971046381666788, 0.3152250891138142]Composition
function* oneTwo() {
yield 1;
yield 2;
}
function* fourFive() {
yield 4;
yield 5;
}
function* sequenceToFive() {
yield 0;
yield* oneTwo();
yield 3;
yield* fourFive();
}
const sequence = sequenceToFive();
console.log([...sequence]); // prints [0, 1, 2, 3, 4, 5]Data Consuming
function* firstLastNameComposer() {
const firstName = yield 'What is your first name?';
const lastName = yield 'What is your last name?';
return `${firstName} ${lastName}`;
}
const gen = firstLastNameComposer()
console.log(gen.next())
// { value: 'What is your first name?', done: false }
console.log(gen.next('Vasya'))
// { value: 'What is your last name?', done: false }
console.log(gen.next('Pupkin'))
// { value: 'Vasya Pupkin', done: true }Handling Rejections
function* generator() {
try {
const obj = {};
obj.foo.bar;
yield 1;
} catch (e) {
console.log("generator error", e);
}
}
const gen = generator();
// generator error
// TypeError: Cannot read property 'bar' of undefined
console.log(gen.next());
// {value: undefined, done: true}
Handling Rejections
function* generator() {
try {
yield 1;
console.log('Hi from generator!'); // never invoked
} catch (e) {
console.log("generator error", e);
}
}
const gen = generator();
gen.next();
console.log(gen.throw(new Error('This is an error')));
Real World Examples
π
Plain Async Code
function* getUserGHRepoCommits(username) {
const API_URL = 'https://api.github.com'
const reposFetch = yield fetch(`${API_URL}/users/${username}/repos`)
const repos = yield reposFetch.json()
const repoCommitsFetch =
yield fetch(`${API_URL}/repos/${username}/${repos[0].name}/commits`)
return repoCommitsFetch.json()
}
const gen = getUserGHRepoCommits('ekscentrysytet')
gen.next().value
.then(reposFetch => gen.next(reposFetch).value)
.then(repos => gen.next(repos).value)
.then(commitsFetch => gen.next(commitsFetch).value)
.then(commits => console.log(commits)) // prints [Object, Object, Object, Object]CO
const co = require('co')
const express = require('express')
const router = express.Router()
const Post = require('../models/Post');
/* ... */
router.put('/:postId', auth, (req, res, next) => {
co(function* () {
const post = yield Post.findOne({id: req.params.postId});
if (!post) throw new Error('postId');
if (req.user.id !== post.author.toString()) {
throw new Error('noRights');
}
const updatedPost = yield post.update(req.body);
return updatedPost;
})
.then(result => res.json(result))
.catch(err => {
res.status(errCode).json(err);
})
})Async Iterators
interface IteratorResult {
value: any;
done: boolean;
}
interface AsyncIterator {
next() : Promise<IteratorResult>;
}
interface AsyncIterable {
[Symbol.asyncIterator]() : AsyncIterator;
}for await (const item of asyncIterable) {
// ...
}Consuming streams
async function streamingRead() {
const response = await fetch('https://html.spec.whatwg.org');
for await (const chunk of streamAsyncIterator(response.body)) {
console.log(`Read ${chunk.length} bytes`);
}
}
async function* streamAsyncIterator(stream) {
const reader = stream.getReader();
try {
while (true) {
const {done, value} = await reader.read();
// Exit if we're done
if (done) return;
// Else yield the chunk
yield value;
}
}
finally {
reader.releaseLock();
}
}
streamingRead();Consuming paginated API's
async function* fetchCommits(repo) {
let url = `https://api.github.com/repos/${repo}/commits`;
while (url) {
const response = await fetch(url, {
headers: {'User-Agent': 'Our script'},
});
const body = await response.json();
let nextPage = response.headers.get('Link').match(/<(.*?)>; rel="next"/);
nextPage = nextPage && nextPage[1];
url = nextPage;
for (let commit of body) {
yield commit;
}
}
}
// then
for await (const commit of fetchCommits('javascript-tutorial/en.javascript.info')) {
console.log(commit);
}Q&A Time
Javascript Iterators & Generators
By Nazar Ilkiv
Javascript Iterators & Generators
- 336