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
--
Monads, Comonad, Monoids, Setoids, Endofunctors.
Easy to understand, read, test and debug
by Vladimir Starkov
frontend engineer at Nordnet Bank AB
--
'cause its single threaded
--
synchronous operation blocks:
- ui in browser
- other processes in node
--
- network
- filesystem
--
- XMLHttpRequest
- jquery ajax, deferred
- nodejs callbacks
- promises
--
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();
--
$.getJSON('example.json')
.done(() => console.log('success'))
.fail(() => console.log('error'))
.always(() => console.log('complete'));
- almost deprecated in jquery 3.0 release,
- and now Promises/A+ compatible
--
fs.readFile('/etc/passwd', (err, data) => {
if (err) throw err;
console.log(data);
});
--
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);
});
});
});
--
api.get('/api/2/login')
.then(data => console.log(data))
.catch(err => console.error(err));
const allImages = [
loadImage('one.png'),
loadImage('two.png'),
loadImage('three.png'),
];
Promise.all(allImages)
.then(images => {
console.log('All images loaded!');
});
--
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.
--
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
--
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
--
// 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
--
--
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)
--
promise1(x).then(promise2)
equals
pipeP(promise1, promise2)(x)
--
- 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
--
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
--
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
--
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
--
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
--
/* 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
--
// 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
--
- What the heck is the event loop anyway?
- http://callbackhell.com/
- Fetch Standard 101
- mdn Promise
- promises Cookbook
- "You Don't Know JS: Async & Performance"
Chapter 3: Promises --
"real world fp" workshop repo
"#3 async" slides
To be continued with "#4 contracts"
Stay tuned
--
*In functions we trust*
Sincerely yours Vladimir Starkov
@iamstarkov on github and twitter