Advertisement

The Strange World of Javascript and all its little Asynchronous Beasts

Software Craftsman at CleanCode
May. 16, 2014
Advertisement

More Related Content

Advertisement
Advertisement

The Strange World of Javascript and all its little Asynchronous Beasts

  1. The strange world of javascript and all its little asynchronous beasts
  2. Federico Galassi @federicogalassi http://federico.galassi.net
  3. I wrote this http://www.jsbestpractices.it
  4. asynchronous non-blocking event-driven confused! callbacks ...
  5. Rob Pike Concurrency is not Parallelism http://vimeo.com/49718712
  6. Rob Pike Concurrency is a way to structure a program by breaking it into pieces that can be executed independently
  7. Javascript is concurrent !
  8. All in one thread No it’s not. Joe Armstrong
  9. I want to disappear
  10. Unlike utopian worlds... I live in synchronous bliss!!
  11. Javascript can learn from concurrent languages
  12. Go has gophers
  13. Gophers block on channels c := make(chan int) go func() { for i := 0; i < 100; i++ { c <- i } }() go func() { for { i := <- c fmt.Println(i) } }()
  14. Erlang has actors
  15. Actors block on receive P = fun Producer(N, C) when N < 100 -> C ! {N, self()}, Producer(N + 1, C) end. C = fun Consumer() -> receive {N, Pid} -> io:format("received ~p from ~p~n", [N, Pid]), Consumer() end end. Cons = spawn(C). P(0, Cons).
  16. The gold rule is
  17. You must be able to BLOCK
  18. Javascript was concurrent by necessity Brendan Eich
  19. Key pressed Do nothing Button.onclickExec. Event queue Time Key pressed the Event Loop ClickKey pressed Click User click button.onclick = function() { element.style.color = "red" })
  20. Continuation passing style refuel() startEngine() takeOff() land() // ... done
  21. Continuation passing style refuel(function() { startEngine() takeOff() land() // ... done })
  22. Continuation passing style refuel(function() { startEngine(function() { takeOff() land() // ... done }) })
  23. Continuation passing style refuel(function() { startEngine(function() { takeOff(function() { land() // ... done }) }) })
  24. The Pyramid of Doom refuel(function() { startEngine(function() { takeOff(function() { land(function() { // ... done }) }) }) })
  25. The Pyramid of Doom
  26. Loss of control flow images.forEach(function(url) { var image = download(url) image.show() })
  27. Loss of control flow images.forEach(function(url) { download(url, function(image) { image.show() }) })
  28. Loss of control flow var showImages = function(images, callback) { var url = images.shift() if (url) { download(url, function(image) { image.show() showImages(images, callback) }) } else { callback() } })
  29. Loss of control flow
  30. Loss of error handling try { download(url, function(image) { image.show() }) } catch(e) { // never executed!! console.log("Cannot show image") }
  31. Sync/Async Ambiguity try { // is download Asynchronous?!?! download(url, function(image) { image.show() }) } catch(e) { // never executed!! console.log("Cannot show image") }
  32. Sync/Async Ambiguity try { // Is download Asynchronous?!?! download(url, function(image) { image.show() }) } catch(e) { // never executed!! console.log("Cannot show image") }
  33. It’s not really like this refuel(function() { startEngine(function() { takeOff(function() { land(function() { // ... done }) }) }) })
  34. refuel(function() { startEngine(function() { takeOff(function() { land(function() { // ... done }) }) }) }) refuel(function() { startEngine(function() { takeOff(function() { land(function() { // ... done }) }) }) }) refuel(function() { startEngine(function() { takeOff(function() { land(function() { // ... done }) }) }) }) More like this // ... After a while ...
  35. refuel(function() { startEngine(function() { takeOff(function() { land(function() { // ... done }) }) }) }) refuel(function() { startEngine(function() { takeOff(function() { land(function() { // ... done }) }) }) }) refuel(function() { startEngine(function() { takeOff(function() { land(function() { // ... done }) }) }) }) Where do we land? // ... After a while ... ??? ??? ???
  36. What do we know?
  37. When an async call completes I’m time warped back in time to the callback code then back to wherever I came from. I find this very difficult to understand http://joearms.github.io/2013/04/02/Red-and-Green-Callbacks.html Joe Armstrong
  38. It’s even worse, every javascript programmer who has a concurrent problem to solve must invent their own concurrency model Joe Armstrong http://joearms.github.io/2013/04/02/Red-and-Green-Callbacks.html
  39. In javascript you CAN’T BLOCK
  40. ES6 to rescue us!
  41. with Generators function* upTo(end) { for (var i = 0; i <= end; i++) { yield i } }
  42. Generators make iterators var counter = upTo(100) counter.next() // => Object {value: 0, done: false} counter.next() // => Object {value: 1, done: false} counter.next() // => Object {value: 2, done: false} // ... counter.next() // => Object {value: 99, done: false} counter.next() // => Object {value: undefined, done: true}
  43. function* upTo(end) { for (var i = 0; i <= end; i++) { yield i } } Generators remember execution stack restarts Here!
  44. Yield can receive values function* upTo(end) { for (var i = 0; i <= end; i++) { var newI = yield i if (newI) i = newI } } var counter = upTo(100) counter.next() // => Object {value: 0, done: false} counter.next() // => Object {value: 1, done: false} counter.next(10) // => Object {value: 11, done: false} counter.next() // => Object {value: 12, done: false}
  45. Yield can receive errors function* upTo(end) { for (var i = 0; i <= end; i++) { yield i } } var counter = upTo(100) counter.next() // => Object {value: 0, done: false} counter.next() // => Object {value: 1, done: false} counter.throw(new Error("argh")) // => Error: argh
  46. Yes, this is Blocking!!
  47. Blocking for sequence async(function*() { yield refuel() yield startEngine() yield takeOff() yield land() })
  48. Blocking for control flow async(function*() { images.forEach(function(url) { var image = yield download(url) image.show() }) })
  49. Blocking for error handling async(function*() { try { var image = yield download(url) image.show() } catch(e) { console.log("Cannot show image") } })
  50. What is async() ? https://github.com/kriskowal/q/tree/v1/examples/async-generators http://pag.forbeslindesay.co.uk/#/22 // Implementation by Lindesay Forbes function async(makeGenerator){ return function (){ var generator = makeGenerator.apply(this, arguments) function handle(result){ // { done: [Boolean], value: [Object] } if (result.done) return result.value return result.value.then(function (res){ return handle(generator.next(res)) }, function (err){ return handle(generator.throw(err)) }) } return handle(generator.next()) } }
  51. async is too complex
  52. Generators support http://kangax.github.io/compat-table/es6/#Generators_(yield)
  53. I think this is the way
  54. or there is the dark way
  55. inventing your own concurrency model Joe Armstrong
  56. it will be leaky
  57. Still you can build your little paradise
  58. Functional composition with Async.js https://github.com/caolan/async Caolan Mcmahon
  59. functions in Async.js function(callback) { download(url, function() { callback() }) }
  60. error handling the node.js way function(callback) { download(url, { success: function(result) { // success, null error then result callback(null, result) }, error: function(err) { // failure, error and no result callback(err) } }) }
  61. The Pyramid of Doom refuel(function() { startEngine(function() { takeOff(function() { land(function() { // ... done }) }) }) })
  62. Demolished with sequential composition async.series([ refuel, startEngine, takeOff, land ], function(err, result) { // done! })
  63. Pretty control flow images.forEach(function(url) { download(url, function(image) { image.show() }) })
  64. Pretty functional composition async.map(images, function(callback) { download(url, function(image) { callback(null, image) }) }, function(err, results) { results.forEach(function(image) { image.show() }) })
  65. Composition of composition async.waterfall([ function(callback) { async.map(files, fs.readFile, callback) }, function(contents, callback) { async.map(contents, countWords, callback) }, function(countedWords, callback) { async.reduce(countedWords, 0, sum, callback) }, ], function(err, totalWords) { // The number of words in files is totalWords })
  66. Composition of composition is the real power
  67. Functional lack of state is the weakness
  68. No decoupling async.series([ refuel, startEngine, takeOff, land ], function(err, result) { // done! }) call start callback binding
  69. async.series([ refuel, startEngine, takeOff, land ], function(err, result) { // done! share = result }) global variable to share No decoupling
  70. async.series([ refuel, startEngine, takeOff, land ], function(err, result) { // done! logger.log(err) stats.update(result) spinner.hide() }) divergent change No decoupling
  71. No decoupling is bad
  72. We need first class asynchronous calls to keep the state of the computation
  73. OOP composition with Promises
  74. Promise is an object representing an async computation var promise = new Promise(function(resolve, reject) { // do asynchronous computation ... if (succeeded) { resolve(result) } else { reject(new Error(err)) } }) http://promisesaplus.com https://www.promisejs.org/
  75. The computation will eventually produce an outcome var promise = new Promise(function(resolve, reject) { // do asynchronous computation ... if (succeeded) { resolve(result) } else { reject(new Error(err)) } }) http://promisesaplus.com https://www.promisejs.org/
  76. Promise has states var promise = new Promise(function(resolve, reject) { // do asynchronous computation ... if (succeeded) { resolve(result) } else { reject(new Error(err)) } }) pending fulfilled rejected
  77. Promise is thenable promise.then( function(result) { // promise fulfilled }, function(err) { // promise rejected } )
  78. Promise remembers its final state promise // after some time ... .then(function(result) { // fulfilled, result is "hello" }) // after a while ... promise .then(function(result) { // fulfilled, result still "hello" })
  79. Promise remembers its final state promise // after some time ... .then(function(result) { // fulfilled, result is "hello" }) // after a while ... promise .then(function(result) { // fulfilled, result still "hello" })
  80. Thenable are chainable promise .then(function() { /* do something */ }) .then(function() { /* do something */ }) .then(function() { /* do something */ }) .then(function() { /* do something */ })
  81. Sequential composition promise .then(refuel) .then(startEngine) .then(takeOff) .then(land)
  82. Pyramid demolished promise .then(refuel) .then(startEngine) .then(takeOff) .then(land)
  83. Promisify function after(time) { return new Promise(function(resolve) { setTimeout(resolve, time) }) } after(5000) .then(function() { // five seconds gone! })
  84. Promise propagation promise .then(function() { /* called immediately */ }) .then(function() { /* called immediately */ })
  85. Promise propagation promise .then(function() { return after(5000) }) // returns a promise .then(function() { /* called after 5 secs */ }) .then(function() { /* called after 5 secs */ })
  86. Promise propagation promise .then(function() { return 5 }) // returns a value .then(function(result) { /* result == 5 */ })
  87. Promise propagation promise .then(after(5000)) .then(function() { throw new Error("argh") }) // throws an error .then(function() { /* never called */ }) .then(null, function(err) { // err == Error(“argh”) })
  88. Lovely error handling promise .then(refuel) .then(startEngine) .then(takeOff) .then(land) .catch(function(err) { // deal with err })
  89. More composition var one = after(1000).then(function() { return 1 }) var two = after(2000).then(function() { return 2 }) // parallel, wait for all Promise.all([one, two]).then(function(result) { // after 2 seconds // result == [1, 2] }) // parallel, wins the first Promise.race([one, two]).then(function(result) { // after 1 second // result == 1 })
  90. Stateful joy
  91. Promises have limited vision
  92. Promises have limited vision setInterval(function() { // does success/error make sense? // what about next intervals ?? }, 1000)
  93. Promises have limited vision $button.on("click", function() { // does success/error make sense? // what about next events ?? })
  94. Streams are weird beasts to promises
  95. Reactive programming https://github.com/Reactive-Extensions/RxJS
  96. Eric Meijer Reactive programming
  97. Observables are collections over time
  98. Iterators are pull iterator.next() // => value iterator.next() // => value iterator.next() // => value // it can return an error iterator.next() // => Error("argh") // it can end iterator.hasNext() // => false iterator.next() // => null
  99. observable.subscribe( function(value) { // next value }, function(err) { // failure }, function() { // completed } ) Observables are push
  100. Observables can do setInterval interval(1000).subscribe( function(value) { // value == undefined // value == undefined // ... }, function(err) { // never called }, function() { // when the interval is cleared } )
  101. Observables can do events click("#button").subscribe( function(value) { // value == { x: 458, y: 788 } // value == { x: 492, y: 971 } // ... }, function(err) { // never called }, function() { // when click is unsubscribed } )
  102. Observables can do async calls download(url).subscribe( function(image) { // once! // image == Image object }, function(err) { // once! // if error }, function() { // once! // when either succeeded or failed }
  103. Observables are all-around
  104. OOP structure functional composition var mouseup = Rx.Observable.fromEvent(dragTarget, 'mouseup') var mousemove = Rx.Observable.fromEvent(document, 'mousemove') var mousedown = Rx.Observable.fromEvent(dragTarget, 'mousedown') var mousedrag = mousedown.selectMany(function (md) { // calculate offsets when mouse down var startX = md.offsetX, startY = md.offsetY // Calculate delta with mousemove until mouseup return mousemove.select(function (mm) { return { left: mm.clientX - startX, top: mm.clientY - startY } }).takeUntil(mouseup) }) // Update position var subscription = mousedrag.subscribe(function (pos) { dragTarget.style.top = pos.top + 'px' dragTarget.style.left = pos.left + 'px' })
  105. Powerful model
  106. Used at scale by Netflix https://github.com/Netflix/RxJava/wiki
  107. Take Away
  108. Block if you can
  109. Choose the concurrency model that fits you
  110. @federicogalassi
Advertisement