ATL JS: Dive into RxJS Observables

ATL JS: Dive into RxJS Observables

94bd558238b69c45d3d3e15797ae94f7?s=128

Jeremy Fairbank

November 27, 2017
Tweet

Transcript

  1. Dive into RxJS Jeremy Fairbank @elpapapollo / jfairbank Observables

  2. Software is broken. We are here to fix it. Say

    hi@testdouble.com
  3. Async

  4. Async API Callback

  5. Callback fetchUserById(1, function(err, user) { if (err) { console.error('Could not

    retrieve user'); } else { console.log(user); } });
  6. }); }); }); }); }); }); Callback Callback Callback Callback

    Callback Callback
  7. function fetchCustomerNameForOrder(orderId, done, fail) { fetchOrder(orderId, function(err, order) { if

    (err) { logError(err); fail(err); } else { fetchCustomer( order.customerId, function(err, customer) { if (err) { logError(err); fail(err); } else { done(customer.name); } } ); } }); }
  8. function fetchCustomerNameForOrder(orderId, done, fail) { fetchOrder(orderId, function(err, order) { if

    (err) { logError(err); fail(err); } else { fetchCustomer( order.customerId, function(err, customer) { if (err) { logError(err); fail(err); } else { done(customer.name); } } ); } }); }
  9. function fetchCustomerNameForOrder(orderId, done, fail) { fetchOrder(orderId, function(err, order) { if

    (err) { logError(err); fail(err); } else { fetchCustomer( order.customerId, function(err, customer) { if (err) { logError(err); fail(err); } else { done(customer.name); } } ); } }); }
  10. Async API ?

  11. Promise function fetchCustomerNameForOrder(orderId) { return fetchOrder(orderId) .then(order => fetchCustomer(order.customerId)) .then(customer

    => customer.name); }
  12. .then(...) .then(...) .then(...) .then(...) .then(...) .then(...) .then(...) .then(...)

  13. Error Error Error Error

  14. function fetchCustomerNameForOrder(orderId) { return fetchOrder(orderId) .then(order => fetchCustomer(order.customerId)) .then(customer =>

    customer.name) .catch(err => { logError(err); throw err; }); }
  15. Getting there…

  16. • More readable and maintainable async code • Better error

    handling • More declarative and versatile syntax • Capable of handling events, streams, and HTTP
  17. RxJS Observables

  18. Arrays [ 1, 2, 3, 4, 5 ] Sequences in

    space
  19. Observables Sequences in time 1 2 3 4 5

  20. Reactive

  21. Reactive 3

  22. import { Observable } from 'rxjs'; const source = Observable.of(1,

    2, 3); source.subscribe(x => console.log(x)); // 1 // 2 // 3
  23. import { Observable } from 'rxjs'; const source = Observable.of(1,

    2, 3); source.subscribe(x => console.log(x)); // 1 // 2 // 3
  24. import { Observable } from 'rxjs'; const source = Observable.of(1,

    2, 3); source.subscribe(x => console.log(x)); // 1 // 2 // 3
  25. import { Observable } from 'rxjs'; const source = Observable.of(1,

    2, 3); source.subscribe(x => console.log(x)); // 1 // 2 // 3
  26. Observable.of(1, 2, 3) .subscribe(x => console.log(x)); console.log subscribe

  27. Observable.of(1, 2, 3) .subscribe(x => console.log(x)); console.log subscribe 1

  28. Observable.of(1, 2, 3) .subscribe(x => console.log(x)); console.log subscribe 1

  29. Observable.of(1, 2, 3) .subscribe(x => console.log(x)); console.log subscribe

  30. Observable.of(1, 2, 3) .subscribe(x => console.log(x)); console.log subscribe 2

  31. Observable.of(1, 2, 3) .subscribe(x => console.log(x)); console.log subscribe 2

  32. Observable.of(1, 2, 3) .subscribe(x => console.log(x)); console.log subscribe

  33. Observable.of(1, 2, 3) .subscribe(x => console.log(x)); console.log subscribe 3

  34. Observable.of(1, 2, 3) .subscribe(x => console.log(x)); console.log subscribe 3

  35. Declarative Transformation Operate on events

  36. Observable.of(1, 2, 3) .map(n => n * 2) .subscribe(x =>

    console.log(x)); // 2 // 4 // 6
  37. Observable.of(1, 2, 3) .map(n => n * 2) .subscribe(x =>

    console.log(x)); // 2 // 4 // 6
  38. Observable.of(1, 2, 3) console.log n * 2 map subscribe

  39. Observable.of(1, 2, 3) console.log n * 2 map subscribe 1

  40. Observable.of(1, 2, 3) console.log n * 2 map subscribe 1

  41. Observable.of(1, 2, 3) console.log n * 2 map subscribe 2

  42. Observable.of(1, 2, 3) console.log n * 2 map subscribe 2

  43. Observable.of(1, 2, 3) console.log n * 2 map subscribe

  44. Observable.of(1, 2, 3) console.log n * 2 map subscribe 2

  45. Observable.of(1, 2, 3) console.log n * 2 map subscribe 2

  46. Observable.of(1, 2, 3) console.log n * 2 map subscribe 4

  47. Observable.of(1, 2, 3) console.log n * 2 map subscribe 4

  48. Observable.of(1, 2, 3) console.log n * 2 map subscribe

  49. Observable.of(1, 2, 3) console.log n * 2 map subscribe 3

  50. Observable.of(1, 2, 3) console.log n * 2 map subscribe 3

  51. Observable.of(1, 2, 3) console.log n * 2 map subscribe 6

  52. Observable.of(1, 2, 3) console.log n * 2 map subscribe 6

  53. Lazy Transformation Do only as much work as needed

  54. Observable.range(1, 100) .map(n => n * 2) .filter(n => n

    > 4) .take(2) .subscribe(x => console.log(x)); // 6 // 8
  55. Observable.range(1, 100) .map(n => n * 2) .filter(n => n

    > 4) .take(2) .subscribe(x => console.log(x)); // 6 // 8
  56. Observable.range(1, 100) .map(n => n * 2) .filter(n => n

    > 4) .take(2) .subscribe(x => console.log(x)); // 6 // 8
  57. Observable.range(1, 100) .map(n => n * 2) .filter(n => n

    > 4) .take(2) .subscribe(x => console.log(x)); // 6 // 8
  58. Observable.range(1, 100) .map(n => n * 2) .filter(n => n

    > 4) .take(2) .subscribe(x => console.log(x)); // 6 // 8
  59. console.log n * 2 map subscribe n > 4 Observable.range(1,

    100) filter 2 take
  60. console.log n * 2 map subscribe n > 4 1

    Observable.range(1, 100) filter 2 take
  61. console.log n * 2 map subscribe n > 4 1

    Observable.range(1, 100) filter 2 take
  62. console.log n * 2 map subscribe n > 4 2

    Observable.range(1, 100) filter 2 take
  63. console.log n * 2 map subscribe n > 4 2

    Observable.range(1, 100) filter 2 take
  64. console.log n * 2 map subscribe n > 4 2

    Observable.range(1, 100) filter 2 take ×
  65. console.log n * 2 map subscribe n > 4 Observable.range(1,

    100) filter 2 take
  66. console.log n * 2 map subscribe n > 4 2

    Observable.range(1, 100) filter 2 take
  67. console.log n * 2 map subscribe n > 4 2

    Observable.range(1, 100) filter 2 take
  68. console.log n * 2 map subscribe n > 4 4

    Observable.range(1, 100) filter 2 take
  69. console.log n * 2 map subscribe n > 4 Observable.range(1,

    100) filter 2 take 4
  70. console.log n * 2 map subscribe n > 4 Observable.range(1,

    100) filter 2 take 4 ×
  71. console.log n * 2 map subscribe n > 4 Observable.range(1,

    100) filter 2 take
  72. console.log n * 2 map subscribe n > 4 3

    Observable.range(1, 100) filter 2 take
  73. console.log n * 2 map subscribe n > 4 3

    Observable.range(1, 100) filter 2 take
  74. console.log n * 2 map subscribe n > 4 6

    Observable.range(1, 100) filter 2 take
  75. console.log n * 2 map subscribe n > 4 Observable.range(1,

    100) filter 2 take 6
  76. ✓ console.log n * 2 map subscribe n > 4

    Observable.range(1, 100) filter 2 take 6
  77. console.log n * 2 map subscribe n > 4 Observable.range(1,

    100) filter 2 take 6
  78. console.log n * 2 map subscribe n > 4 Observable.range(1,

    100) filter 1 take 6
  79. console.log n * 2 map subscribe n > 4 Observable.range(1,

    100) filter 1 take
  80. console.log n * 2 map subscribe n > 4 4

    Observable.range(1, 100) filter 1 take
  81. console.log n * 2 map subscribe n > 4 4

    Observable.range(1, 100) filter 1 take
  82. console.log n * 2 map subscribe n > 4 8

    Observable.range(1, 100) filter 1 take
  83. console.log n * 2 map subscribe n > 4 Observable.range(1,

    100) filter 1 take 8
  84. ✓ console.log n * 2 map subscribe n > 4

    Observable.range(1, 100) filter 1 take 8
  85. console.log n * 2 map subscribe n > 4 Observable.range(1,

    100) filter 1 take 8
  86. console.log n * 2 map subscribe n > 4 Observable.range(1,

    100) filter 0 take 8
  87. DOM Events

  88. let counter = 0; function updateCounter(n) { counter += n;

    counterEl.innerHTML = counter; } incrementBtn.addEventListener('click', () => { updateCounter(1); }); decrementBtn.addEventListener('click', () => { updateCounter(-1); });
  89. let counter = 0; function updateCounter(n) { counter += n;

    counterEl.innerHTML = counter; } incrementBtn.addEventListener('click', () => { updateCounter(1); }); decrementBtn.addEventListener('click', () => { updateCounter(-1); });
  90. Observable.fromEvent(incrementBtn, 'click') .mapTo(1) .scan((acc, curr) => acc + curr, 0)

    .subscribe((counter) => { counterEl.innerHTML = counter; });
  91. Observable.fromEvent(incrementBtn, 'click') .mapTo(1) .scan((acc, curr) => acc + curr, 0)

    .subscribe((counter) => { counterEl.innerHTML = counter; });
  92. Observable.fromEvent(incrementBtn, 'click') .mapTo(1) .scan((acc, curr) => acc + curr, 0)

    .subscribe((counter) => { counterEl.innerHTML = counter; });
  93. Observable.fromEvent(incrementBtn, 'click') .mapTo(1) .scan((acc, curr) => acc + curr, 0)

    .subscribe((counter) => { counterEl.innerHTML = counter; });
  94. Observable.fromEvent(incrementBtn, 'click') .mapTo(1) .scan((acc, curr) => acc + curr, 0)

    .subscribe((counter) => { counterEl.innerHTML = counter; });
  95. Observable.fromEvent(incrementBtn, 'click') .mapTo(1) .scan((acc, curr) => acc + curr, 0)

    .subscribe((counter) => { counterEl.innerHTML = counter; });
  96. Observable.fromEvent(incrementBtn, 'click') .mapTo(1) .scan((acc, curr) => acc + curr, 0)

    .subscribe((counter) => { counterEl.innerHTML = counter; });
  97. .scan((acc, curr) => acc + curr, 0)

  98. .scan((acc, curr) => acc + curr, 0) Accumulated counter value

  99. .scan((acc, curr) => acc + curr, 0) Current event value

    (1)
  100. .scan((acc, curr) => acc + curr, 0) Return new accumulated

    counter value
  101. .scan((acc, curr) => acc + curr, 0) Initial counter value

  102. counterEl.innerHTML = counter acc + curr 1 mapTo subscribe 0

    scan
  103. acc + curr 1 mapTo subscribe e scan 0 counterEl.innerHTML

    = counter
  104. 1 mapTo subscribe e scan acc + curr 0 counterEl.innerHTML

    = counter
  105. 1 mapTo subscribe 1 scan acc + curr 0 counterEl.innerHTML

    = counter
  106. 1 mapTo subscribe scan 1 acc + curr 0 counterEl.innerHTML

    = counter
  107. 1 mapTo subscribe scan 1 acc + curr 1 Counter

    value counterEl.innerHTML = counter
  108. 1 mapTo subscribe scan 1 acc + curr 1 counterEl.innerHTML

    = counter
  109. acc + curr 1 mapTo subscribe scan 1 counterEl.innerHTML =

    counter
  110. acc + curr 1 mapTo subscribe e scan 1 counterEl.innerHTML

    = counter
  111. 1 mapTo subscribe e scan acc + curr 1 counterEl.innerHTML

    = counter
  112. 1 mapTo subscribe 1 scan acc + curr 1 counterEl.innerHTML

    = counter
  113. 1 mapTo subscribe scan 1 acc + curr 1 counterEl.innerHTML

    = counter
  114. 1 mapTo subscribe scan 2 acc + curr 2 Counter

    value counterEl.innerHTML = counter
  115. 1 mapTo subscribe scan 2 acc + curr 2 counterEl.innerHTML

    = counter
  116. const source = Observable.merge( Observable.fromEvent(incrementBtn, 'click').mapTo(1), Observable.fromEvent(decrementBtn, 'click').mapTo(-1), ); source

    .scan((acc, curr) => acc + curr, 0) .subscribe((counter) => { counterEl.innerHTML = counter; });
  117. const source = Observable.merge( Observable.fromEvent(incrementBtn, 'click').mapTo(1), Observable.fromEvent(decrementBtn, 'click').mapTo(-1), ); source

    .scan((acc, curr) => acc + curr, 0) .subscribe((counter) => { counterEl.innerHTML = counter; });
  118. const source = Observable.merge( Observable.fromEvent(incrementBtn, 'click').mapTo(1), Observable.fromEvent(decrementBtn, 'click').mapTo(-1), ); source

    .scan((acc, curr) => acc + curr, 0) .subscribe((counter) => { counterEl.innerHTML = counter; });
  119. const source = Observable.merge( Observable.fromEvent(incrementBtn, 'click').mapTo(1), Observable.fromEvent(decrementBtn, 'click').mapTo(-1), ); source

    .scan((acc, curr) => acc + curr, 0) .subscribe((counter) => { counterEl.innerHTML = counter; });
  120. const source = Observable.merge( Observable.fromEvent(incrementBtn, 'click').mapTo(1), Observable.fromEvent(decrementBtn, 'click').mapTo(-1), ); source

    .scan((acc, curr) => acc + curr, 0) .subscribe((counter) => { counterEl.innerHTML = counter; });
  121. acc + curr 1 mapTo subscribe 0 scan -1 mapTo

    Increment Decrement counterEl.innerHTML = counter
  122. acc + curr 1 mapTo subscribe 0 scan -1 mapTo

    Increment Decrement e counterEl.innerHTML = counter
  123. acc + curr 1 mapTo subscribe 0 scan -1 mapTo

    Increment Decrement e counterEl.innerHTML = counter
  124. acc + curr 1 mapTo subscribe 0 scan -1 mapTo

    Increment Decrement 1 counterEl.innerHTML = counter
  125. acc + curr 1 mapTo subscribe 0 scan -1 mapTo

    Increment Decrement 1 counterEl.innerHTML = counter
  126. acc + curr 1 mapTo subscribe 1 scan -1 mapTo

    Increment Decrement 1 counterEl.innerHTML = counter
  127. acc + curr 1 mapTo subscribe 1 scan -1 mapTo

    Increment Decrement 1 counterEl.innerHTML = counter
  128. acc + curr 1 mapTo subscribe 1 scan -1 mapTo

    Increment Decrement counterEl.innerHTML = counter
  129. acc + curr 1 mapTo subscribe 1 scan -1 mapTo

    Increment Decrement e counterEl.innerHTML = counter
  130. acc + curr 1 mapTo subscribe 1 scan -1 mapTo

    Increment Decrement e counterEl.innerHTML = counter
  131. acc + curr 1 mapTo subscribe 1 scan -1 mapTo

    Increment Decrement -1 counterEl.innerHTML = counter
  132. acc + curr 1 mapTo subscribe 1 scan -1 mapTo

    Increment Decrement -1 counterEl.innerHTML = counter
  133. acc + curr 1 mapTo subscribe 0 scan -1 mapTo

    Increment Decrement 0 counterEl.innerHTML = counter
  134. acc + curr 1 mapTo subscribe 0 scan -1 mapTo

    Increment Decrement 0 counterEl.innerHTML = counter
  135. Async HTTP

  136. Promise fetchOrders() .then((orders) => { orders.forEach((order) => { console.log(order); });

    });
  137. Promise fetchOrders() .then((orders) => { orders.forEach((order) => { console.log(order); });

    });
  138. Observable fetchOrders() .subscribe((orders) => { orders.forEach((order) => { console.log(order); });

    });
  139. Why Observables?

  140. Promise fetchOrders() .then(orders => orders.filter( order => order.customerName === 'Tucker'

    )) .then(orders => orders.map(order => order.id)) .then((orderIds) => { orderIds.forEach(id => console.log(id)); });
  141. Promise fetchOrders() .then(orders => orders.filter( order => order.customerName === 'Tucker'

    )) .then(orders => orders.map(order => order.id)) .then((orderIds) => { orderIds.forEach(id => console.log(id)); });
  142. Promise fetchOrders() .then(orders => orders.filter( order => order.customerName === 'Tucker'

    )) .then(orders => orders.map(order => order.id)) .then((orderIds) => { orderIds.forEach(id => console.log(id)); });
  143. Promise fetchOrders() .then(orders => orders.filter( order => order.customerName === 'Tucker'

    )) .then(orders => orders.map(order => order.id)) .then((orderIds) => { orderIds.forEach(id => console.log(id)); });
  144. fetchOrders() .mergeAll() .filter( order => order.customerName === 'Tucker' ) .map(order

    => order.id) .subscribe(id => console.log(id)); Observable
  145. fetchOrders() .mergeAll() .filter( order => order.customerName === 'Tucker' ) .map(order

    => order.id) .subscribe(id => console.log(id)); Observable
  146. fetchOrders() .mergeAll() .filter( order => order.customerName === 'Tucker' ) .map(order

    => order.id) .subscribe(id => console.log(id)); Observable
  147. fetchOrders() .mergeAll() .filter( order => order.customerName === 'Tucker' ) .map(order

    => order.id) .subscribe(id => console.log(id)); Observable
  148. fetchOrders() .mergeAll() .filter( order => order.customerName === 'Tucker' ) .map(order

    => order.id) .subscribe(id => console.log(id)); Observable
  149. Cancellation

  150. const promise = fetchOrders() .then((orders) => { orders.forEach((order) => {

    console.log(order); }); }); promise.cancel(); Promise
  151. const promise = fetchOrders() .then((orders) => { orders.forEach((order) => {

    console.log(order); }); }); promise.cancel(); Promise
  152. const promise = fetchOrders() .then((orders) => { orders.forEach((order) => {

    console.log(order); }); }); promise.cancel(); Promise ×
  153. const subscription = fetchOrders() .subscribe((orders) => { orders.forEach((order) => {

    console.log(order); }); }); subscription.unsubscribe(); Observable
  154. const subscription = fetchOrders() .subscribe((orders) => { orders.forEach((order) => {

    console.log(order); }); }); subscription.unsubscribe(); Observable Cancel request
  155. Lazy Subscriptions

  156. const p1 = fetchOrders(); const p2 = fetchOrders(); const p3

    = fetchOrders(); Promises
  157. const p1 = fetchOrders(); const p2 = fetchOrders(); const p3

    = fetchOrders(); Promises Immediate
  158. Observables const o1 = fetchOrders(); const o2 = fetchOrders(); const

    o3 = fetchOrders(); o1.subscribe(); o2.subscribe(); o3.subscribe();
  159. Observables Lazy const o1 = fetchOrders(); const o2 = fetchOrders();

    const o3 = fetchOrders(); o1.subscribe(); o2.subscribe(); o3.subscribe();
  160. Observables Lazy const o1 = fetchOrders(); const o2 = fetchOrders();

    const o3 = fetchOrders(); o1.subscribe(); o2.subscribe(); o3.subscribe(); Issue Request
  161. const o1 = fetchOrders(); o1.subscribe(); o1.subscribe(); o1.subscribe();

  162. const o1 = fetchOrders(); o1.subscribe(); o1.subscribe(); o1.subscribe(); Pure, shareable value

  163. const o1 = fetchOrders(); o1.subscribe(); o1.subscribe(); o1.subscribe(); Issue new request

    with same observable Pure, shareable value
  164. Create HTTP Requests

  165. function fetchOrders() { return Observable.ajax.get('/orders'); } Built-in AJAX

  166. function fetchOrders() { return Observable.ajax.get('/orders'); } Built-in AJAX

  167. function fetchOrders() { return Observable.create((subscriber) => { fetchOrdersFromDb((orders) => {

    subscriber.next(orders); subscriber.complete(); }); }); } Custom Observable Creation
  168. function fetchOrders() { return Observable.create((subscriber) => { fetchOrdersFromDb((orders) => {

    subscriber.next(orders); subscriber.complete(); }); }); } Custom Observable Creation
  169. function fetchOrders() { return Observable.create((subscriber) => { fetchOrdersFromDb((orders) => {

    subscriber.next(orders); subscriber.complete(); }); }); } Custom Observable Creation
  170. function fetchOrders() { return Observable.create((subscriber) => { fetchOrdersFromDb((orders) => {

    subscriber.next(orders); subscriber.complete(); }); }); } Custom Observable Creation
  171. function fetchOrders() { return Observable.create((subscriber) => { fetchOrdersFromDb((orders) => {

    subscriber.next(orders); subscriber.complete(); }); }); } Custom Observable Creation
  172. function fetchOrders() { return Observable.create((subscriber) => { fetchOrdersFromDb((orders) => {

    subscriber.next(orders); subscriber.complete(); }); }); } Custom Observable Creation
  173. function fetchOrders() { return Observable.create((subscriber) => { fetchOrdersFromDb((orders) => {

    subscriber.next(orders); subscriber.complete(); }); }); } Custom Observable Creation ?
  174. Observable.of(1, 2, 3) .subscribe(x => console.log(x));

  175. Observable.of(1, 2, 3) .subscribe(x => console.log(x)); Subscriber (or Observer)

  176. Observable.of(1, 2, 3) .subscribe({ next: x => console.log(x), }); Subscriber

    Object
  177. Observable.of(1, 2, 3) .subscribe({ next: x => console.log(x), complete: ()

    => console.log('Done!'), }); // 1 // 2 // 3 // Done!
  178. function fetchOrders() { return Observable.create((subscriber) => { fetchOrdersFromDb((orders) => {

    subscriber.next(orders); subscriber.complete(); }); }); } fetchOrders().subscribe({ next: x => console.log(x), complete: () => console.log('Done!'), });
  179. function fetchOrders() { return Observable.create((subscriber) => { fetchOrdersFromDb((orders) => {

    subscriber.next(orders); subscriber.complete(); }); }); } fetchOrders().subscribe({ next: x => console.log(x), complete: () => console.log('Done!'), });
  180. function fetchOrders() { return Observable.create((subscriber) => { fetchOrdersFromDb((orders) => {

    subscriber.next(orders); subscriber.complete(); }); }); } fetchOrders().subscribe({ next: x => console.log(x), complete: () => console.log('Done!'), });
  181. Error Handling

  182. const source = Observable.create((subscriber) => { subscriber.next(1); subscriber.error(new Error('Uh oh'));

    subscriber.next(2); }); source.subscribe({ next: x => console.log(x), complete: () => console.log('Done!'), error: e => console.error(e), }); // 1 // Error: Uh oh
  183. const source = Observable.create((subscriber) => { subscriber.next(1); subscriber.error(new Error('Uh oh'));

    subscriber.next(2); }); source.subscribe({ next: x => console.log(x), complete: () => console.log('Done!'), error: e => console.error(e), }); // 1 // Error: Uh oh
  184. const source = Observable.create((subscriber) => { subscriber.next(1); subscriber.error(new Error('Uh oh'));

    subscriber.next(2); }); source.subscribe({ next: x => console.log(x), complete: () => console.log('Done!'), error: e => console.error(e), }); // 1 // Error: Uh oh
  185. const source = Observable.create((subscriber) => { subscriber.next(1); subscriber.error(new Error('Uh oh'));

    subscriber.next(2); }); source.subscribe(x => console.log(x)); No error handler, so what happens?
  186. Error Error Error Error Promises

  187. const source = Observable.create((subscriber) => { subscriber.next(1); subscriber.error(new Error('Uh oh'));

    subscriber.next(2); }); source.subscribe(x => console.log(x)); No swallowed errors!
  188. const source = Observable.create((subscriber) => { subscriber.next(1); subscriber.error(new Error('Uh oh'));

    subscriber.next(2); }); source.subscribe({ next: x => console.log(x), complete: () => console.log('Done!'), error: e => console.error(e), }); // 1 // Error: Uh oh
  189. const source = Observable.create((subscriber) => { subscriber.next(1); subscriber.error(new Error('Uh oh'));

    subscriber.next(2); }); source.subscribe({ next: x => console.log(x), complete: () => console.log('Done!'), error: e => console.error(e), }); // 1 // Error: Uh oh Never called
  190. fetchOrders() .catch((e) => { logError(e); return Observable.of([]); }) .subscribe(x =>

    console.log(x)); Catching
  191. fetchOrders() .catch((e) => { logError(e); return Observable.of([]); }) .subscribe(x =>

    console.log(x)); Catching
  192. fetchOrders() .catch((e) => { logError(e); return Observable.of([]); }) .subscribe(x =>

    console.log(x)); Catching
  193. fetchOrders() .catch((e) => { logError(e); return Observable.of([]); }) .subscribe(x =>

    console.log(x)); Catching
  194. console.log catch subscribe fetchOrders() Observable.of([])

  195. console.log catch subscribe fetchOrders() Observable.of([]) ✓

  196. console.log catch subscribe fetchOrders() Observable.of([]) ✓

  197. console.log catch subscribe fetchOrders() Observable.of([]) ×

  198. console.log catch subscribe fetchOrders() Observable.of([]) ×

  199. console.log catch subscribe fetchOrders() Observable.of([]) []

  200. console.log catch subscribe fetchOrders() Observable.of([]) []

  201. fetchOrders() .catch((e) => { logError(e); return legacyFetchOrders() .catch((e2) => {

    logError(e2); return Observable.of([]); }) }) .subscribe(x => console.log(x)); Or delegate to legacy API…
  202. fetchOrders() .catch((e) => { logError(e); return legacyFetchOrders() .catch((e2) => {

    logError(e2); return Observable.of([]); }) }) .subscribe(x => console.log(x)); Or delegate to legacy API…
  203. fetchOrders() .catch((e) => { logError(e); return legacyFetchOrders() .catch((e2) => {

    logError(e2); return Observable.of([]); }) }) .subscribe(x => console.log(x)); Or delegate to legacy API…
  204. fetchOrders() .catch((e) => { logError(e); return legacyFetchOrders() .catch((e2) => {

    logError(e2); return Observable.of([]); }) }) .subscribe(x => console.log(x)); Or delegate to legacy API…
  205. console.log catch subscribe legacyFetchOrders() fetchOrders() catch Observable.of([])

  206. console.log catch subscribe legacyFetchOrders() fetchOrders() catch Observable.of([]) ✓

  207. console.log catch subscribe legacyFetchOrders() fetchOrders() catch Observable.of([]) ✓

  208. console.log catch subscribe legacyFetchOrders() fetchOrders() catch Observable.of([])

  209. console.log catch subscribe legacyFetchOrders() fetchOrders() catch Observable.of([]) ×

  210. console.log catch subscribe legacyFetchOrders() fetchOrders() catch Observable.of([]) ×

  211. console.log catch subscribe legacyFetchOrders() fetchOrders() catch Observable.of([]) ×

  212. console.log catch subscribe legacyFetchOrders() fetchOrders() catch Observable.of([]) × ✓

  213. console.log catch subscribe legacyFetchOrders() fetchOrders() catch Observable.of([]) × ✓

  214. console.log catch subscribe legacyFetchOrders() fetchOrders() catch Observable.of([])

  215. console.log catch subscribe legacyFetchOrders() fetchOrders() catch Observable.of([]) ×

  216. console.log catch subscribe legacyFetchOrders() fetchOrders() catch Observable.of([]) ×

  217. console.log catch subscribe legacyFetchOrders() fetchOrders() catch Observable.of([]) ×

  218. console.log catch subscribe legacyFetchOrders() fetchOrders() catch Observable.of([]) × ×

  219. console.log catch subscribe legacyFetchOrders() fetchOrders() catch Observable.of([]) × ×

  220. console.log catch subscribe legacyFetchOrders() fetchOrders() catch Observable.of([]) × × []

  221. Hot Cold vs.

  222. Observable creates the source. Cold

  223. Best for one-off unique requests. Cold

  224. function fetchOrders() { return Observable.create((subscriber) => { fetchOrdersFromDb((orders) => {

    subscriber.next(orders); subscriber.complete(); }); }); }
  225. function fetchOrders() { return Observable.create((subscriber) => { fetchOrdersFromDb((orders) => {

    subscriber.next(orders); subscriber.complete(); }); }); } Resource requested/created at subscription time
  226. WebSockets

  227. function ordersStream() { return Observable.create((subscriber) => { const url =

    'ws://example.com/orders'; const socket = new WebSocket(url); socket.addEventListener('message', (data) => { subscriber.next(data); }); }); }
  228. function ordersStream() { return Observable.create((subscriber) => { const url =

    'ws://example.com/orders'; const socket = new WebSocket(url); socket.addEventListener('message', (data) => { subscriber.next(data); }); }); }
  229. function ordersStream() { return Observable.create((subscriber) => { const url =

    'ws://example.com/orders'; const socket = new WebSocket(url); socket.addEventListener('message', (data) => { subscriber.next(data); }); }); }
  230. function ordersStream() { return Observable.create((subscriber) => { const url =

    'ws://example.com/orders'; const socket = new WebSocket(url); socket.addEventListener('message', (data) => { subscriber.next(data); }); }); }
  231. const stream = ordersStream(); stream.subscribe(x => console.log(x));

  232. subscriber .next(data) WebSocket console.log stream.subscribe(x => console.log(x)); Subscriber

  233. subscriber .next(data) ... WebSocket console.log stream.subscribe(x => console.log(x)); Subscriber

  234. subscriber .next(data) ... WebSocket console.log stream.subscribe(x => console.log(x)); Subscriber

  235. subscriber .next(data) ... WebSocket Subscriber console.log stream.subscribe(x => console.log(x));

  236. subscriber .next(data) ... WebSocket console.log stream.subscribe(x => console.log(x)); Subscriber

  237. None
  238. .subscribe(...)

  239. .subscribe(...) .subscribe(...)

  240. .subscribe(...) .subscribe(...) .subscribe(...)

  241. .subscribe(...) .subscribe(...) .subscribe(...) .subscribe(...)

  242. .subscribe(...) .subscribe(...) .subscribe(...) .subscribe(...) .subscribe(...)

  243. .subscribe(...) .subscribe(...) .subscribe(...) .subscribe(...) .subscribe(...) .subscribe(...)

  244. .subscribe(...) .subscribe(...) .subscribe(...) .subscribe(...) .subscribe(...) .subscribe(...) .subscribe(...)

  245. .subscribe(...) .subscribe(...) .subscribe(...) .subscribe(...) .subscribe(...) .subscribe(...) .subscribe(...) .subscribe(...)

  246. Hot Observable closes over the source.

  247. Hot Best for multicasting and sharing resources.

  248. const url = 'ws://example.com/orders'; const socket = new WebSocket(url); function

    ordersStream() { return Observable.create((subscriber) => { socket.addEventListener('message', (data) => { subscriber.next(data); }); }); }
  249. const url = 'ws://example.com/orders'; const socket = new WebSocket(url); function

    ordersStream() { return Observable.create((subscriber) => { socket.addEventListener('message', (data) => { subscriber.next(data); }); }); } Resource created outside subscription
  250. const url = 'ws://example.com/orders'; const socket = new WebSocket(url); function

    ordersStream() { return Observable.create((subscriber) => { socket.addEventListener('message', (data) => { subscriber.next(data); }); }); } Close over existing resource when subscribing
  251. const stream = ordersStream(); const sub1 = stream.subscribe(x => console.log(x));

    const sub2 = stream.subscribe(x => console.log(x));
  252. Shared stream of data const stream = ordersStream(); const sub1

    = stream.subscribe(x => console.log(x)); const sub2 = stream.subscribe(x => console.log(x));
  253. subscriber .next(event.data) WebSocket Subscriber console.log stream.subscribe(x => console.log(x))

  254. subscriber .next(event.data) ... WebSocket console.log stream.subscribe(x => console.log(x)) Subscriber

  255. subscriber .next(event.data) ... WebSocket console.log stream.subscribe(x => console.log(x)) Subscriber

  256. subscriber .next(event.data) ... WebSocket console.log stream.subscribe(x => console.log(x)) Subscriber

  257. subscriber .next(event.data) ... WebSocket console.log stream.subscribe(x => console.log(x)) Subscriber

  258. subscriber .next(event.data) WebSocket Subscriber console.log New Subscriber console.log stream.subscribe(...) stream.subscribe(...)

  259. subscriber .next(event.data) WebSocket console.log ... console.log stream.subscribe(...) stream.subscribe(...) Subscriber New

    Subscriber
  260. subscriber .next(event.data) WebSocket console.log ... console.log stream.subscribe(...) stream.subscribe(...) Subscriber New

    Subscriber
  261. subscriber .next(event.data) WebSocket console.log ... console.log ... stream.subscribe(...) stream.subscribe(...) Subscriber

    New Subscriber
  262. subscriber .next(event.data) ... WebSocket console.log ... console.log stream.subscribe(...) stream.subscribe(...) Subscriber

    New Subscriber
  263. function ordersStream() { return Observable.create((subscriber) => { const url =

    'ws://example.com/orders'; const socket = new WebSocket(url); socket.addEventListener('message', (data) => { subscriber.next(data); }); }) .share(); } Share It
  264. function ordersStream() { return Observable.create((subscriber) => { const url =

    'ws://example.com/orders'; const socket = new WebSocket(url); socket.addEventListener('message', (data) => { subscriber.next(data); }); }) .share(); } Share It
  265. function ordersStream() { return Observable.create((subscriber) => { const url =

    'ws://example.com/orders'; const socket = new WebSocket(url); socket.addEventListener('message', (data) => { subscriber.next(data); }); }) .share(); } Share It
  266. const stream = ordersStream(); const sub1 = stream.subscribe(x => console.log(x));

    const sub2 = stream.subscribe(x => console.log(x));
  267. Shared stream of data too const stream = ordersStream(); const

    sub1 = stream.subscribe(x => console.log(x)); const sub2 = stream.subscribe(x => console.log(x));
  268. Clean Up

  269. function ordersStream() { return Observable.create((subscriber) => { const url =

    'ws://example.com/orders'; const socket = new WebSocket(url); socket.addEventListener('message', (data) => { subscriber.next(data); }); return () => { socket.close(); }; }) .share(); }
  270. function ordersStream() { return Observable.create((subscriber) => { const url =

    'ws://example.com/orders'; const socket = new WebSocket(url); socket.addEventListener('message', (data) => { subscriber.next(data); }); return () => { socket.close(); }; }) .share(); } Called when all subscriptions unsubscribe
  271. function ordersStream() { return Observable.create((subscriber) => { const url =

    'ws://example.com/orders'; const socket = new WebSocket(url); socket.addEventListener('message', (data) => { subscriber.next(data); }); return () => { socket.close(); }; }) .share(); } Close socket, deallocate resources, etc.
  272. fetchOrders() .mergeAll() .filter( order => order.customerName === 'Tucker' ) .map(order

    => order.id) .subscribe(id => console.log(id)); Recall
  273. fetchOrders() .mergeAll() .filter( order => order.customerName === 'Tucker' ) .map(order

    => order.id) .subscribe(id => console.log(id)); ? Recall
  274. Observable.of([1, 2, 3]) .subscribe(x => console.log(x)); // [ 1, 2,

    3 ]
  275. Observable.of([1, 2, 3]) .subscribe(x => console.log(x)); // [ 1, 2,

    3 ]
  276. Observable.of([1, 2, 3]) .subscribe(x => console.log(x)); // [ 1, 2,

    3 ]
  277. Observable.of([1, 2, 3]) .mergeAll() .subscribe(x => console.log(x)); // 1 //

    2 // 3
  278. Observable.of([1, 2, 3]) .mergeAll() .subscribe(x => console.log(x)); // 1 //

    2 // 3 Flatten
  279. Observable.of([1, 2, 3]) .mergeAll() .subscribe(x => console.log(x)); // 1 //

    2 // 3
  280. subscribe mergeAll console.log [1, 2, 3]

  281. subscribe mergeAll console.log [1, 2, 3]

  282. subscribe mergeAll console.log [2, 3] 1

  283. subscribe mergeAll console.log [2, 3] 1

  284. subscribe mergeAll console.log [3] 2

  285. subscribe mergeAll console.log [3] 2

  286. subscribe mergeAll console.log 3

  287. subscribe mergeAll console.log 3

  288. Higher Order Observables

  289. Observable.of(1, 2, 3) .map(n => n * 2) .subscribe(x =>

    console.log(x)); // 2 // 4 // 6
  290. Observable.of(1, 2, 3) .map(n => n * 2) .subscribe(x =>

    console.log(x)); // 2 // 4 // 6 Delay?
  291. Observable.of(1, 2, 3) .map(n => Observable.of(n * 2)) .subscribe(x =>

    console.log(x)); // ScalarObservable { value: 2 } // ScalarObservable { value: 4 } // ScalarObservable { value: 6 }
  292. Observable.of(1, 2, 3) .map(n => Observable.of(n * 2)) .subscribe(x =>

    console.log(x)); // ScalarObservable { value: 2 } // ScalarObservable { value: 4 } // ScalarObservable { value: 6 }
  293. Observable.of(1, 2, 3) .map(n => Observable.of(n * 2)) .subscribe(x =>

    console.log(x)); // ScalarObservable { value: 2 } // ScalarObservable { value: 4 } // ScalarObservable { value: 6 }
  294. Observable.of(1, 2, 3) .map(n => Observable.of(n * 2)) .mergeAll() .subscribe(x

    => console.log(x)); // 2 // 4 // 6
  295. Observable.of(1, 2, 3) .map(n => Observable.of(n * 2)) .mergeAll() .subscribe(x

    => console.log(x)); // 2 // 4 // 6
  296. Observable.of(1, 2, 3) .mergeMap(n => Observable.of(n * 2)) .subscribe(x =>

    console.log(x)); // 2 // 4 // 6
  297. subscribe mergeMap console.log Observable.of(n * 2)

  298. subscribe mergeMap console.log Observable.of(n * 2) 1

  299. subscribe mergeMap console.log Observable.of(n * 2) 1

  300. subscribe mergeMap console.log Observable.of(n * 2) Observable.of(2) subscribe 2

  301. subscribe mergeMap console.log Observable.of(n * 2) Observable.of(2) subscribe 2

  302. subscribe mergeMap console.log Observable.of(n * 2) Observable.of(2) subscribe 2

  303. Observable.of(1) .delay(1000) .subscribe(x => console.log(x)) // <tick> // 1

  304. Observable.of(1) .delay(1000) .subscribe(x => console.log(x)) // <tick> // 1

  305. Observable.of(1) .delay(1000) .subscribe(x => console.log(x)) // <tick> // 1

  306. Observable.of(1) .delay(1000) .subscribe(x => console.log(x)) // <tick> // 1

  307. Observable.of(1, 2, 3) .mergeMap(n => ( Observable.of(n * 2).delay(1000) ))

    .subscribe(x => console.log(x)); // <tick> // 2 // 4 // 6
  308. Observable.of(1, 2, 3) .mergeMap(n => ( Observable.of(n * 2).delay(1000) ))

    .subscribe(x => console.log(x)); // <tick> // 2 // 4 // 6
  309. Observable.of(1, 2, 3) .mergeMap(n => ( Observable.of(n * 2).delay(1000) ))

    .subscribe(x => console.log(x)); // <tick> // 2 // 4 // 6
  310. Observable.of(1, 2, 3) .mergeMap(n => ( Observable.of(n * 2).delay(1000) ))

    .subscribe(x => console.log(x)); // <tick> // 2 // 4 // 6 ?
  311. Concurrency

  312. subscribe mergeMap console.log Observable.of(n * 2)

  313. subscribe mergeMap console.log Observable.of(n * 2) 1

  314. subscribe mergeMap console.log Observable.of(n * 2) 1

  315. subscribe mergeMap console.log Observable.of(n * 2) 2

  316. subscribe mergeMap console.log Observable.of(n * 2) 2 2

  317. subscribe mergeMap console.log Observable.of(n * 2) 2 2

  318. subscribe mergeMap console.log Observable.of(n * 2) 2 4

  319. subscribe mergeMap console.log Observable.of(n * 2) 3 2 4

  320. subscribe mergeMap console.log Observable.of(n * 2) 3 2 4

  321. subscribe mergeMap console.log Observable.of(n * 2) 2 4 6

  322. subscribe mergeMap console.log Observable.of(n * 2) 2 4 6

  323. subscribe mergeMap console.log Observable.of(n * 2) 4 6 2

  324. subscribe mergeMap console.log Observable.of(n * 2) 6 4

  325. subscribe mergeMap console.log Observable.of(n * 2) 6

  326. Observable.of(1, 2, 3) .mergeMap(n => ( Observable.of(n * 2).delay(1000) ),

    1) .subscribe(x => console.log(x)); // <tick> // 2 // <tick> // 4 // <tick> // 6
  327. Observable.of(1, 2, 3) .mergeMap(n => ( Observable.of(n * 2).delay(1000) ),

    1) .subscribe(x => console.log(x)); // <tick> // 2 // <tick> // 4 // <tick> // 6
  328. Observable.of(1, 2, 3) .mergeMap(n => ( Observable.of(n * 2).delay(1000) ),

    1) .subscribe(x => console.log(x)); // <tick> // 2 // <tick> // 4 // <tick> // 6
  329. Observable.of(1, 2, 3) .mergeMap(n => ( Observable.of(n * 2).delay(1000) ),

    1) .subscribe(x => console.log(x)); // <tick> // 2 // <tick> // 4 // <tick> // 6
  330. Observable.of(1, 2, 3) .mergeMap(n => ( Observable.of(n * 2).delay(1000) ),

    1) .subscribe(x => console.log(x)); // <tick> // 2 // <tick> // 4 // <tick> // 6
  331. Observable.of(1, 2, 3) .concatMap(n => ( Observable.of(n * 2).delay(1000) ))

    .subscribe(x => console.log(x)); // <tick> // 2 // <tick> // 4 // <tick> // 6
  332. subscribe concatMap console.log Observable.of(n * 2)

  333. subscribe console.log Observable.of(n * 2) 1 concatMap

  334. subscribe console.log Observable.of(n * 2) 1 concatMap

  335. subscribe console.log Observable.of(n * 2) 2 concatMap

  336. subscribe console.log Observable.of(n * 2) 2 concatMap

  337. subscribe console.log Observable.of(n * 2) 2 concatMap

  338. subscribe console.log Observable.of(n * 2) 2 concatMap

  339. subscribe console.log Observable.of(n * 2) 2 concatMap

  340. subscribe console.log Observable.of(n * 2) 4 concatMap

  341. subscribe console.log Observable.of(n * 2) 4 concatMap

  342. subscribe console.log Observable.of(n * 2) 4 concatMap

  343. subscribe console.log Observable.of(n * 2) 3 concatMap

  344. subscribe console.log Observable.of(n * 2) 3 concatMap

  345. subscribe console.log Observable.of(n * 2) 6 concatMap

  346. subscribe console.log Observable.of(n * 2) 6 concatMap

  347. subscribe console.log Observable.of(n * 2) 6 concatMap

  348. Rate Limiting

  349. Observable.of(1, 2, 3) .concatMap((id) => { const url = `/orders/${id}`;

    return Observable.ajax.get(url) .delay(1000); }) .pluck('response') .bufferCount(3) .subscribe(x => console.log(x)); // [ { id: '1', name: 'Order 1' }, // { id: '2', name: 'Order 2' }, // { id: '3', name: 'Order 3' } ]
  350. Observable.of(1, 2, 3) .concatMap((id) => { const url = `/orders/${id}`;

    return Observable.ajax.get(url) .delay(1000); }) .pluck('response') .bufferCount(3) .subscribe(x => console.log(x)); // [ { id: '1', name: 'Order 1' }, // { id: '2', name: 'Order 2' }, // { id: '3', name: 'Order 3' } ]
  351. Observable.of(1, 2, 3) .concatMap((id) => { const url = `/orders/${id}`;

    return Observable.ajax.get(url) .delay(1000); }) .pluck('response') .bufferCount(3) .subscribe(x => console.log(x)); // [ { id: '1', name: 'Order 1' }, // { id: '2', name: 'Order 2' }, // { id: '3', name: 'Order 3' } ]
  352. Observable.of(1, 2, 3) .concatMap((id) => { const url = `/orders/${id}`;

    return Observable.ajax.get(url) .delay(1000); }) .pluck('response') .bufferCount(3) .subscribe(x => console.log(x)); // [ { id: '1', name: 'Order 1' }, // { id: '2', name: 'Order 2' }, // { id: '3', name: 'Order 3' } ]
  353. Observable .ajax .get(url) subscribe console.log pluck bufferCount 'response' 3 concatMap

  354. Observable .ajax .get(url) subscribe concatMap console.log pluck bufferCount 'response' 3

    1
  355. Observable .ajax .get(url) subscribe concatMap console.log pluck bufferCount 'response' 3

  356. Observable .ajax .get(url) subscribe concatMap console.log pluck bufferCount 'response' 3

    R Rate limit next request
  357. Observable .ajax .get(url) subscribe concatMap console.log pluck bufferCount 'response' 3

    R
  358. Observable .ajax .get(url) subscribe concatMap console.log pluck bufferCount 'response' 3

    R
  359. Observable .ajax .get(url) subscribe concatMap console.log pluck bufferCount 'response' 3

    O1
  360. Observable .ajax .get(url) subscribe concatMap console.log pluck bufferCount 'response' 2

    O1
  361. Observable .ajax .get(url) subscribe concatMap console.log pluck bufferCount 'response' 2

    O1 +
  362. Observable .ajax .get(url) subscribe concatMap console.log pluck bufferCount 'response' 1

    O1 O2
  363. Observable .ajax .get(url) subscribe concatMap console.log pluck bufferCount 'response' 1

    O1 O2 +
  364. Observable .ajax .get(url) subscribe concatMap console.log pluck bufferCount 'response' 0

    O1 O2 O3
  365. Observable .ajax .get(url) subscribe concatMap console.log […] pluck bufferCount 'response'

    0
  366. Sequential vs. Parallel

  367. const promise = Promise.all([ fetchOrder(1), fetchOrder(2), fetchOrder(3), ]); promise.then(x =>

    console.log(x)); // [ { id: '1', name: 'Order 1' }, // { id: '2', name: 'Order 2' }, // { id: '3', name: 'Order 3' } ]
  368. const promise = Promise.all([ fetchOrder(1), fetchOrder(2), fetchOrder(3), ]); promise.then(x =>

    console.log(x)); // [ { id: '1', name: 'Order 1' }, // { id: '2', name: 'Order 2' }, // { id: '3', name: 'Order 3' } ]
  369. const source = Observable.forkJoin( fetchOrder(1), fetchOrder(2), fetchOrder(3) ); source.subscribe(x =>

    console.log(x)); // [ { response: { id: 1, name: 'Order 1' } }, // { response: { id: 2, name: 'Order 2' } }, // { response: { id: 3, name: 'Order 3' } } ]
  370. const source = Observable.forkJoin( fetchOrder(1), fetchOrder(2), fetchOrder(3) ); source.subscribe(x =>

    console.log(x)); // [ { response: { id: 1, name: 'Order 1' } }, // { response: { id: 2, name: 'Order 2' } }, // { response: { id: 3, name: 'Order 3' } } ]
  371. const source = Observable.forkJoin( fetchOrder(1), fetchOrder(2), fetchOrder(3) ); source.subscribe(x =>

    console.log(x)); // [ { response: { id: 1, name: 'Order 1' } }, // { response: { id: 2, name: 'Order 2' } }, // { response: { id: 3, name: 'Order 3' } } ] Observable or Promise
  372. fetchOrder(1) fetchOrder(2) fetchOrder(3) forkJoin subscribe console.log

  373. fetchOrder(1) fetchOrder(2) fetchOrder(3) forkJoin subscribe console.log R1

  374. fetchOrder(1) fetchOrder(2) fetchOrder(3) forkJoin R1 subscribe console.log

  375. fetchOrder(1) fetchOrder(2) fetchOrder(3) forkJoin R1 subscribe console.log R3

  376. fetchOrder(1) fetchOrder(2) fetchOrder(3) forkJoin R1 R3 subscribe console.log

  377. fetchOrder(1) fetchOrder(2) fetchOrder(3) forkJoin R1 R3 subscribe console.log R2

  378. fetchOrder(1) fetchOrder(2) fetchOrder(3) forkJoin R1 R2 R3 subscribe console.log

  379. fetchOrder(1) fetchOrder(2) fetchOrder(3) forkJoin subscribe console.log R1 R2 R3

  380. Tip of the iceberg

  381. • Declarative, lazy operations • Expressive event management • No

    more error swallowing • Rate limiting and concurrent processing
  382. • Declarative, lazy operations • Expressive event management • No

    more error swallowing • Rate limiting and concurrent processing ✓ ✓ ✓ ✓
  383. github.com/ReactiveX/rxjs reactivex.io/rxjs RxJS

  384. Observables ECMAScript Proposal:
 github.com/tc39/proposal-observable Another spec implementation:
 github.com/zenparsing/zen-observable

  385. Thanks! Jeremy Fairbank @elpapapollo / jfairbank Slides: bit.ly/rxjs-atl-js