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

Better Async Javascript

Better Async Javascript

Functional Reactive Programming, and how to better deal with asynchrony in Javascript. Why functional programming matters.

Ernesto García

November 06, 2015
Tweet

More Decks by Ernesto García

Other Decks in Technology

Transcript

  1. // Timers setTimeout(callback, 250); setInterval(callback, 250); // Websockets new Websocket(url,

    protocol). onmessage(msgCallback). onclose(closeCallback); // DOM Events $(element).on('click', callback); element.addEventListener('click', callback); // IndexedDB var request = indexedDB.open('dbname'); request.onerror = errorFn; request.onsuccess = successFn; // Promises $.ajax(options). then(successFn). fail(errorFn); Traditional Async JS
  2. • Different treatment to similar concepts. • Difficult to combine

    in complex scenarios. • The need to keep state (race conditions). • Forgetting to unsubscribe (memory leaks). • Hard to debug and handle errors. Traditional Async JS
  3. An observable can… • Emit values (0 to ∞) •

    Emit errors (0 to 1 or ∞)* • Signal the end of the stream (0 to 1) Time
  4. Iterable const list = [0, 1, 2, … 96, 97,

    98, 99]; list .map(x => x + 1) .filter(x => x % 2 === 0) .forEach(x => console.log(x)); => 2 => 4 => 6 => 8 => 10 … => 100 const keystrokes = Observable .fromEvent('#search', 'keyup'); keystrokes .map(event => event.target.value) .filter(str => str.length >= 3) .forEach(str => console.log(str)); => "hel" => "hell" => "hello" => "hello " => "hello w" … => "hello world" Observable
  5. Iteration PULLS data from source Source PUSHES data to iteration.

    Non-blocking execution Blocks execution const list = [0, 1, 2, … 96, 97, 98, 99]; list .map(x => x + 1) .filter(x => x % 2 === 0) .forEach(x => console.log(x)); => 2 => 4 => 6 => 8 => 10 … => 100 const keystrokes = Observable .fromEvent('#search', 'keyup'); keystrokes .map(event => event.target.value) .filter(str => str.length >= 3) .forEach(str => console.log(str)); => "hel" => "hell" => "hello" => "hello " => "hello w" … => "hello world"
  6. Think Functionally “OOP makes code understandable by encapsulating moving parts.

    Functional programming makes code understandable by minimizing moving parts.” Michael Feathers
  7. Observable .fromEvent('#search', 'keyup') .filter(e => e.key === 'Enter') .forEach(triggerSearch); Good

    Observable .fromEvent('#search', 'keyup') .forEach(e => { if (e.key === 'Enter') { triggerSearch(e); } }); Bad Think Functionally # $
  8. const drawingStarts = Observable.fromEvent(canvas, 'mousedown'); const mouseMoves = Observable.fromEvent(canvas, 'mousemove');

    const drawingEnds = Observable.fromEvent(canvas, 'mouseup') .merge(Observable.fromEvent(canvas, 'mouseleave')); const drawingMoves = drawingStarts .flatMap(e => mouseMoves.takeUntil(drawingEnds)) drawingMoves .forEach(e => drawPoint(canvas, e.pageX, e.pageY)); Time
  9. const syncResults = Observable .fromArray(items) .flatMap(item => Observable .fromPromise(item.sync()) .map(response

    => {response, item, success: true}) .catch(error => {error, item, success: false})) .map((result, index) => $.extend(result, {progress: [index+1, items.length]})); => { success: true, progress: [1, 7], … } => { success: false, progress: [2, 7], … } … => { success: true, progress: [7, 7], … }
  10. var currentRequest = null, reqCount = 0; $('input#search').on('keyup', e =>

    { const str = $(e.target).val(); if (str.length > 3) { currentRequest = $.ajax({ url: `/search?q=${str}`, reqCount: ++reqCount, beforeSend: function() { if (currentRequest !== null) { return false; } }, }). then(function(data) { if (this.reqCount === reqCount) { renderResults(data); } }). always(function() { currentRequest = null; }); } }); OLD SCHOOL
  11. const searchTerms = Observable .fromEvent('#search', 'keyup') .debounce(500 /* ms */)

    .map(event => event.target.value) .filter(str => str.length >= 3) .skipDuplicates(); const searchSuggestions = searchTerms .flatMapLatest(str => Observable .fromAjax(`/search?q=${str}`) .retry(3) .takeUntil(searchTerms)); searchSuggestions .forEach(renderResults); USING FRP
  12. 1 var timeoutId = null; 2 var clickCount = 0;

    3 $('#click-target').on('mouseup', () => { 4 clickCount++; 5 if (!timeoutId) { 6 timeoutId = setTimeout(() => { 7 if (clickCount > 1) { 8 console.log(clickCount); 9 } 10 timeoutId = null; 11 clickCount = 0; 12 }, 300 /* ms */); 13 } 14 }); 1 Observable.fromEvent('#click-target', 'mouseup'). 2 bufferEvery(300 /* ms */). 3 map(burst => burst.length). 4 filter(clickCount => clickCount > 1). 5 forEach(clickCount => console.log(clickCount));
  13. Observables Async Model • Uniform API for similar concepts. •

    Easier to compose more complex interactions. • No mutable state to maintain. • Less need to unsubscribe manually. • Simpler error handling and debugging.
  14. Use Everywhere (not just in JS) ReactiveX RxJava Kefir Bacon.js

    RxJS ReactiveUI React4J ReactiveCocoa ProAct.js Frappuccino FlapJax Elm Sodium Frappe Javascript Java Python Ruby Scala Clojure .NET C++ C# Groovy Cocoa OCaml Haskell Android iOS Node.js (it goes great with #ReactJS too)