Funkcionální a asynchronní programování v Node.js

Spuštění skriptu v Node.js

Vytvoříme soubor main.mjs a z příkazové řádky jej spustíme pomocí příkazu node main.mjs.

Proč .mjs?

Node.js pracuje se třemi příponami: .js, .mjs a .cjs. Přípona rozhoduje, jaký styl importování ostatních zdrojových kódů (když z jednoho JS souboru importuji jiný) je použit. .cjs je klasický CommonJS a .mjs je modernější způsob zvaný ECMAScript Modules, který budeme používat. .js je univerzální a záleží na nastavení v souboru package.json (bude vysvětleno v následujících hodinách).

Funkcionální programování

V JavaScriptu jsou funkce hodnoty, tudíž je můžeme ukládat do proměnných nebo předávat jiným funkcím jako argumenty.

function add(a, b) {
  return a + b
}

const subtract = (a, b) => {
  return a - b
}

const secti = add
const odecti = subtract

console.log(secti(1, 2)) // 3
console.log(odecti(4, 2)) // 2
const binaryOperation = (a, operation, b) => {
  return operation(a, b)
}

const add = (c, d) => {
  return c + d
}

console.log(binaryOperation(1, add, 2)) // 3

Funkce také mohou vracet jiné funkce:

const makeAdd = () => {
  return (a, b) => {
    return a + b
  }
}

const add = makeAdd()
console.log(add(1, 2)) // 3

Toto začne být zajímavé, pokud vnitřní funkce referencuje proměnnou vnější funkce – closure.

const makeCounter = () => {
  let i = 0

  return () => {
    i++
    console.log(i)
  }
}

const counter1 = makeCounter()
counter1() // 1
counter1() // 2

const counter2 = makeCounter()
counter2() // 1
counter1() // 3

Array.filter

const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9]

const evenNumbers = numbers.filter((number) => {
  return number % 2 == 0
})

console.log(evenNumbers) // [2, 4, 6, 8]

Alternativní zápis:

const evenNumbers = numbers.filter(number => number % 2 === 0)

Alternativní zápis:

const isEven = number => number % 2 === 0
const evenNumbers = numbers.filter(isEven)

Array.map

const people = [
  { firstName: 'Franta', lastName: 'Sádlo' },
  { firstName: 'Jirka', lastName: 'Máslo' },
  { firstName: 'Pepa', lastName: 'Vomáčka' },
]

const firstNames = people.map((person) => {
  return person.firstName
})

console.log(firstNames) // ['Franta', 'Jirka', 'Pepa']

Alternativní zápis:

const firstNames = people.map(person => person.firstName)
const numbers = [1, 2, 3, 4, 5, 6]

const squares = numbers.map((number) => {
  return `Čtverec ${number} je ${number * number}`
})

console.log(squares)

Array.find

const people = [
  { firstName: 'Franta', lastName: 'Sádlo' },
  { firstName: 'Jirka', lastName: 'Máslo' },
  { firstName: 'Pepa', lastName: 'Vomáčka' },
]

const pepaVomacka = people.find((person) => {
  return person.firstName === 'Pepa' && person.lastName === 'Vomáčka'
})

console.log(pepaVomacka) // { firstName: 'Pepa', lastName: 'Vomáčka' }

Asynchronní programování

Program pokračuje dále, zatímco nějaký úkol se vykonává na pozadí.

POZOR: Nejedná se o paralelní programování.

console.log('A')

setTimeout(() => {
  console.log('B')
}, 1000)

console.log('C')

Event loop

Event loop

console.log('A1')

setTimeout(() => {
  console.log('B1')

  setTimeout(() => {
    console.log('C1')

    setTimeout(() => {
      console.log('D1')
    }, 1000)
  }, 1000)
}, 1000)

setTimeout(() => {
  console.log('B2')

  setTimeout(() => {
    console.log('C2')
  }, 1000)
}, 1000)

Asynchronní funkce nemohou vrátit hodnotu pomocí return:

const addAfter1s = (a, b, callback) => {
  setTimeout(() => {
    callback(a + b)
  }, 1000)
}

const result = addAfter1s(1, 2, (result) => {
  return result
})

// Neexistuje způsob, jak zde získat výsledek po asynchronním sčítání
console.log(result) // undefined

Čtení pro pokročilé: http://journal.stuffwithstuff.com/2015/02/01/what-color-is-your-function/

Přečtení souboru z disku

import fs from 'fs'

fs.readFile('index.html', (err, data) => {
  if (err) {
    console.error(err.message)
  } else {
    console.log(data.toString())
  }
})

Perzistentní čítač (counter)

import fs from 'fs'

const file = 'count'

fs.readFile(file, (err, data) => {
  let count

  if (err) {
    count = 0
  } else {
    count = Number(data.toString())
  }

  console.log(count)

  fs.writeFile(file, String(count + 1), (err) => {
    if (err) {
      console.error(err.message)
    }
  })
})

Refaktorovaná verze:

import fs from 'fs'

const file = 'count'

getCount((count) => {
  console.log(count)

  fs.writeFile(file, String(count + 1), (err) => {
    if (err) {
      console.error(err.message)
    }
  })
})

function getCount(callback) {
  fs.readFile(file, (err, data) => {
    if (err) {
      callback(0)
    } else {
      callback(Number(data.toString()))
    }
  })
}