Skip to content

Latest commit

 

History

History
430 lines (305 loc) · 8.98 KB

03-async.md

File metadata and controls

430 lines (305 loc) · 8.98 KB

title: real world fp, 03 async theme: sudodoki/reveal-cleaver-theme style: https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.6.0/styles/zenburn.min.css controls: true

--

real world fp

#3 async

Monads, Comonad, Monoids, Setoids, Endofunctors.
Easy to understand, read, test and debug




by Vladimir Starkov
frontend engineer at Nordnet Bank AB

--

why async

'cause its single threaded

--

why

synchronous operation blocks:

  • ui in browser
  • other processes in node

--

what

  • network
  • filesystem

--

History

History

  • XMLHttpRequest
  • jquery ajax, deferred
  • nodejs callbacks
  • promises

--

XMLHttpRequest, meh

function reqListener () {
  console.log(this.responseText);
}

var oReq = new XMLHttpRequest();
oReq.addEventListener("load", reqListener);
oReq.open("GET", "http://www.example.org/example.txt");
oReq.send();

--

jquery ajax, deferred, better

$.getJSON('example.json')
  .done(() => console.log('success'))
  .fail(() => console.log('error'))
  .always(() => console.log('complete'));

--

nodejs callbacks, kind of good

fs.readFile('/etc/passwd', (err, data) => {
  if (err) throw err;
  console.log(data);
});

--

nodejs callbacks, good, but not really

nodejs callbacks, not good at all

loadImage('one.png', function(err, image1) {
  if (err) throw err;

  loadImage('two.png', function(err, image2) {
    if (err) throw err;

    loadImage('three.png', function(err, image3) {
      if (err) throw err;

      var images = [image1, image2, image3];
      console.log('All images loaded', images);
    });
  });
});

--

promises

api.get('/api/2/login')
  .then(data => console.log(data))
  .catch(err => console.error(err));

promises, much better

const allImages = [
  loadImage('one.png'),
  loadImage('two.png'),
  loadImage('three.png'),  
];

Promise.all(allImages)
  .then(images => {
    console.log('All images loaded!');
  });

--

why promises

Promises > callbacks

any (browser) API that involves networking e.g., <img src>, <a href>, XMLHttpRequest, @font-face, WebSocket) goes through Fetch
Fetch Standard 101

Fetch is promise based standard for network calls.

--

how to write code with promises

const getImagesByTopic = topic =>
  fetch(`/api/images/?topic=${topic}`)
    .then(res => res.json());

export default getImagesByTopic;
  • Note 0: always return functions
  • Note 1: reject only with new Error('err desc')
  • Note 2: be aware of throw and implicit catch

--

how to use promises

import getImagesByTopic from '../utils/getImagesByTopic';

getImageUrlsFromAPI('harambe')
  .then(image => { console.log('save harambe!') })
  .catch(err => console.error(err)); // error handling

note: handle errors when you actually using promises, not in implementation

--

Promise chain

// implementation
const api = url => fetch(url).then(res => res.json());

const isAuthenticated = () =>
  api('/api/status')
    .then(data => data.session_type)
    .then(type => type === 'authenticated');

// usage
isAuthenticated()
  .then(isAuthenticated => console.log(isAuthenticated))
  .catch(err => console.error(err));
  // error handling belongs only to implementation

--

Can we do it better?

Compose 'em all!

--

pipe, function composition recap

const double = x => x * 2;
const inc = x => x + 1;

const incAndDouble1 = x => double(inc(x));
const incAndDouble2 = x => pipe(inc, double)(x);
const incAndDouble3 = pipe(inc, double);

incAndDouble1(10); // 22
incAndDouble2(10); // 22
incAndDouble3(10); // 22

TL;DR: want to inc and double = pipe(inc, double)

--

PipeP to the rescue, in short

promise1(x).then(promise2)
equals
pipeP(promise1, promise2)(x)

--

PipeP to the rescue

  • Note: promised based function — function, which returns Promise, which resolves to some value
  • Takes several promise based functions or functions
  • Returns one promise based function which takes arguments
  • Invokes (left to right) each passed function one after another
  • Every next function will get calculation result of previous one
  • Note 1: left most function can take any arguments
  • Note 2: only left most function required to be promise based function

--

PipeP, (as a blackbox)

Human readable promises composition.

It just works™

// const api = url => fetch(url).then(res => res.json());
const api = pipeP(fetch, res => res.json()); // url => pipeP(…)(url);

/* const isAuthenticated = () =>
  api('/api/status')
    .then(data => data.session_type)
    .then(type => type === 'authenticated'); */
const isAuthenticated = pipeP(
  api('/api/status'),
  data => data.session_type,
  type => type === 'authenticated'
);

isAuthenticated()
  .then(isAuthenticated => console.log(isAuthenticated))
  .catch(err => console.error(err)); // error handling belongs to usage

--

Promises composition

For those, who are curious
And for the reference

// synchronous composition
// function composition (left to right)
const pipe = (headFN, ...restFns) => (...args) => restFns.reduce(
  (value, fn) => fn(value),
  headFN(...args),
);

// asynchronous composition
// promises composition (left to right)
const pipeP = (headPromiseFn, ...restPromiseFns) => (...args) => restPromiseFns.reduce(
  (promiseValue, promiseFn) => promiseValue.then(promiseFn),
  headPromiseFn(...args)
);

Note: as far as Promise chain can handle regular functions, pipeP can handle them as well

--

Async FP, network

import fetch from 'fetch';

const networkHello = R.pipeP(
  fetch,             // fetch url
  res => res.json(), // get json
  obj => obj.foo,
  str => str.toUpperCase() // convert to uppercase
)

const mockBin = 'http://mockbin.org/bin/bbe7f656-12d6-4877-9fa8-5cd61f9522a9?foo=bar&foo=baz';

networkHello(mockBin)
  .then(str => console.log(str)) // "HELLO WORLD!"
  .catch(err => console.error(err)); // error handling

--

Async FP, filesystem

import fs from 'fs';
const readFile = file => new Promise((resolve, reject) => {
  fs.readFile(file, { encoding: 'utf8' }, (err, res) => {
    (err) ? reject(err) : resolve(res);
  })
});

const fsHello = R.pipeP(// open file, convert content to upper case
  readFile,
  str => str.toUpperCase
);

// > less input.txt
// Hello World!
fsHello('input.txt')
  .then(str => console.log(str); ) // "HELLO WORLD!"
  .catch(err => console.error(err)); // error handling

--

Async FP, summary

/* const asyncOperation = val =>
  promiseFn1(val)
    .then(fn2)
    .then(fn3)
    .then(promiseFn4)
    .then(fn5); */

const asyncOperation = R.pipeP(
  promiseFn1,
  fn2,
  fn3,
  promiseFn4,
  fn5
);

asyncOperation('some')
  .then(result => console.log(result)) // your result
  .catch(err => console.error(err));   // error handling

--

Async FP, summary 2

// synchronous composition
const funPipe = pipe(
  fun1, fun2, fun3
);

// asynchronous composition
const promisePipe = pipeP(
  promiseFn1, promiseFn2, promiseFn3,
  // you can use regular functions here also
);

// usage
funPipe('some'); // result
promisePipe('some')
  .then(result => console.log(result)) // result
  .catch(err => console.error(err));   // error handling

--

Further reading

Functional Programming, (recursion)

"real world fp" workshop repo
"#3 async" slides

To be continued with "#4 contracts"

Stay tuned

--

real world fp

#3 async


*In functions we trust*

Sincerely yours Vladimir Starkov
@iamstarkov on github and twitter