The Strange World of Javascript and all its little Asynchronous Beasts

  • 758 views
Uploaded on

Javascript is a wonderland populated by all kinds of exotic beasts. Callbacks, events, promises, functional programming, reactive programming, streams, generators are all part of this incredible …

Javascript is a wonderland populated by all kinds of exotic beasts. Callbacks, events, promises, functional programming, reactive programming, streams, generators are all part of this incredible asynchronous bestiary. We’re talking of insidious or even ferocious things, a great danger to the unwary programmer. Let’s look at them, learn from them, tame them and finally put them to our own advantage. Let’s stop Javascript from being an unfamiliar place and make it feel much more like home.

Talk I held on 14/05/2014 at JsDay, Verona, Italy. Corrected slides.
http://2014.jsday.it/talk/the-strange-world-of-javascript-and-all-its-little-asynchronous-beasts/

Feedback!
https://joind.in/talk/view/11280
Follow me on Twitter!
https://twitter.com/federicogalassi

More in: Technology , Business
  • Full Name Full Name Comment goes here.
    Are you sure you want to
    Your message goes here
    Be the first to comment
No Downloads

Views

Total Views
758
On Slideshare
0
From Embeds
0
Number of Embeds
6

Actions

Shares
Downloads
10
Comments
0
Likes
3

Embeds 0

No embeds

Report content

Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

Cancel
    No notes for slide

Transcript

  • 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