Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Did you know JavaScript has iterators? DublinJS

Did you know JavaScript has iterators? DublinJS

How many ways do you know to do iteration with JavaScript and Node.js? While, for loop, for…in, for..of, .map(), .forEach(), streams, iterators, etc! Yes, there are a lot of ways! But did you know that JavaScript has iteration protocols to standardise synchronous and even asynchronous iteration? In this workshop we will learn about these protocols and discover how to build iterators and iterable objects, both synchronous and asynchronous. We will learn about some common use cases for these protocols, explore generators and async generators (great tools for iteration) and finally discuss some hot tips, common pitfalls, and some (more or less successful) wild ideas!

Luciano Mammino

March 07, 2023
Tweet

More Decks by Luciano Mammino

Other Decks in Technology

Transcript

  1. META_SLIDE!
    loige
    loige.link/iterate
    2

    View full-size slide

  2. String[] transactions = {
    "paid 20",
    "received 10",
    "paid 5",
    "received 15",
    "paid 10",
    "received 12"
    };
    var total = Stream.of(transactions).mapToInt(transaction -> {
    var parts = transaction.split(" ");
    int amount = Integer.decode(parts[1]);
    if (Objects.equals(parts[0], "paid")) {
    amount = -amount;
    }
    return amount;
    }).sum();
    if (total >= 0) {
    System.out.println("Life is good :)");
    } else {
    System.out.println("You are broke :(");
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    loige
    replit.com/@lmammino/r-u-broke-java
    3

    View full-size slide

  3. String[] transactions = {
    "paid 20",
    "received 10",
    "paid 5",
    "received 15",
    "paid 10",
    "received 12"
    };
    1
    2
    3
    4
    5
    6
    7
    8
    9
    var total = Stream.of(transactions).mapToInt(transaction -> {
    10
    var parts = transaction.split(" ");
    11
    int amount = Integer.decode(parts[1]);
    12
    if (Objects.equals(parts[0], "paid")) {
    13
    amount = -amount;
    14
    }
    15
    16
    return amount;
    17
    }).sum();
    18
    19
    if (total >= 0) {
    20
    System.out.println("Life is good :)");
    21
    } else {
    22
    System.out.println("You are broke :(");
    23
    }
    24
    var total = Stream.of(transactions).mapToInt(transaction -> {
    String[] transactions = {
    1
    "paid 20",
    2
    "received 10",
    3
    "paid 5",
    4
    "received 15",
    5
    "paid 10",
    6
    "received 12"
    7
    };
    8
    9
    10
    var parts = transaction.split(" ");
    11
    int amount = Integer.decode(parts[1]);
    12
    if (Objects.equals(parts[0], "paid")) {
    13
    amount = -amount;
    14
    }
    15
    16
    return amount;
    17
    }).sum();
    18
    19
    if (total >= 0) {
    20
    System.out.println("Life is good :)");
    21
    } else {
    22
    System.out.println("You are broke :(");
    23
    }
    24
    var parts = transaction.split(" ");
    int amount = Integer.decode(parts[1]);
    if (Objects.equals(parts[0], "paid")) {
    amount = -amount;
    }
    return amount;
    String[] transactions = {
    1
    "paid 20",
    2
    "received 10",
    3
    "paid 5",
    4
    "received 15",
    5
    "paid 10",
    6
    "received 12"
    7
    };
    8
    9
    var total = Stream.of(transactions).mapToInt(transaction -> {
    10
    11
    12
    13
    14
    15
    16
    17
    }).sum();
    18
    19
    if (total >= 0) {
    20
    System.out.println("Life is good :)");
    21
    } else {
    22
    System.out.println("You are broke :(");
    23
    }
    24
    }).sum();
    String[] transactions = {
    1
    "paid 20",
    2
    "received 10",
    3
    "paid 5",
    4
    "received 15",
    5
    "paid 10",
    6
    "received 12"
    7
    };
    8
    9
    var total = Stream.of(transactions).mapToInt(transaction -> {
    10
    var parts = transaction.split(" ");
    11
    int amount = Integer.decode(parts[1]);
    12
    if (Objects.equals(parts[0], "paid")) {
    13
    amount = -amount;
    14
    }
    15
    16
    return amount;
    17
    18
    19
    if (total >= 0) {
    20
    System.out.println("Life is good :)");
    21
    } else {
    22
    System.out.println("You are broke :(");
    23
    }
    24
    if (total >= 0) {
    System.out.println("Life is good :)");
    } else {
    System.out.println("You are broke :(");
    }
    String[] transactions = {
    1
    "paid 20",
    2
    "received 10",
    3
    "paid 5",
    4
    "received 15",
    5
    "paid 10",
    6
    "received 12"
    7
    };
    8
    9
    var total = Stream.of(transactions).mapToInt(transaction -> {
    10
    var parts = transaction.split(" ");
    11
    int amount = Integer.decode(parts[1]);
    12
    if (Objects.equals(parts[0], "paid")) {
    13
    amount = -amount;
    14
    }
    15
    16
    return amount;
    17
    }).sum();
    18
    19
    20
    21
    22
    23
    24
    String[] transactions = {
    "paid 20",
    "received 10",
    "paid 5",
    "received 15",
    "paid 10",
    "received 12"
    };
    var total = Stream.of(transactions).mapToInt(transaction -> {
    var parts = transaction.split(" ");
    int amount = Integer.decode(parts[1]);
    if (Objects.equals(parts[0], "paid")) {
    amount = -amount;
    }
    return amount;
    }).sum();
    if (total >= 0) {
    System.out.println("Life is good :)");
    } else {
    System.out.println("You are broke :(");
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    loige
    replit.com/@lmammino/r-u-broke-java
    4

    View full-size slide

  4. transactions = [
    "paid 20",
    "received 10",
    "paid 5",
    "received 15",
    "paid 10",
    "received 12"
    ];
    def get_amount(transaction):
    type, amount = transaction.split(' ')
    amount = int(amount)
    if type == 'paid':
    return -amount
    return amount
    *_, total = accumulate(
    map(get_amount, transactions)
    )
    if total >= 0:
    print("Life is good :)")
    else:
    print("You are broke :(")
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    loige
    replit.com/@lmammino/r-u-broke-py
    5

    View full-size slide

  5. let transactions = [
    "paid 20",
    "received 10",
    "paid 5",
    "received 15",
    "paid 10",
    "received 12",
    ];
    let total: i32 = transactions
    .iter()
    .map(|transaction| {
    let (action, amount) = transaction.split_once(' ').unwrap();
    let amount = amount.parse::().unwrap();
    if action == "paid" {
    -amount
    } else {
    amount
    }
    })
    .sum();
    if total >= 0 {
    println!("Life is good :)");
    } else {
    println!("You are broke :(");
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    loige
    replit.com/@lmammino/r-u-broke-rust
    6

    View full-size slide

  6. const transactions = [
    "paid 20",
    "received 10",
    "paid 5",
    "received 15",
    "paid 10",
    "received 12"
    ]
    const total = Iterator.from(transactions)
    .map(transaction => {
    let [action, amount] = transaction.split(" ")
    amount = Number.parseInt(amount)
    if (action === "paid") {
    return -amount
    } else {
    return amount
    }
    })
    .reduce((acc,curr) => acc + curr)
    if (total >= 0) {
    console.log("Life is good :)")
    } else {
    console.log("You are broke :(");
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    loige
    replit.com/@lmammino/r-u-broke-js
    7

    View full-size slide

  7. const transactions = [
    "paid 20",
    "received 10",
    "paid 5",
    "received 15",
    "paid 10",
    "received 12"
    ]
    const total = Iterator.from(transactions)
    .map(transaction => {
    let [action, amount] = transaction.split(" ")
    amount = Number.parseInt(amount)
    if (action === "paid") {
    return -amount
    } else {
    return amount
    }
    })
    .reduce((acc,curr) => acc + curr)
    if (total >= 0) {
    console.log("Life is good :)")
    } else {
    console.log("You are broke :(");
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    const total = Iterator.from(transactions)
    const transactions = [
    1
    "paid 20",
    2
    "received 10",
    3
    "paid 5",
    4
    "received 15",
    5
    "paid 10",
    6
    "received 12"
    7
    ]
    8
    9
    10
    .map(transaction => {
    11
    let [action, amount] = transaction.split(" ")
    12
    amount = Number.parseInt(amount)
    13
    if (action === "paid") {
    14
    return -amount
    15
    } else {
    16
    return amount
    17
    }
    18
    })
    19
    .reduce((acc,curr) => acc + curr)
    20
    21
    if (total >= 0) {
    22
    console.log("Life is good :)")
    23
    } else {
    24
    console.log("You are broke :(");
    25
    }
    26
    .map(transaction => {
    const transactions = [
    1
    "paid 20",
    2
    "received 10",
    3
    "paid 5",
    4
    "received 15",
    5
    "paid 10",
    6
    "received 12"
    7
    ]
    8
    9
    const total = Iterator.from(transactions)
    10
    11
    let [action, amount] = transaction.split(" ")
    12
    amount = Number.parseInt(amount)
    13
    if (action === "paid") {
    14
    return -amount
    15
    } else {
    16
    return amount
    17
    }
    18
    })
    19
    .reduce((acc,curr) => acc + curr)
    20
    21
    if (total >= 0) {
    22
    console.log("Life is good :)")
    23
    } else {
    24
    console.log("You are broke :(");
    25
    }
    26
    .reduce((acc,curr) => acc + curr)
    const transactions = [
    1
    "paid 20",
    2
    "received 10",
    3
    "paid 5",
    4
    "received 15",
    5
    "paid 10",
    6
    "received 12"
    7
    ]
    8
    9
    const total = Iterator.from(transactions)
    10
    .map(transaction => {
    11
    let [action, amount] = transaction.split(" ")
    12
    amount = Number.parseInt(amount)
    13
    if (action === "paid") {
    14
    return -amount
    15
    } else {
    16
    return amount
    17
    }
    18
    })
    19
    20
    21
    if (total >= 0) {
    22
    console.log("Life is good :)")
    23
    } else {
    24
    console.log("You are broke :(");
    25
    }
    26
    const transactions = [
    "paid 20",
    "received 10",
    "paid 5",
    "received 15",
    "paid 10",
    "received 12"
    ]
    const total = Iterator.from(transactions)
    .map(transaction => {
    let [action, amount] = transaction.split(" ")
    amount = Number.parseInt(amount)
    if (action === "paid") {
    return -amount
    } else {
    return amount
    }
    })
    .reduce((acc,curr) => acc + curr)
    if (total >= 0) {
    console.log("Life is good :)")
    } else {
    console.log("You are broke :(");
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    loige
    replit.com/@lmammino/r-u-broke-js
    8

    View full-size slide

  8. const transactions = [
    "paid 20",
    "received 10",
    "paid 5",
    "received 15",
    "paid 10",
    "received 12"
    ]
    const total = Iterator.from(transactions)
    .map(transaction => {
    let [action, amount] = transaction.split(" ")
    amount = Number.parseInt(amount)
    if (action === "paid") {
    return -amount
    } else {
    return amount
    }
    })
    .reduce((acc,curr) => acc + curr)
    if (total >= 0) {
    console.log("Life is good :)")
    } else {
    console.log("You are broke :(");
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    loige
    replit.com/@lmammino/r-u-broke-js
    This is not JavaScript, it's FutureJavaScript™
    github.com/tc39/proposal-iterator-helpers
    9

    View full-size slide

  9. import Iterator from 'core-js-pure/actual/iterator/index.js'
    1
    2
    const transactions = [
    3
    "paid 20",
    4
    "received 10",
    5
    "paid 5",
    6
    "received 15",
    7
    "paid 10",
    8
    "received 12"
    9
    ]
    10
    11
    const total = Iterator.from(transactions)
    12
    .map(transaction => {
    13
    let [action, amount] = transaction.split(" ")
    14
    amount = Number.parseInt(amount)
    15
    if (action === "paid") {
    16
    return -amount
    17
    } else {
    18
    return amount
    19
    }
    20
    })
    21
    .reduce((acc,curr) => acc + curr)
    22
    23
    if (total >= 0) {
    24
    console.log("Life is good :)")
    25
    } else {
    26
    console.log("You are broke :(");
    27 loige
    But if you want the future, today...
    npm i --save core-js-pure
    10

    View full-size slide

  10. loige
    ITERATORS ARE LAZY!
    You can consume the collection 1 item at the time
    You don't need to keep all the items in memory
    Great for large datasets
    You can even have endless iterators!
    12

    View full-size slide

  11. loige
    The concept of Iterators exists already in JavaScript...
    🗞 GOOD NEWS FROM THE WORLD...
    ... since ES2015!
    13

    View full-size slide

  12. WAIT, WHO THE HECK IS THIS GUY !?
    🤷
    👋 I'm Luciano (
    🍕🍝)
    Senior Architect @ fourTheorem (Dublin )
    nodejsdp.link
    📔 Co-Author of Node.js Design Patterns
    👉
    Let's connect!
    (blog)
    (twitter)
    (twitch)
    (github)
    loige.co
    @loige
    loige
    lmammino
    14

    View full-size slide

  13. ALWAYS RE-IMAGINING
    WE ARE A PIONEERING TECHNOLOGY CONSULTANCY FOCUSED
    ON CLOUD, AWS & SERVERLESS
    loige
    😇 We are always looking for talent: fth.link/careers
    We can help with:
    Cloud Migrations
    Training & Cloud enablement
    Building serverless applications
    Cutting cloud costs
    15

    View full-size slide

  14. loige
    podcast
    awsbites.com
    IF YOU LIKE AWS...
    16

    View full-size slide

  15. loige
    🥤
    LET'S HAVE A QUICK REFRESHER
    ON SOME JS "ITERATION" CONCEPTS...
    17

    View full-size slide

  16. const array = ['foo', 'bar', 'baz']
    for (const item of array) {
    console.log(item)
    }
    loige
    Output:
    foo
    bar
    baz
    18

    View full-size slide

  17. const str = 'foo'
    for (const item of str) {
    console.log(item)
    }
    loige
    Output:
    f
    o
    o
    19

    View full-size slide

  18. const set = new Set(['foo', 'bar', 'baz'])
    for (const item of set) {
    console.log(item)
    }
    loige
    Output:
    foo
    bar
    baz
    20

    View full-size slide

  19. const map = new Map([
    ['foo', 'bar'], ['baz', 'qux']
    ])
    for (const item of map) {
    console.log(item)
    }
    loige
    Output:
    [ 'foo', 'bar' ]
    [ 'baz', 'qux' ]
    21

    View full-size slide

  20. const obj = {
    foo: 'bar',
    baz: 'qux'
    }
    for (const item of obj) {
    console.log(item)
    }
    loige
    Output:

    Uncaught TypeError: obj is not iterable
    OMG `for ... of`
    does not work with plain objects!
    😱
    22

    View full-size slide

  21. const obj = {
    foo: 'bar',
    baz: 'qux'
    }
    for (const item of Object.entries(obj)) {
    console.log(item)
    }
    loige
    Output:
    [ 'foo', 'bar' ]
    [ 'baz', 'qux' ]
    23

    View full-size slide

  22. const array = ['foo', 'bar', 'baz']
    console.log(...array)
    loige
    Output:
    foo bar baz
    spread syntax!
    25

    View full-size slide

  23. 📒 AGENDA
    Generators
    Iterator protocol
    Iterable protocol
    Async Iterator protocol
    Async Iterable protcol
    Real-life™ examples
    loige 28

    View full-size slide

  24. ➡ GENERATORS
    loige 29

    View full-size slide

  25. GENERATOR FN & OBJ
    loige
    function * myGenerator () {
    // generator body
    yield 'someValue'
    // ... do more stuff
    }
    const genObj = myGenerator()
    genObj.next() // -> { done: false, value: 'someValue' }
    30

    View full-size slide

  26. function * fruitGen () {
    yield '
    🍑
    '
    yield '
    🍉
    '
    yield '
    🍋
    '
    yield '
    🥭
    '
    }
    const fruitGenObj = fruitGen()
    console.log(fruitGenObj.next()) // { value: '
    🍑
    ', done: false }
    console.log(fruitGenObj.next()) // { value: '
    🍉
    ', done: false }
    console.log(fruitGenObj.next()) // { value: '
    🍋
    ', done: false }
    console.log(fruitGenObj.next()) // { value: '
    🥭
    ', done: false }
    console.log(fruitGenObj.next()) // { value: undefined, done: true }
    loige 31

    View full-size slide

  27. function * fruitGen () {
    yield '
    🍑
    '
    yield '
    🍉
    '
    yield '
    🍋
    '
    yield '
    🥭
    '
    }
    const fruitGenObj = fruitGen()
    // generator objects are iterable!
    for (const fruit of fruitGenObj) {
    console.log(fruit)
    }
    //
    🍑
    //
    🍉
    //
    🍋
    //
    🥭
    loige 32

    View full-size slide

  28. function * range (start, end) {
    for (let i = start; i < end; i++) {
    yield i
    }
    }
    // generators are lazy!
    for (const i of range(0, Number.MAX_VALUE)) {
    console.log(i)
    }
    const zeroToTen = [...range(0, 11)]
    loige 33

    View full-size slide

  29. // generators can be "endless"
    function * cycle (values) {
    let current = 0
    while (true) {
    yield values[current]
    current = (current + 1) % values.length
    }
    }
    for (const value of cycle(['even', 'odd'])) {
    console.log(value)
    }
    // even
    // odd
    // even
    // ...
    loige 34

    View full-size slide

  30. 📝 MINI-SUMMARY
    loige
    A generator function returns a generator object which is both an iterator and an iterable.
    A generator function uses `yield` to yield a value and pause its execution. The generator object
    is used to make progress on an instance of the generator (by calling `next()`).
    Generator functions are a great way to create custom iterable objects.
    Generator objects are lazy and they can be endless.
    35

    View full-size slide

  31. ➡ ITERATORS & ITERABLES
    loige 36

    View full-size slide

  32. ITERATOR OBJ
    loige
    An object that acts like a cursor to iterate over blocks of data sequentially
    37

    View full-size slide

  33. ITERABLE OBJ
    loige
    An object that contains data that can be iterated over sequentially
    38

    View full-size slide

  34. THE ITERATOR PROTOCOL
    An object is an iterator if it has a next() method.
    Every time you call it, it returns an object with the keys
    done (boolean) and value.
    loige 39

    View full-size slide

  35. function createCountdown (from) {
    let nextVal = from
    return {
    next () {
    if (nextVal < 0) {
    return { done: true }
    }
    return {
    done: false,
    value: nextVal--
    }
    }
    }
    }
    loige 40

    View full-size slide

  36. const countdown = createCountdown(3)
    console.log(countdown.next())
    // { done: false, value: 3 }
    console.log(countdown.next())
    // { done: false, value: 2 }
    console.log(countdown.next())
    // { done: false, value: 1 }
    console.log(countdown.next())
    // { done: false, value: 0 }
    console.log(countdown.next())
    // { done: true }
    loige 41

    View full-size slide

  37. 🔥
    Generator Objects are iterators (and iterables)!
    loige 42

    View full-size slide

  38. function * createCountdown (from) {
    for (let i = from; i >= 0; i--) {
    yield i
    }
    }
    loige 43

    View full-size slide

  39. const countdown = createCountdown(3)
    console.log(countdown.next())
    // { done: false, value: 3 }
    console.log(countdown.next())
    // { done: false, value: 2 }
    console.log(countdown.next())
    // { done: false, value: 1 }
    console.log(countdown.next())
    // { done: false, value: 0 }
    console.log(countdown.next())
    // { done: true, value: undefined }
    loige 44

    View full-size slide

  40. THE ITERABLE PROTOCOL
    An object is iterable if it implements the
    Symbol.iterator method, a zero-argument function
    that returns an iterator.
    loige 45

    View full-size slide

  41. function createCountdown (from) {
    let nextVal = from
    return {
    [Symbol.iterator]: () => ({
    next () {
    if (nextVal < 0) {
    return { done: true }
    }
    return { done: false, value: nextVal-- }
    }
    })
    }
    }
    loige 46

    View full-size slide

  42. function createCountdown (from) {
    return {
    [Symbol.iterator]: function * () {
    for (let i = from; i >= 0; i--) {
    yield i
    }
    }
    }
    }
    loige 47

    View full-size slide

  43. function * createCountdown () {
    for (let i = from; i >= 0; i--) {
    yield i
    }
    }
    loige
    🔥 or just use generators!
    48

    View full-size slide

  44. const countdown = createCountdown(3)
    for (const value of countdown) {
    console.log(value)
    }
    // 3
    // 2
    // 1
    // 0
    loige 49

    View full-size slide

  45. const iterableIterator = {
    next () {
    return { done: false, value: 'hello' }
    },
    [Symbol.iterator] () {
    return this
    }
    }
    An object can be an iterable and an iterator at the same time!
    loige 50

    View full-size slide

  46. 📝 MINI-SUMMARY 1/2
    loige
    An iterator is an object that allows us to traverse a collection
    The iterator protocol specifies that an object is an iterator if it has a `next()` method that
    returns an object with the shape `{done, value}`.
    `done` (a boolean) tells us if the iteration is completed
    `value` represents the value from the current iteration.
    You can write an iterator as an anonymous object (e.g. returned by a factory function), using
    classes or using generators.
    51

    View full-size slide

  47. 📝 MINI-SUMMARY 2/2
    loige
    The iterable protocol defines what's expected for a JavaScript object to be considered
    iterable. That is an object that holds a collection of data on which you can iterate on
    sequentially.
    An object is iterable if it implements a special method called `Symbol.iterator` which returns
    an iterator. (An object is iterable if you can get an iterator from it!)
    Generator functions produce objects that are iterable.
    We saw that generators produce objects that are also iterators.
    It is possible to have objects that are both iterator and iterable. The trick is to create the object
    as an iterator and to implement a `Symbol.iterator` that returns the object itself (`this`).
    52

    View full-size slide

  48. OK, very cool!
    But, so far this is all synchronous iteration.
    What about async?
    🙄
    loige 53

    View full-size slide

  49. ➡ ASYNC ITERATORS & ITERABLES
    loige 54

    View full-size slide

  50. THE ASYNC ITERATOR PROTOCOL
    An object is an async iterator if it has a next() method.
    Every time you call it, it returns a promise that resolves to
    an object with the keys done (boolean) and value.
    loige 55

    View full-size slide

  51. import { setTimeout } from 'node:timers/promises'
    function createAsyncCountdown (from, delay = 1000) {
    let nextVal = from
    return {
    async next () {
    await setTimeout(delay)
    if (nextVal < 0) {
    return { done: true }
    }
    return { done: false, value: nextVal-- }
    }
    }
    }
    loige 56

    View full-size slide

  52. const countdown = createAsyncCountdown(3)
    console.log(await countdown.next())
    // { done: false, value: 3 }
    console.log(await countdown.next())
    // { done: false, value: 2 }
    console.log(await countdown.next())
    // { done: false, value: 1 }
    console.log(await countdown.next())
    // { done: false, value: 0 }
    console.log(await countdown.next())
    // { done: true }
    loige 57

    View full-size slide

  53. import { setTimeout } from 'node:timers/promises'
    // async generators "produce" async iterators!
    async function * createAsyncCountdown (from, delay = 1000) {
    for (let i = from; i >= 0; i--) {
    await setTimeout(delay)
    yield i
    }
    }
    loige 59

    View full-size slide

  54. THE ASYNC ITERABLE PROTOCOL
    An object is an async iterable if it implements the
    `Symbol.asyncIterator` method, a zero-argument
    function that returns an async iterator.
    loige 60

    View full-size slide

  55. import { setTimeout } from 'node:timers/promises'
    function createAsyncCountdown (from, delay = 1000) {
    return {
    [Symbol.asyncIterator]: async function * () {
    for (let i = from; i >= 0; i--) {
    await setTimeout(delay)
    yield i
    }
    }
    }
    }
    loige 61

    View full-size slide

  56. HOT TIP
    🔥
    With async generators we can create objects that are
    both async iterators and async iterables!
    (We don't need to specify
    Symbol.asyncIterator explicitly!)
    loige 62

    View full-size slide

  57. import { setTimeout } from 'node:timers/promises'
    // async generators "produce" async iterators
    // (and iterables!)
    async function * createAsyncCountdown (from, delay = 1000) {
    for (let i = from; i >= 0; i--) {
    await setTimeout(delay)
    yield i
    }
    }
    loige 63

    View full-size slide

  58. const countdown = createAsyncCountdown(3)
    for await (const value of countdown) {
    console.log(value)
    }
    loige 64

    View full-size slide

  59. 📝 MINI-SUMMARY 1/2
    loige
    Async iterators are the asynchronous counterpart of iterators.
    They are useful to iterate over data that becomes available asynchronously (e.g. coming from a
    database or a REST API).
    A good example is a paginated API, we could build an async iterator that gives a new page for
    every iteration.
    An object is an async iterator if it has a `next()` method which returns a `Promise` that resolves
    to an object with the shape: `{done, value}`.
    The main difference with the iterator protocol is that this time `next()` returns a promise.
    When we call next we need to make sure we `await` the returned promise.
    65

    View full-size slide

  60. 📝 MINI-SUMMARY 2/2
    loige
    The async iterable protocol defines what it means for an object to be an async iterable.
    Once you have an async iterable you can use the `for await ... of` syntax on it.
    An object is an async iterable if it has a special method called `Symbol.asyncIterator` that
    returns an async iterator.
    Async iterables are a great way to abstract paginated data that is available asynchronously or
    similar operations like pulling jobs from a remote queue.
    A small spoiler, async iterables can also be used with Node.js streams...
    66

    View full-size slide

  61. A REAL USE CASE & A CHALLENGE!
    loige
    Async Iterables are commonly used to abstract over paginated resources. Can you implement a
    paginator for the Rick & Morty API?
    function createCharactersPaginator () {
    // return an async iterable object that produces
    // pages with name of Rick and Morty characters
    // taken from the API
    // https://rickandmortyapi.com/api/character
    }
    // This is what we want to support
    👇
    const paginator = createCharactersPaginator()
    for await (const page of paginator) {
    console.log(page)
    }
    67

    View full-size slide

  62. IN CONCLUSION
    💥
    loige
    JavaScript has built-in sync/async iteratable/iterators
    These are great tools when you need to deal with large (or even endless) data sets
    They give you great control over how to consume data incrementally
    Iterator helpers are still lacking and not as mature as in other languages
    ... although, you should know enough to be able to create your own helpers now
    😜
    ... and you can leverage generator functions to make your life easier!
    ... or you can wait for & to land
    Iterator Helpers Async Iterator Helpers
    ... or you can use to polyfill these features today!
    core-js
    68

    View full-size slide

  63. BONUS: A FREE WORKSHOP FOR YOU!
    🎁
    loige
    loige.link/lets-iter-repo
    git clone https://github.com/lmammino/iteration-protocols-workshop.git
    cd iteration-protocols-workshop
    npm i
    69

    View full-size slide

  64. Front cover Photo by on
    Back cover photo by on
    Sam Barber Unsplash
    photonblast Unsplash
    fourtheorem.com
    THANKS!
    🙌
    loige
    nodejsdp.link
    70

    View full-size slide