Luca Del Puppo - Senior Software Developer

JavaScript's Hidden Gems:

Unveiling the Magic of Iterators and Generators

Which is the result?

const array = ['a', 'b', 'c']

for (const item of array) {
    console.log(item)
}
Output

a
b
c

And now?

for (const item of 'abc') {
    console.log(item)
}
Output

a
b
c

Last test

const obj = {
  a: 'a',
  b: 'b',
  c: 'c'
}
for (const item of obj) {
    console.log(item)
}
Output:
⛔️ Uncaught TypeError: obj is not iterable

🤔

You are using Iterators every day

don't you know?

for ... of

The for...of statement executes a loop that operates on a sequence of values sourced from an iterable object. Iterable objects include instances of built-ins such as Array, String, TypedArray, Map, Set, NodeList (and other DOM collections), as well as the arguments object, generators produced by generator functions, and user-defined iterables.

Just another test?

const a = ['a', 'b', 'c'];

console.log(...a)
Output

a
b
c

spread operator (...)

The spread (...) syntax allows an iterable, such as an array or string, to be expanded in places where zero or more arguments (for function calls) or elements (for array literals) are expected. In an object literal, the spread syntax enumerates the properties of an object and adds the key-value pairs to the object being created.

Luca Del Puppo

  • Senior Software Developer
  • JavaScript enthusiast
  • TypeScript lover
  • “Youtuber”
  • “Writer”

Love sport: running, hiking

Love animals

Why should you use iterators?

  • Lazy abstraction to represent repetition
  • Consume collection only when needed
  • Perfect for large datasets

But what is

an Iterable object?

Iterator object

An object that acts like a cursor to iterate over blocks of data sequentially

Iterator object

Iterator object

interface Iterator {
  next<T>(): { 
    done: boolean,
    value: T
  }
}

Iterable object

An object that provides data that can be iterated over sequentially

Iterable object

Iterable object

interface Iterable<T> {
  [Symbol.iterator](): Iterator<T>;
}

Iterator protocol

const iterator = {
  next () {
    return { 
      done: false,
      value: "someValue"
    }
  }
}
interface Iterator {
  next<T>(): { 
    done: boolean,
    value: T
  }
}
function range(start: number, end: number)
	: Iterator<number> {
  let n = start;
  return {
    next() {
      if (n > end) {
        return { done: true, value: null };
      }
      return { done: false, value: n++ };
    }
  };
};
function range(start: number, end: number)
	: Iterable<number> {
  return {
    [Symbol.iterator]() {
      let n = start;
      return {
        next() {
          if (n > end) {
            return { done: true, value: null };
          }
          return { done: false, value: n++ };
        }
      };
    },
  };
};
interface Iterable<T> {
  [Symbol.iterator](): Iterator<T>;
}

Well-know Symbols

const iterator =
      range(1, 10)[Symbol.iterator]();

for (let result = iterator.next();
     	!result.done;
     	result = iterator.next()) {
  console.log(result.value);
}




for (const value of range(1, 10)) {
  console.log(value);
}

What if I'd break

your loop? 😈

function range(start: number, end: number)
	: Iterable<number> {
  return {
    [Symbol.iterator]() {
      let n = start;
      return {
        next() {
          if (n > end) return { done: true, value: null };
          return { done: false, value: n++ };
        },
        return() {
          return { done: true, value: null };
        },
      };
    },
  };
};

Generators

Imagine the iterators' power
but with a simpler syntax

function* range(start: number, end: number)
	: Iterable<number> {
  let n = start;
  while (n <= end) {
    yield n++;
  }
}

The Generator signature

The yield syntax

const iterator =
      range(1, 10)[Symbol.iterator]();

for (let result = iterator.next();
     	!result.done;
     	result = iterator.next()) {
  console.log(result.value);
}




for (const value of range(1, 10)) {
  console.log(value);
}

Asynchronous

how can I handle you?

const asyncIterator = {
  async next () {
    return { 
      done: false,
      value: "someValue"
    }
  },
  async return() {
    return {
      done: true,
      value: undefined
    }
  }
}
interface AsyncIterator {
  next<T>(): Promise<{
    done: boolean,
    value: T
  }>,
  return<T>(): Promise<{ 
    done: boolean,
    value: T
  }>
}
const getUsers = (ids: number[]): AsyncIterable<User> => {
  return {
    [Symbol.asyncIterator]() {
      let i = 0;
      return {
        async next() {
          if (i === ids.length) {
            return { done: true, value: null };
          }
          const data = await fetch(
            `https://reqres.in/api/users/${ids[i++]}`
          ).then(res => res.json());
          return { done: false, value: data };
        },
        async return() {
          return { done: true, value: null };
        },
      };
    },
  };
};






for await (const user of getUsers([1, 2, 3, 4, 5])) {
  console.log(user);
}

for await ... of

And what about generators?

async function* getUsers(ids: number[]) {
  for (const id of ids) {
    const data = await
    	fetch(`https://reqres.in/api/users/${id}`)
    	.then(res =>
      		res.json()
    	);
    yield data;
  }
}

enable async

yield return







for await (const user of getUsers([1, 2, 3, 4, 5])) {
  console.log(user);
}

In the same way

Real Use Cases

Node

  • Streams
  • Massive data that cannot stay in the sever 

I love csv

36KB

50KB

10KB

4GB

800MB

...
...
...
"1699553281266","0.1386636303377431"
"1699553281266","46.83486548448599"
"1699553281266","32.464374818037015"
"1699553281266","51.66273865612248"
"1699553281266","51.233009455984416"
"1699553281266","38.046865447500046"
"1699553281266","22.610913015578337"
...
...
...

Timestamp

Temperature

import {open} from 'node:fs/promises';
import Papa from 'papaparse';

...

const file = await open(filePath);
for await (const line of file.readLines()) {
  const csvLine = Papa.parse(line)
  const [timestamp,value] = csvLine.data[0]
  const valueAsNumber = Number(value)
  if (valueAsNumber > 50) {
    console.log({ timestamp, valueAsNumber })
  }
}

...

Frontend

  • Infinitive Scroll
  • Redux-saga

Conclusion

🌟 Embrace Synchronous Flow

  • Iterators bring elegance to handling data
  • Sync Generators enable step-by-step control

🌠 Unleash the Async Magic

  • Async Generators offer concurrency with finesse
  • Asynchronous Iteration handles non-blocking operations
  • Elevate your code to the next level of efficiency

🔑 Key Takeaways

  • JavaScript's versatility shines through Iterators and Generators
  • Sync or Async, adapt to the needs of your project

Slides

YouTube Videos & Blog Posts

Javascript Iterators & Generators

Luca Del Puppo

@puppo92

Luca Del Puppo

Puppo_92

@puppo

We are hiring

Thank you!