Luciano Mammino PRO
Cloud developer, entrepreneur, fighter, butterfly maker! #nodejs #javascript - Author of https://www.nodejsdesignpatterns.com , Founder of https://fullstackbulletin.com
2023-09-20
{"requestId":"d2328daa...","level":"ERROR","timestamp":"2023-05-26T23:18:24.834Z","customerId":"01H1D5NGA5D689HD57CN5BZSG3","error":"ERR_SYS_FCKD"}
{"requestId":"c4fe5fdd...","level":"INFO","timestamp":"2023-05-26T23:18:29.966Z","customerId":"01H1D5MZAVPSXSXPTYADF4BDND","message":"transaction in progress"}
{"requestId":"69733d2e...","level":"ERROR","timestamp":"2023-05-26T23:18:33.570Z","customerId":"01H1D5MZAVPSXSXPTYADF4BDND","error":"ERR_SYS_FCKD"}
{"requestId":"2845fc48...","level":"ERROR","timestamp":"2023-05-26T23:18:40.489Z","customerId":"01H1D5KGHRV3SNJ71C4E920872","error":"ERR_SYS_FCKD"}
{"requestId":"3e3e49b5...","level":"ERROR","timestamp":"2023-05-26T23:18:50.277Z","customerId":"01H1D5KSG0K81CCAGTSTBXHFA6","error":"ERR_CLIENT_TIMEOUT"}
{"requestId":"f28abf98...","level":"ERROR","timestamp":"2023-05-26T23:18:56.575Z","customerId":"01H1D5K2JDVD941R6H67YEY0G8","error":"ERR_BUSY"}
{"requestId":"608e579f...","level":"ERROR","timestamp":"2023-05-26T23:19:04.529Z","customerId":"01H1D5KSG0K81CCAGTSTBXHFA6","error":"ERR_NOT_FOUND"}
{"requestId":"3125588e...","level":"ERROR","timestamp":"2023-05-26T23:19:11.514Z","customerId":"01H1D5MJ4XNJ580DMM8Y7T1HRW","error":"ERR_SERVER_TIMEOUT"}
{"requestId":"40fb0329...","level":"INFO","timestamp":"2023-05-26T23:19:13.536Z","customerId":"01H1D5N6HS2EMA900A2V6NQQ2Q","message":"user logged in"}
{"requestId":"564afe31...","level":"ERROR","timestamp":"2023-05-26T23:19:21.708Z","customerId":"01H1D5K2JDVD941R6H67YEY0G8","error":"ERR_CLIENT_TIMEOUT"}
import { readFile } from 'node:fs/promises'
const rawData = await readFile('logs.jsonl', 'utf-8')
const lines = rawData.split(/\n+/)
const messages = lines.map(line => line === '' ? {} : JSON.parse(line))
const errors = messages.filter(message => message.error === 'ERR_SYS_FCKD')
const errorsByCustomer = errors.reduce((acc, error) => {
if (!acc[error.customerId]) {
acc[error.customerId] = 0
}
acc[error.customerId]++
return acc
}, {})
console.log('Errors by customer:')
console.table(errorsByCustomer)
Errors by customer:
{
'01H1D5NGA5D689HD57CN5BZSG3': 109,
'01H1D5MZAVPSXSXPTYADF4BDND': 118,
'01H1D5KGHRV3SNJ71C4E920872': 118,
'01H1D5KSG0K81CCAGTSTBXHFA6': 95,
'01H1D5MJ4XNJ580DMM8Y7T1HRW': 103,
'01H1D5N6HS2EMA900A2V6NQQ2Q': 96,
'01H1D5M5RTKKK5SC4ADWAPK7Q0': 130,
'01H1D5K2JDVD941R6H67YEY0G8': 115,
'01H1D5KZ7GV7HK213XE3WV5GQX': 113
}
import { readFile } from 'node:fs/promises'
const rawData = await readFile('logs.jsonl', 'utf-8')
const lines = rawData.split(/\n+/)
const messages = lines.map(line => line === '' ? {} : JSON.parse(line))
const errors = messages.filter(message => message.error === 'ERR_SYS_FCKD')
const errorsByCustomer = errors.reduce((acc, error) => {
if (!acc[error.customerId]) {
acc[error.customerId] = 0
}
acc[error.customerId]++
return acc
}, {})
console.log('Errors by customer:')
console.table(errorsByCustomer)
import { readFile } from 'node:fs/promises'
const rawData = await readFile('logs.jsonl', 'utf-8')
const lines = rawData.split(/\n+/)
const messages = lines.map(line => line === '' ? {} : JSON.parse(line))
const errors = messages.filter(message => message.error === 'ERR_SYS_FCKD')
const errorsByCustomer = errors.reduce((acc, error) => {
if (!acc[error.customerId]) {
acc[error.customerId] = 0
}
acc[error.customerId]++
return acc
}, {})
console.log('Errors by customer:')
console.table(errorsByCustomer)
//
{
}
{"requestId":"d2328daa...","level":"ERROR","timestamp":"2023-05-26T23:18:24.834Z","customerId":"01H1D5NGA5D689HD57CN5BZSG3","error":"ERR_SYS_FCKD"}
{
}
{"requestId":"d2328daa...","level":"ERROR","timestamp":"2023-05-26T23:18:24.834Z","customerId":"01H1D5NGA5D689HD57CN5BZSG3","error":"ERR_SYS_FCKD"}
{
}
{"requestId":"d2328daa...","level":"ERROR","timestamp":"2023-05-26T23:18:24.834Z","customerId":"01H1D5NGA5D689HD57CN5BZSG3","error":"ERR_SYS_FCKD"}
{
"01H1D5NGA5D689HD57CN5BZSG3": 1
}
{"requestId":"c4fe5fdd...","level":"INFO","timestamp":"2023-05-26T23:18:29.966Z","customerId":"01H1D5MZAVPSXSXPTYADF4BDND","message":"transaction in progress"}
{
"01H1D5NGA5D689HD57CN5BZSG3": 1
}
{"requestId":"c4fe5fdd...","level":"INFO","timestamp":"2023-05-26T23:18:29.966Z","customerId":"01H1D5MZAVPSXSXPTYADF4BDND","message":"transaction in progress"}
{
"01H1D5NGA5D689HD57CN5BZSG3": 1
}
{"requestId":"69733d2e...","level":"ERROR","timestamp":"2023-05-26T23:18:33.570Z","customerId":"01H1D5MZAVPSXSXPTYADF4BDND","error":"ERR_SYS_FCKD"}
{
"01H1D5NGA5D689HD57CN5BZSG3": 1
}
{"requestId":"69733d2e...","level":"ERROR","timestamp":"2023-05-26T23:18:33.570Z","customerId":"01H1D5MZAVPSXSXPTYADF4BDND","error":"ERR_SYS_FCKD"}
{
"01H1D5NGA5D689HD57CN5BZSG3": 1
}
{"requestId":"69733d2e...","level":"ERROR","timestamp":"2023-05-26T23:18:33.570Z","customerId":"01H1D5MZAVPSXSXPTYADF4BDND","error":"ERR_SYS_FCKD"}
{
"01H1D5NGA5D689HD57CN5BZSG3": 1,
"01H1D5MZAVPSXSXPTYADF4BDND": 1
}
{"requestId":"2845fc48...","level":"ERROR","timestamp":"2023-05-26T23:18:40.489Z","customerId":"01H1D5NGA5D689HD57CN5BZSG3","error":"ERR_SYS_FCKD"}
{
"01H1D5NGA5D689HD57CN5BZSG3": 1,
"01H1D5MZAVPSXSXPTYADF4BDND": 1
}
{"requestId":"2845fc48...","level":"ERROR","timestamp":"2023-05-26T23:18:40.489Z","customerId":"01H1D5NGA5D689HD57CN5BZSG3","error":"ERR_SYS_FCKD"}
{
"01H1D5NGA5D689HD57CN5BZSG3": 1,
"01H1D5MZAVPSXSXPTYADF4BDND": 1
}
{"requestId":"2845fc48...","level":"ERROR","timestamp":"2023-05-26T23:18:40.489Z","customerId":"01H1D5NGA5D689HD57CN5BZSG3","error":"ERR_SYS_FCKD"}
{
"01H1D5NGA5D689HD57CN5BZSG3": 2,
"01H1D5MZAVPSXSXPTYADF4BDND": 1
}
const array = ['foo', 'bar', 'baz']
for (const item of array) {
console.log(item)
}
Prints all the items in the array!
Does it need to be an array? 🤔
Output:
foo
bar
baz
const str = 'foo'
for (const item of str) {
console.log(item)
}
Output:
f
o
o
const obj = {
foo: 'bar',
baz: 'qux'
}
for (const item of obj) {
console.log(item)
}
Output: ⛔️ Uncaught TypeError: obj is not iterable
OMG `for ... of`
does not work with plain objects! 😱
const array = ['foo', 'bar', 'baz']
console.log(...array)
Output:
foo bar baz
spread syntax!
function * myGenerator () {
// generator body
yield 'someValue'
// ... do more stuff
}
const genObj = myGenerator()
genObj.next() // -> { done: false, value: 'someValue' }
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 }
function * fruitGen () {
yield '🍑'
yield '🍉'
yield '🍋'
yield '🥭'
}
const fruitGenObj = fruitGen()
// generator objects are iterable!
for (const fruit of fruitGenObj) {
console.log(fruit)
}
// 🍑
// 🍉
// 🍋
// 🥭
const iterator = {
next () {
return {
done: false,
value: "someValue"
}
}
}
function createCountdown (from) {
let nextVal = from
return {
next () {
if (nextVal < 0) {
return { done: true }
}
return {
done: false,
value: nextVal--
}
}
}
}
A factory function that creates an iterator
returns an object
... which has a next() method
... which returns an object with
done & value
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 }
function createCountdown (from) {
let nextVal = from
return {
next () {
if (nextVal < 0) {
return { done: true }
}
return {
done: false,
value: nextVal--
}
}
}
}
function * createCountdown (from) {
for (let i = from; i >= 0; i--) {
yield i
}
}
Equivalent code using generators
const iterable = {
[Symbol.iterator] () {
return {
// iterator
next () {
return {
done: false,
value: "someValue"
}
}
}
}
}
function createCountdown (from) {
let nextVal = from
return {
[Symbol.iterator]: () => ({
next () {
if (nextVal < 0) {
return { done: true }
}
return { done: false, value: nextVal-- }
}
})
}
}
[...createCountdown(3)] // [3,2,1,0]
for (const value of createCountdown(3)) {
console.log(value)
}
// 3
// 2
// 1
// 0
function createCountdown (from) {
let nextVal = from
return {
[Symbol.iterator]: () => ({
next () {
if (nextVal < 0) {
return { done: true }
}
return {
done: false,
value: nextVal--
}
}
})
}
}
function * createCountdown (from) {
for (let i = from; i >= 0; i--) {
yield i
}
}
Equivalent code using generators
const iterableIterator = {
next () {
return { done: false, value: 'hello' }
},
[Symbol.iterator] () {
return this
}
}
Iterator protocol
Iterable protocol
const asyncIterator = {
async next () {
return {
done: false,
value: "someValue"
}
}
}
const asyncIterable = {
[Symbol.asyncIterator] () {
return {
// iterator
async next () {
return {
done: false,
value: "someValue"
}
}
}
}
}
import { setTimeout } from 'node:timers/promises'
async function * createAsyncCountdown (from, delay = 1000) {
for (let i = from; i >= 0; i--) {
await setTimeout(delay)
yield i
}
}
const countdown = createAsyncCountdown(3)
for await (const value of countdown) {
console.log(value)
}
import { createReadStream } from 'node:fs'
const readable = createReadStream(
'logs.jsonl',
{ encoding: 'utf-8' }
)
// a readable stream is an Async Iterable!
for await (const chunk of readable) {
// do something with a chunk of data
}
import { createReadStream } from 'node:fs'
const readable = createReadStream(
'logs.jsonl',
{ encoding: 'utf-8' }
)
// a readable stream is an Async Iterable!
for await (const chunk of readable) {
// do something with a chunk of data
}
A chunk is an arbitrary amount of data (not necessarily a line)
// utils/byline.js
export async function * byLine (asyncIterable) {
let remainder = ''
for await (const chunk of asyncIterable) {
const lines = (remainder + chunk).split(/\n+/)
remainder = lines.pop()
yield * lines
}
if (remainder.length > 0) {
yield remainder
}
}
import { createReadStream } from 'node:fs'
import { byLine } from './utils/byline.js'
const readable = createReadStream('logs.jsonl', { encoding: 'utf-8' })
const errorsByCustomer = {}
for await (const line of byLine(readable)) {
const message = line === '' ? {} : JSON.parse(line)
if (message.error === 'ERR_SYS_FCKD') {
if (!errorsByCustomer[message.customerId]) {
errorsByCustomer[message.customerId] = 0
}
errorsByCustomer[message.customerId]++
}
}
console.log('Errors by customer:')
console.log(errorsByCustomer)
import { createReadStream } from 'node:fs'
import { byLine } from './utils/byline.js'
const readable = createReadStream('logs.jsonl', { encoding: 'utf-8' })
const errorsByCustomer = {}
for await (const line of byLine(readable)) {
const message = line === '' ? {} : JSON.parse(line)
if (message.error === 'ERR_SYS_FCKD') {
if (!errorsByCustomer[message.customerId]) {
errorsByCustomer[message.customerId] = 0
}
errorsByCustomer[message.customerId]++
}
}
console.log('Errors by customer:')
console.log(errorsByCustomer)
Original Front cover photo by Jörg Angeli on Unsplash
Original Background photo by Michael Behrens on Unsplash
By Luciano Mammino
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!
Cloud developer, entrepreneur, fighter, butterfly maker! #nodejs #javascript - Author of https://www.nodejsdesignpatterns.com , Founder of https://fullstackbulletin.com