Upgrade to Pro — share decks privately, control downloads, hide ads and more …

A Unified Model of Asynchrony in JS

A Unified Model of Asynchrony in JS

We are all familiar with writing JS using callbacks, events, 'lifecycle' methods, and maybe even Promises and Streams. These are useful and familiar patterns, but they can be difficult to translate between, and it can be difficult to manually manage the state introduced by having multiple asynchronous data sources, such as DOM events, AJAX requests, and the events exposed by components elsewhere on the page (not to mention JS in a server environment). In this talk I introduce a style of functional reactive programming made possible by RxJS, and which I am trying to make more approachable in my library called Pipeline.js. I introduce the concept of a "Pipe", which is a generalized representation of an asynchronous data source which comes with a rich set of transformation and composition methods that handle common async patterns and help insulate you from the state you don't want to deal with manually.

Ryan Artecona

April 17, 2014
Tweet

Other Decks in Programming

Transcript

  1. A Unified Model of Asynchrony in JS An Intro to

    Pipeline.js and RxJS ! ! ⟚ Ryan Artecona
  2. Distinct patterns • Callbacks • Events (pub/sub, EventEmitter) • Streams

    • Promises • “Lifecycle” methods • Other ES6 generators, ES6 Object.observe, Web Workers (window.postMessage), library- specific flavors of each
  3. Overlapping behaviors "Give this function your async value(s) when it's

    ready" All support, with callback/Promise giving only a single value "Let this function know if something unexpected happens" All support, either with a special err parameter or a separate error channel "Let this function know when you are done sending stuff" Possible with all, either implicitly with a single value or on a separate channel "If I stop needing you, I will tell you" Usually supported by Events and Streams, otherwise "let the GC handle it"
  4. Pipe A Pipe is a push-driven, time-ordered collection of discrete

    values that can be iterated over, can error, can finish, and can be cancelled. A Pipe is like an Array, except you can’t index into it. A Pipe is like an event channel, except it has is own error and done streams, and it can be composed with others. A Pipe is like a Promise, except it can can send multiple values, it can be cancelled, and it can be lazy. A Pipe is like a Stream or an ES6 generator, except it is simpler than a Stream and more composable than both. *Backpressure is complex, but RxJS supports some experimental backpressure patterns.
  5. pipe.map() shapes = Pipe.of( , , , ); shapeTypes =

    shapes .map(function(shape) { return shape.type; });
  6. pipe.filter() shapes = Pipe.of( , , , ); circles =

    shapes .filter(function(shape) { return shape.type === ‘circle’; });
  7. next error done Attachment Called "attaching an Outlet", where an

    Outlet is a triple of next, error, and done handlers that disposes its handlers when they are no longer needed. To get values out of out of a Pipe, attach handlers for the events you want.
  8. next error done Attachment Called "attaching an Outlet", where an

    Outlet is a triple of next, error, and done handlers that disposes its handlers when they are no longer needed. To get values out of out of a Pipe, attach handlers for the events you want. bond
  9. next error done Attachment Called "attaching an Outlet", where an

    Outlet is a triple of next, error, and done handlers that disposes its handlers when they are no longer needed. var nums = Pipe.of(0, 1, 2, 3); ! nums.on({ bond: function(bond) {...}, next: function(num) {...}, error: function(err) {...}, done: function() {...} }); To get values out of out of a Pipe, attach handlers for the events you want. bond
  10. Lazy, Shared, Cold vs. Hot Lazy: the work doesn’t start

    until a Pipe is subscribed to Shared: slow-to-generate values can be cached and resent think memoization, or how resolved Promises behave Cold: values are generated specifically for each subscriber e.g. from an array each value for each subscriber flows through the full chain of operators Hot: values are sent to whoever is subscribed at the time e.g. UI/DOM events
  11. Wrap existing sources “I want one! How do I get

    one?” Ask for one There are will be APIs for DOM events, XHR requests Wrap one There are will be wrappers for node style callbacks, events, Promises, Streams *Wrappers try to be smart, but ultimately inherit the traits of what they wrap Create one Cold: Pipe.of(), Pipe.empty(), new Pipe(function(outlet){…}) Hot: Inlet, Pipe.proxyMethod()
  12. Typeahead $searchBox.piped(‘keyup’) .map( function(event) { return event.target.value; }) .throttle( 400

    /*ms*/) .dedupe() .mapTakingFromLatest( function(query) { return getSearchSuggestions(query) .map( function(response) { return response.suggestions; }) .takeUntil( $searchBox.piped(‘blur’) ); }) .takeUntil( $searchBox.piped(‘blur’) ) .on({ next: function(suggestions) { $typeahead.render(suggestions); }, done: function() { $typeahead.hide(); } });
  13. LivePostActivity var post = this; ! var inView = Pipe.proxyMethod(post,

    ‘scrolledIntoView’) .mapReplace(true); ! var outOfView = Pipe.proxyMethod(post, ‘scrolledOutOfView’) .mapReplace(false); ! var shouldReceiveUpdates = inView.mergeWith(outOfView); ! var postUpdates = new Pipe(function() { return pollUpdatesForPost( post.id, post.lastUpdated ); }) .autoAttachWhen(shouldReceiveUpdates); ! postUpdates.on({ next: function(update) { post.lastUpdated = update.timeSent; }); postUpdates .filter( function(update) { return update.type === ‘like’; }) .scan(post.likeCount, function(likes, update) { return likes + update.likeCountDelta; }) .on({ next: function(newLikeCount) { post.likeCount = newLikeCount; this.renderLikeCount(); }); ! postUpdates .filter( function(update) { return update.type === ‘comment’; }) .on({ next: function(update) { post.comments.push(update.newComment); this.renderComments(); }); • poll server for updates per post • only want updates for posts visible in the viewport
  14. Notes Pipeline.js is experimental, educational, and not quite usable right

    now. Though I’d love to have help! RxJS is several years old, stable, and actively developed. It’s used heavily at Netflix! + Backpressure is experimental, but supported + Pushing toward automatic coercion of native Promises – The API is really clunky – Includes a kitchen sink, among other appliances
  15. Pipeline.js RxJS Pipe Observable +1 Outlet Observer Inlet +2 Subject

    +3 Bond +1 Disposable +4 .on({next: fn, …}) .subscribe(nextFn, …) .map() .map() .filter() .filter() .scan() .scan() .concatMap(fn) .map(fn).concatAll() .zipWith() .zipArray() .takeUntilNext() .takeUntil() .mapTakingFromLatest() .flatMapLatest() .dedupe() .distinctUntilChanged() .mergeWith() .merge() .autoAttachWhen() .pausable() .proxyMethod() —- Class (+n subclasses) not yet implemented nonexistent
  16. Linkroll Pipeline.js
 https://github.com/ryanartecona/pipeline.js RxJS
 https://github.com/Reactive-Extensions/RxJS ReactiveCocoa (or RAC)
 https://github.com/ReactiveCocoa/ReactiveCocoa Learning

    resources: • READMEs, in order of helpfulness: RAC, Pipeline.js, RxJS • “Asynchronous JavaScript at Netflix”: 
 https://www.youtube.com/watch?v=XRYN2xt11Ek • FRP on iOS ebook:
 https://leanpub.com/iosfrp/ • RxJS Observable docs:
 https://github.com/Reactive-Extensions/RxJS/blob/master/doc/api/core/observable.md