My evolving approach to the front end application development
JavaScript ninja, image processing expert, software quality fanatic
#CycleConf
event analysis and statistics for financial markets
Boston and New York
data
finished?
$http.get(...).then(...)
finished?
$http.get(...).then(...)
.then(...)
* times
plain object
plain object
prototype
var outer = { foo: 'bar' }
var inner = Object.create(outer)
JavaScript prototypes
We are not describing a "blueprint" for making objects. We are making objects!
inner.foo // 'bar'
function inner($scope) {
$scope.foo = 42
}
<div ng-controller="inner">
{{ foo }}
</div>
Angular code works with objects, not classes
function inner($scope) {
$scope.foo = 42
$scope.setFoo = function (v) {
$scope.foo = v
}
}
<div ng-controller="inner"
ng-click="setFoo(10)">
{{ foo }}
</div>
mixing in properties and methods
function inner($scope) {
$scope.foo = 42
$scope.setFoo = function (v) {
$scope.foo = v
}
}
$('div').scope().setFoo(2)
var set = $('div').scope().setFoo
set(100)
Promise.resolve(42).then(console.log)
Error: Illegal invocation
log: function () {
this.doSomething(...)
}
stream
stream operation
* times
stream
stream
stream
RxJS for the win!
stream
stream
Start with ES5 Array operations: map, filter, some, reduce
synchronous callbacks, finite list
synchronous callbacks, finite list
const out = [1, 2, 3]
.map(x => x * 2)
.filter(x > 3)
console.log(out) // [4, 6]
synchronous callbacks, finite list
result is returned from the call
const out = [1, 2, 3]
.map(x => x * 2)
.filter(x > 3)
console.log(out) // [4, 6]
asynchronous callbacks, infinite
Rx.Observable.from(...)
.map(x => x * 2)
.filter(x > 3)
.buffer(...)
item is passed forward
sequence / time methods
Rx.Observable.from(...)
.map(x => x * 2)
.filter(x > 3)
.buffer(...)
function add(a, b) {
return a + b
}
.subscribe(...)
add(2, 3)
[1, 2, 3].map(x => x * 2)
.forEach(console.log)
Error: Illegal invocation
[1, 2, 3].map(x => x * 2)
.forEach(console.log.bind(console))
function double(x) {
return x * 2
}
function action(op, list) {
return list.map(op)
}
// action(double, [1, 2, 3])
window.postMessage(double.toString())
const recreated = eval('(' + message + ')')
function double(x) {
return x * 2
}
function add(a, b) {
return a + b
}
....
....
const result = add(10, 4)
....
....
Referencial transparency
function add(a, b) {
return a + b
}
....
....
const result = 10 + 4
....
....
Referencial transparency
function add(a, b) {
return a + b
}
....
....
const result = 14
....
....
Referencial transparency
function expensive(x) {
// do something that takes a while
}
....
....
[1, 2, 3, 2, 2]
.map(R.memoize(expensive))
....
....
// expensive(1), expensive(2), expensive(3)
Memoization
var o = foo()
// hmm
o<method name>()
var fn = foo()
fn(...)
Using a method requires knowing about the object's structure (its API)
Using a function is simple - just call it.
function foo(a, b, c) {
**** a *** {
if (b < ****) {
}
*** x
}
}
function foo(a, b, c) {
var x = 3
**** a *** {
if (b < ****) {
}
*** x
}
}
const x = 10
function foo(a, b, c) {
**** a *** {
if (b < ****) {
}
*** x
}
}
function x(...) {} // pure
function foo(a, b, c) {
**** a *** {
if (b < ****) {
}
*** x
}
}
function foo(a, b, c) {
**** a *** {
if (b < ****) {
}
*** c
}
}
Wait, do I need to pass 10 arguments every time then?
function foo(a, b, c) { ... }
var fooA = foo.bind(null, valueA)
fooA(valueB, valueC)
function foo(a, b, c) { ... }
// (a, b, c) or (b, c, a) or ...
?
?
?
Place arguments most likely to be known first at the leftmost position
Takes advantage of the built-in JavaScript partial application
function mul(a, b) { return a * b }
const by10 = mul.bind(null, 10)
by10(8) // 80
const R = require('ramda')
const mul = R.curry((a, b) => a * b)
// by10 = mul.bind(null, 10)
const by10 = mul(10)
by10(8) // 80
f(g(h(x)))
// same as
const F = compose(f, g, h)
F(x)
const F = compose(f, g, h)
F(x)
Can a web application be pure?
function main() {
window.title = title
}
function controller($window) {
$window.title = title
}
function main() {
const title = 'hi'
return function foo() {
window.title = title
}
}
// pure
// dirty
const app = main()
app()
// pure
// dirty
function main(win) {
const title = 'hi'
return function foo() {
win.title = title
}
}
// pure
// dirty
const app = main()
app(window)
// pure
// dirty
function main({DOM}) {
const stream$ = DOM.select(...)
return {
DOM: stream$
}
}
Cycle.run(main, {
DOM: makeDomDriver...
})
// pure
// dirty
function main({DOM}) {
const stream$ = DOM.select(...)
return {
DOM: stream$
}
}
Cycle.run(main, {
DOM: makeDomDriver...
})
we have created streams from DOM to DOM - but they are NOT running yet
// DOM.stream$.subscribe(...)
function inner($scope) {
$scope.foo = 42
$scope.setFoo = function (v) {
$scope.foo = v
}
}
+ plain objects without "this"
- very impure behavior
- performance, learning curve
Solve the reactivity part of the data flow a little, but not the app's purity
Solves the efficient DOM update problem, but not the (async) data flow
React was chosen to solve performance and component development problems
function makeCodeCoverageDriver(serverUrl) {
const ws = new WebSocket(serverUrl)
return (outgoing$) => {
outgoing$.subscribe(outgoing => ws.send(outgoing))
return Rx.Observable.fromCallback(ws.onmessage)()
}
}
Uses WebSocket or WebRTC data channel
Links are coming ...
JS => FP => Reactive => Pure => Realtime
#CycleConf
these slides: slides.com/bahmutov/mvc-frp
all links: glebbahmutov.com/blog/cycle-conf