Luciano Mammino (@loige)
RomaJS - June 16, 2021
for i / for...in / for...of
while / do while
Array.forEach / Array.map / Array.flatMap / Array.reduce / Array.reduceRight  / Array.filter / Array.find / Array.findIndex / Array.entries / Array.values / Array.every / Array.some
Object.keys / Object.values / Object.entries
Iterators / Generators
Spread operator [...iterable]
Events / Streams
Async iterators / Async generators
for await...of
That was 28 different concepts! 😳
I'm Luciano (🇮🇹🍕🍝) 👋
👨💻 Senior Architect
Co-Author of Node.js Design Patterns 👉
Accelerated Serverless | AI as a Service | Platform Modernisation
WE ARE HIRING: Do you want to work with us?
const judokas = [
  'Driulis Gonzalez Morales',
  'Ilias Iliadis',
  'Tadahiro Nomura',
  'Anton Geesink',
  'Teddy Riner',
  'Ryoko Tani'
]
for (const judoka of judokas) {
  console.log(judoka)
}Driulis Gonzalez Morales
Ilias Iliadis
Tadahiro Nomura
Anton Geesink
Teddy Riner
Ryoko Tani
OUTPUT
const judoka = 'Ryoko Tani'
for (const char of judoka) {
  console.log(char)
}
R
y
o
k
o 
T
a
n
i
OUTPUT
const medals = new Set([
  'gold',
  'silver',
  'bronze'
])
for (const medal of medals) {
  console.log(medal)
}
gold
silver
bronze
OUTPUT
const medallists = new Map([
  ['Teddy Riner', 33],
  ['Driulis Gonzalez Morales', 16],
  ['Ryoko Tani', 16],
  ['Ilias Iliadis', 15]
])
for (const [judoka, medals] of medallists) {
  console.log(`${judoka} has won ${medals} medals`)
}Teddy Riner has won 33 medals
Driulis Gonzalez Morales has won 16 medals
Ryoko Tani has won 16 medals
Ilias Iliadis has won 15 medals
OUTPUT
const medallists = {
  'Teddy Riner': 33,
  'Driulis Gonzalez Morales': 16,
  'Ryoko Tani': 16,
  'Ilias Iliadis': 15
}
for (const [judoka, medals] of Object.entries(medallists)) {
  console.log(`${judoka} has won ${medals} medals`)
}Teddy Riner has won 33 medals
Driulis Gonzalez Morales has won 16 medals
Ryoko Tani has won 16 medals
Ilias Iliadis has won 15 medals
OUTPUT
const medallists = {
  'Teddy Riner': 33,
  'Driulis Gonzalez Morales': 16,
  'Ryoko Tani': 16,
  'Ilias Iliadis': 15
}
for (const [judoka, medals] of medallists) {
  console.log(`${judoka} has won ${medals} medals`)
}for (const [judoka, medals] of medallists) {
                              ^
TypeError: medallists is not iterable
  at Object. (.../05-for-of-object.js:8:32)
ERROR
const countdown = [3, 2, 1, 0]
// spread into array
const from5to0 = [5, 4, ...countdown]
console.log(from5to0) // [ 5, 4, 3, 2, 1, 0 ]
// spread function arguments
console.log('countdown data:', ...countdown)
// countdown data: 3 2 1 0import {
  DynamoDBClient,
  paginateListTables
} from '@aws-sdk/client-dynamodb'
const client = new DynamoDBClient({});
for await (const page of paginateListTables({ client }, {})) {
  // page.TableNames is an array of table names
  for (const tableName of page.TableNames) {
    console.log(tableName)
  }
}In JavaScript, 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.
function createCountdown (start) {
  let nextVal = start
  return {
    next () {
      if (nextVal < 0) {
        return { done: true }
      }
      return {
        done: false,
        value: nextVal--
      }
    }
  }
}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 }An object is iterable if it implements the @@iterator* method, a zero-argument function that returns an iterator.
* Symbol.iterator
function createCountdown (start) {
  let nextVal = start
  return {
    [Symbol.iterator]: () => ({
      next () {
        if (nextVal < 0) {
          return { done: true }
        }
        return {
          done: false, 
          value: nextVal--
        }
      }
    })
  }
}
const countdown = createCountdown(3)
for (const value of countdown) {
  console.log(value)
}
// 3
// 2
// 1
// 0const iterableIterator = {
  next() {
    return { done: false, value: "hello" }
  },
  [Symbol.iterator]() {
    return this
  }
}iterableIterator.next()
// { done: false, value: "hello" }
for (const value of iterableIterator) {
 console.log(value)
}
// hello
// hello
// hello
// ...function * createCountdown (start) {
  for (let i = start; i >= 0; i--) {
    yield i
  }
}// As iterable
const countdown = createCountdown(3)
for (const value of countdown) {
 console.log(value)
}
// 3
// 2
// 1
// 0// As iterator
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 }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.
import { setTimeout } from 'timers/promises'
function createAsyncCountdown (start, delay = 1000) {
  let nextVal = start
  return {
    async next () {
      await setTimeout(delay)
      if (nextVal < 0) {
        return { done: true }
      }
      return { done: false, value: nextVal-- }
    }
  }
}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 }An object is an async iterable if it implements the @@asyncIterator* method, a zero-argument function that returns an async iterator.
* Symbol.asyncIterator
import { setTimeout } from 'timers/promises'
function createAsyncCountdown (start, delay = 1000) {
  return {
    [Symbol.asyncIterator]: function () {
      let nextVal = start
      return {
        async next () {
          await setTimeout(delay)
          if (nextVal < 0) {
            return { done: true }
          }
          return { done: false, value: nextVal-- }
        }
      }
    }
  }
}const countdown = createAsyncCountdown(3)
for await (const value of countdown) {
  console.log(value) // 3 ... 2 ... 1 ... 0
}import { setTimeout } from 'timers/promises'
async function * createAsyncCountdown (start, delay = 1000) {
  for (let i = start; i >= 0; i--) {
    await setTimeout(delay)
    yield i
  }
}const countdown = createAsyncCountdown(3)
for await (const value of countdown) {
  console.log(value) // 3 ... 2 ... 1 ... 0
}Sequential iteration pattern
Data arriving in order over time
You need to complete processing the current “chunk” before you can request the next one
Examples
paginated iteration
consuming tasks from a remote queue
import { createReadStream } from 'fs'
const sourceStream = createReadStream('bigdata.csv')
let bytes = 0
for await (const chunk of sourceStream) {
  bytes += chunk.length
}
console.log(`bigdata.csv: ${bytes} bytes`)import { createReadStream } from 'fs'
import { once } from 'events'
const sourceStream = createReadStream('bigdata.csv')
const destStream = new SlowTransform()
for await (const chunk of sourceStream) {
  const canContinue = destStream.write(chunk)
  if (!canContinue) {
    // backpressure, now we stop and we need to wait for drain
    await once(destStream, 'drain')
    // ok now it's safe to resume writing
  }
}import { pipeline } from 'stream/promises'
import { createReadStream, createWriteStream } from 'fs'
import { createBrotliCompress } from 'zlib'
const sourceStream = createReadStream('bigdata.csv')
const compress = createBrotliCompress()
const destStream = createWriteStream('bigdata.csv.br')
await pipeline(
  sourceStream,
  compress,
  destStream
)import { on } from 'events'
import glob from 'glob' // from npm
const matcher = glob('**/*.js')
for await (const [filePath] of on(matcher, 'match')) {
  console.log(filePath)
}import { on } from 'events'
import glob from 'glob' // from npm
const matcher = glob('**/*.js')
for await (const [filePath] of on(matcher, 'match')) {
  console.log(filePath)
}
// ⚠️  DANGER, DANGER (high voltage ⚡️): We'll never get here!
console.log('ALL DONE! :)')import { on } from 'events'
import glob from 'glob'
const matcher = glob('**/*.js')
const ac = new global.AbortController()
matcher.once('end', () => ac.abort())
try {
  for await (const [filePath] of on(matcher, 'match', { signal: ac.signal })) {
    console.log(`./${filePath}`)
  }
} catch (err) {
  if (!ac.signal.aborted) {
    console.error(err)
    process.exit(1)
  }
  // we ignore the AbortError
}
console.log('NOW WE GETTING HERE! :)') // YAY! 😻import { createServer } from 'http'
import { on } from 'events'
const server = createServer()
server.listen(8000)
for await (const [req, res] of on(server, 'request')) {
  res.end('hello dear friend')
}import { createServer } from 'http'
import { on } from 'events'
const server = createServer()
server.listen(8000)
for await (const [req, res] of on(server, 'request')) {
  // ... AS LONG AS WE DON'T USE await HERE, WE ARE FINE!
}import { createServer } from 'http'
import { on } from 'events'
import { setTimeout } from 'timers/promises'
const server = createServer()
server.listen(8000)
for await (const [req, res] of on(server, 'request')) {
  await setTimeout(1000)
  res.end('hello dear friend')
}import { createServer } from 'http'
import { setTimeout } from 'timers/promises'
const server = createServer(async function (req, res) {
  await setTimeout(1000)
  res.end('hello dear friend')
})
server.listen(8000)nodejsdesignpatterns.com - possibly a great book 😇
nodejsdesignpatterns.com/blog/javascript-async-iterators/ - In-depth article on all things iterators
loige.link/async-it - Finding a lost song with Node.js & async iterators
If you enjoyed this talk, you might also enjoy nodejsdp.link 😛