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 0
import {
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
// 0
const 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 😛