Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Speaker Deck
PRO
Sign in
Sign up
for free
JazzCon 2017: The Rise of Async JavaScript
Jeremy Fairbank
March 23, 2017
Programming
1
95
JazzCon 2017: The Rise of Async JavaScript
Jeremy Fairbank
March 23, 2017
Tweet
Share
More Decks by Jeremy Fairbank
See All by Jeremy Fairbank
jfairbank
0
71
jfairbank
1
75
jfairbank
1
210
jfairbank
0
110
jfairbank
0
120
jfairbank
0
23
jfairbank
2
190
jfairbank
1
89
jfairbank
2
610
Other Decks in Programming
See All in Programming
wasabeef
1
560
tommykw
1
320
nrslib
20
13k
makicamel
1
170
line_developers_tw2
0
610
nanimonodemonai
2
1.4k
komagata
1
1.8k
aratayokoyama
0
200
kyonmm
2
2.1k
yaamaa
0
410
e10dokup
0
440
daiki1020
0
1.1k
Featured
See All Featured
lemiorhan
626
42k
gr2m
83
11k
paulrobertlloyd
71
3.6k
orderedlist
PRO
329
35k
pauljervisheath
196
15k
zakiwarfel
88
3.3k
mojombo
358
62k
danielanewman
200
19k
bkeepers
408
57k
smashingmag
283
47k
productmarketing
5
650
zenorocha
297
39k
Transcript
the RISEof async JavaScript Jeremy Fairbank @elpapapollo | gh/jfairbank
sigient.com
Async API Callback
Callback fetchUserById(1, function(err, user) { if (err) { console.error('Could not
retrieve user'); } else { console.log(user); } });
}); }); }); }); }); }); Callback Callback Callback Callback
Callback Callback
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); } } ); } }); }
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); } } ); } }); }
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); } } ); } }); }
Async API ?
Promise function fetchCustomerNameForOrder(orderId) { return fetchOrder(orderId) .then(order => fetchCustomer(order.customerId)) .then(customer
=> customer.name); }
.then(...) .then(...) .then(...) .then(...) .then(...) .then(...) .then(...) .then(...)
Error Error Error Error
function fetchCustomerNameForOrder(orderId) { return fetchOrder(orderId) .then(order => fetchCustomer(order.customerId)) .then(customer =>
customer.name) .catch(err => { logError(err); throw err; }); }
Getting there…
• More readable and maintainable • Synchronous-looking but nonblocking •
Use native flow control constructs • More declarative and versatile
async function fetchCustomerNameForOrder(orderId) { try { const order = await
fetchOrder(orderId); const customer = await fetchCustomer(order.customerId); return customer.name; } catch (err) { logError(err); throw err; } }
async function fetchCustomerNameForOrder(orderId) { try { const order = await
fetchOrder(orderId); const customer = await fetchCustomer(order.customerId); return customer.name; } catch (err) { logError(err); throw err; } }
async function fetchCustomerNameForOrder(orderId) { try { const order = await
fetchOrder(orderId); const customer = await fetchCustomer(order.customerId); return customer.name; } catch (err) { logError(err); throw err; } }
async function fetchCustomerNameForOrder(orderId) { try { const order = await
fetchOrder(orderId); const customer = await fetchCustomer(order.customerId); return customer.name; } catch (err) { logError(err); throw err; } }
Await
function fetchOrder(orderId) { return fetch(`/orders/${orderId}`) .then(response => response.json()); } function
printOrder(orderId) { fetchOrder(orderId) .then(order => console.log(order)); }
function fetchOrder(orderId) { return fetch(`/orders/${orderId}`) .then(response => response.json()); } async
function printOrder(orderId) { const order = await fetchOrder(orderId); console.log(order); }
async printOrder(orderId) { const order = await fetchOrder(orderId); console.log(order); }
fetchOrder(orderId) order
async printOrder(orderId) { const order = await fetchOrder(orderId); console.log(order); }
await
async printOrder(orderId) { const order = await fetchOrder(orderId); console.log(order); }
await order
await order
await order
async printOrder(orderId) { const order = await fetchOrder(orderId); console.log(order); }
?
async function printOrder(orderId) { const order = await fetchOrder(orderId); console.log(order);
} printOrder(1); Invoke
async function printOrder(orderId) { const order = await fetchOrder(orderId); console.log(order);
} printOrder(1); Encounter await
async function printOrder(orderId) { const order = await fetchOrder(orderId); console.log(order);
} printOrder(1); Wrap in Promise.resolve
Wrap in Promise.resolve async function printOrder(orderId) { const promise =
Promise.resolve(fetchOrder(orderId)); console.log(order); } printOrder(1);
async function printOrder(orderId) { const promise = Promise.resolve(fetchOrder(orderId)); console.log(order); }
printOrder(1); Wrap rest with then callback
async function printOrder(orderId) { const promise = Promise.resolve(fetchOrder(orderId)); return promise.then(order
=> { console.log(order); return Promise.resolve(undefined); }); } printOrder(1); Wrap rest with then callback
async function printOrder(orderId) { const promise = Promise.resolve(fetchOrder(orderId)); return promise.then(order
=> { console.log(order); return Promise.resolve(undefined); }); } printOrder(1); Wrap implicit return with Promise.resolve
async function meaningOfLife() { return 42; } meaningOfLife() === 42;
???
async function meaningOfLife() { return 42; } function meaningOfLife() {
return Promise.resolve(42); } meaningOfLife() .then(answer => answer === 42);
Exceptional Situations
async function printOrder(orderId) { try { const order = await
fetchOrder(orderId); console.log(order); } catch (e) { console.log('Error retrieving order', e); } }
async function printOrder(orderId) { try { const order = await
fetchOrder(orderId); console.log(order); } catch (e) { console.log('Error retrieving order', e); } } ?
function fetchOrder(orderId) { return fetch(`/orders/${orderId}`) .then(resp => { if (resp.status
=== 404) { throw new Error('Order not found'); } return resp.json(); }); }
function fetchOrder(orderId) { return new Promise((resolve, reject) => { $.get(`/orders/${orderId}`)
.done(order => resolve(order)) .fail(resp => { if (resp.status === 404) { reject(new Error('Order not found')); } }); }); }
Error Error Error Error
Regain Control
async function findOrCreateOrder(orderId) { let order; if (await orderExists(orderId)) {
order = await fetchOrder(orderId); } else { order = await createOrder(); } return order; }
async function findOrCreateOrder(orderId) { let order; if (await orderExists(orderId)) {
order = await fetchOrder(orderId); } else { order = await createOrder(); } return order; }
async function printOrders(orderIds) { for (const id of orderIds) {
const order = await fetchOrder(id); console.log(order); } }
async function printOrders(orderIds) { for (const id of orderIds) {
const order = await fetchOrder(id); console.log(order); } }
async function printOrders(orderIds) { for (const id of orderIds) {
const order = await fetchOrder(id); console.log(order); } } Problem?
async function printOrders(orderIds) { for (const id of orderIds) {
const order = await fetchOrder(id); console.log(order); } } printOrders([1, 2, 3]);
printOrders([1, 2, 3]); await fetchOrder(1); await fetchOrder(2); await fetchOrder(3);
printOrders([1, 2, 3]); await fetchOrder(1); await fetchOrder(2); await fetchOrder(3);
printOrders([1, 2, 3]); await fetchOrder(1); await fetchOrder(2); await fetchOrder(3);
Order is important
async function printOrders(orderIds) { const orders = await Promise.all( orderIds.map(fetchOrder)
); orders.forEach(order => console.log(order)); }
async function printOrders(orderIds) { const orders = await Promise.all( orderIds.map(fetchOrder)
); orders.forEach(order => console.log(order)); }
printOrders([1, 2, 3]); await Promise.all([ fetchOrder(1), fetchOrder(2), fetchOrder(3) ]);
• Readable, synchronous- like code • Native flow control constructs
• Sequential and concurrent processing
• Readable, synchronous- like code • Native flow control constructs
• Sequential and concurrent processing ✓ ✓ ✓ ✓
Events and streams?
let counter = 0; function updateCounter(n) { counter += n;
counterEl.innerHTML = counter; } incrementBtn.addEventListener('click', () => { updateCounter(1); }); decrementBtn.addEventListener('click', () => { updateCounter(-1); });
let counter = 0; function updateCounter(n) { counter += n;
counterEl.innerHTML = counter; } incrementBtn.addEventListener('click', () => { updateCounter(1); }); decrementBtn.addEventListener('click', () => { updateCounter(-1); }); ×
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; });
RxJS
[ 1, 2, 3, 4, 5 ] Arrays Sequences in
space
Observables Sequences in time 1 2 3 4 5
Reactive
Reactive 3
const { Observable } = require('rxjs'); const source = Observable.of(1,
2, 3); source.subscribe(x => console.log(x)); // 1 // 2 // 3
const { Observable } = require('rxjs'); const source = Observable.of(1,
2, 3); source.subscribe(x => console.log(x)); // 1 // 2 // 3
const { Observable } = require('rxjs'); const source = Observable.of(1,
2, 3); source.subscribe(x => console.log(x)); // 1 // 2 // 3
const { Observable } = require('rxjs'); const source = Observable.of(1,
2, 3); source.subscribe(x => console.log(x)); // 1 // 2 // 3
Observable.of(1, 2, 3) .subscribe(x => console.log(x)); console.log subscribe
Observable.of(1, 2, 3) .subscribe(x => console.log(x)); console.log subscribe 1
Observable.of(1, 2, 3) .subscribe(x => console.log(x)); console.log subscribe 1
Observable.of(1, 2, 3) .subscribe(x => console.log(x)); console.log subscribe
Observable.of(1, 2, 3) .subscribe(x => console.log(x)); console.log subscribe 2
Observable.of(1, 2, 3) .subscribe(x => console.log(x)); console.log subscribe 2
Observable.of(1, 2, 3) .subscribe(x => console.log(x)); console.log subscribe
Observable.of(1, 2, 3) .subscribe(x => console.log(x)); console.log subscribe 3
Observable.of(1, 2, 3) .subscribe(x => console.log(x)); console.log subscribe 3
Declarative Transformation Operate on events
Observable.of(1, 2, 3) .map(n => n * 2) .subscribe(x =>
console.log(x)); // 2 // 4 // 6
Observable.of(1, 2, 3) .map(n => n * 2) .subscribe(x =>
console.log(x)); // 2 // 4 // 6
Observable.of(1, 2, 3) console.log n * 2 map subscribe
Observable.of(1, 2, 3) console.log n * 2 map subscribe 1
Observable.of(1, 2, 3) console.log n * 2 map subscribe 1
Observable.of(1, 2, 3) console.log n * 2 map subscribe 2
Observable.of(1, 2, 3) console.log n * 2 map subscribe 2
Observable.of(1, 2, 3) console.log n * 2 map subscribe
Observable.of(1, 2, 3) console.log n * 2 map subscribe 2
Observable.of(1, 2, 3) console.log n * 2 map subscribe 2
Observable.of(1, 2, 3) console.log n * 2 map subscribe 4
Observable.of(1, 2, 3) console.log n * 2 map subscribe 4
Observable.of(1, 2, 3) console.log n * 2 map subscribe
Observable.of(1, 2, 3) console.log n * 2 map subscribe 3
Observable.of(1, 2, 3) console.log n * 2 map subscribe 3
Observable.of(1, 2, 3) console.log n * 2 map subscribe 6
Observable.of(1, 2, 3) console.log n * 2 map subscribe 6
Lazy Transformation Do only as much work as needed
Observable.range(1, 100) .map(n => n * 2) .filter(n => n
> 4) .take(2) .subscribe(x => console.log(x)); // 6 // 8
Observable.range(1, 100) .map(n => n * 2) .filter(n => n
> 4) .take(2) .subscribe(x => console.log(x)); // 6 // 8
Observable.range(1, 100) .map(n => n * 2) .filter(n => n
> 4) .take(2) .subscribe(x => console.log(x)); // 6 // 8
Observable.range(1, 100) .map(n => n * 2) .filter(n => n
> 4) .take(2) .subscribe(x => console.log(x)); // 6 // 8
Observable.range(1, 100) .map(n => n * 2) .filter(n => n
> 4) .take(2) .subscribe(x => console.log(x)); // 6 // 8
console.log n * 2 map subscribe n > 4 Observable.range(1,
100) filter 2 take
console.log n * 2 map subscribe n > 4 1
Observable.range(1, 100) filter 2 take
console.log n * 2 map subscribe n > 4 1
Observable.range(1, 100) filter 2 take
console.log n * 2 map subscribe n > 4 2
Observable.range(1, 100) filter 2 take
console.log n * 2 map subscribe n > 4 2
Observable.range(1, 100) filter 2 take
console.log n * 2 map subscribe n > 4 2
Observable.range(1, 100) filter 2 take ×
console.log n * 2 map subscribe n > 4 Observable.range(1,
100) filter 2 take
console.log n * 2 map subscribe n > 4 2
Observable.range(1, 100) filter 2 take
console.log n * 2 map subscribe n > 4 2
Observable.range(1, 100) filter 2 take
console.log n * 2 map subscribe n > 4 4
Observable.range(1, 100) filter 2 take
console.log n * 2 map subscribe n > 4 Observable.range(1,
100) filter 2 take 4
console.log n * 2 map subscribe n > 4 Observable.range(1,
100) filter 2 take 4 ×
console.log n * 2 map subscribe n > 4 Observable.range(1,
100) filter 2 take
console.log n * 2 map subscribe n > 4 3
Observable.range(1, 100) filter 2 take
console.log n * 2 map subscribe n > 4 3
Observable.range(1, 100) filter 2 take
console.log n * 2 map subscribe n > 4 6
Observable.range(1, 100) filter 2 take
console.log n * 2 map subscribe n > 4 Observable.range(1,
100) filter 2 take 6
✓ console.log n * 2 map subscribe n > 4
Observable.range(1, 100) filter 2 take 6
console.log n * 2 map subscribe n > 4 Observable.range(1,
100) filter 2 take 6
console.log n * 2 map subscribe n > 4 Observable.range(1,
100) filter 1 take 6
console.log n * 2 map subscribe n > 4 Observable.range(1,
100) filter 1 take
console.log n * 2 map subscribe n > 4 4
Observable.range(1, 100) filter 1 take
console.log n * 2 map subscribe n > 4 4
Observable.range(1, 100) filter 1 take
console.log n * 2 map subscribe n > 4 8
Observable.range(1, 100) filter 1 take
console.log n * 2 map subscribe n > 4 Observable.range(1,
100) filter 1 take 8
✓ console.log n * 2 map subscribe n > 4
Observable.range(1, 100) filter 1 take 8
console.log n * 2 map subscribe n > 4 Observable.range(1,
100) filter 1 take 8
console.log n * 2 map subscribe n > 4 Observable.range(1,
100) filter 0 take 8
DOM Events
Observable.fromEvent(incrementBtn, 'click') .mapTo(1) .scan((acc, curr) => acc + curr, 0)
.subscribe((counter) => { counterEl.innerHTML = counter; });
Observable.fromEvent(incrementBtn, 'click') .mapTo(1) .scan((acc, curr) => acc + curr, 0)
.subscribe((counter) => { counterEl.innerHTML = counter; });
Observable.fromEvent(incrementBtn, 'click') .mapTo(1) .scan((acc, curr) => acc + curr, 0)
.subscribe((counter) => { counterEl.innerHTML = counter; });
Observable.fromEvent(incrementBtn, 'click') .mapTo(1) .scan((acc, curr) => acc + curr, 0)
.subscribe((counter) => { counterEl.innerHTML = counter; });
Observable.fromEvent(incrementBtn, 'click') .mapTo(1) .scan((acc, curr) => acc + curr, 0)
.subscribe((counter) => { counterEl.innerHTML = counter; });
Observable.fromEvent(incrementBtn, 'click') .mapTo(1) .scan((acc, curr) => acc + curr, 0)
.subscribe((counter) => { counterEl.innerHTML = counter; });
Observable.fromEvent(incrementBtn, 'click') .mapTo(1) .scan((acc, curr) => acc + curr, 0)
.subscribe((counter) => { counterEl.innerHTML = counter; });
.scan((acc, curr) => acc + curr, 0) Accumulated counter value
.scan((acc, curr) => acc + curr, 0) Current event value
(1)
.scan((acc, curr) => acc + curr, 0) Return new accumulated
counter value
.scan((acc, curr) => acc + curr, 0) Initial counter value
counterEl.innerHTML = counter acc + curr 1 mapTo subscribe 0
scan
acc + curr 1 mapTo subscribe e scan 0 counterEl.innerHTML
= counter
1 mapTo subscribe e scan acc + curr 0 counterEl.innerHTML
= counter
1 mapTo subscribe 1 scan acc + curr 0 counterEl.innerHTML
= counter
1 mapTo subscribe scan 1 acc + curr 0 counterEl.innerHTML
= counter
1 mapTo subscribe scan 1 acc + curr 1 Counter
value counterEl.innerHTML = counter
1 mapTo subscribe scan 1 acc + curr 1 counterEl.innerHTML
= counter
acc + curr 1 mapTo subscribe scan 1 counterEl.innerHTML =
counter
acc + curr 1 mapTo subscribe e scan 1 counterEl.innerHTML
= counter
1 mapTo subscribe e scan acc + curr 1 counterEl.innerHTML
= counter
1 mapTo subscribe 1 scan acc + curr 1 counterEl.innerHTML
= counter
1 mapTo subscribe scan 1 acc + curr 1 counterEl.innerHTML
= counter
1 mapTo subscribe scan 2 acc + curr 2 Counter
value counterEl.innerHTML = counter
1 mapTo subscribe scan 2 acc + curr 2 counterEl.innerHTML
= counter
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; });
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; });
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; });
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; });
acc + curr 1 mapTo subscribe 0 scan -1 mapTo
Increment Decrement counterEl.innerHTML = counter
acc + curr 1 mapTo subscribe 0 scan -1 mapTo
Increment Decrement e counterEl.innerHTML = counter
acc + curr 1 mapTo subscribe 0 scan -1 mapTo
Increment Decrement e counterEl.innerHTML = counter
acc + curr 1 mapTo subscribe 0 scan -1 mapTo
Increment Decrement 1 counterEl.innerHTML = counter
acc + curr 1 mapTo subscribe 0 scan -1 mapTo
Increment Decrement 1 counterEl.innerHTML = counter
acc + curr 1 mapTo subscribe 1 scan -1 mapTo
Increment Decrement 1 counterEl.innerHTML = counter
acc + curr 1 mapTo subscribe 1 scan -1 mapTo
Increment Decrement 1 counterEl.innerHTML = counter
acc + curr 1 mapTo subscribe 1 scan -1 mapTo
Increment Decrement counterEl.innerHTML = counter
acc + curr 1 mapTo subscribe 1 scan -1 mapTo
Increment Decrement e counterEl.innerHTML = counter
acc + curr 1 mapTo subscribe 1 scan -1 mapTo
Increment Decrement e counterEl.innerHTML = counter
acc + curr 1 mapTo subscribe 1 scan -1 mapTo
Increment Decrement -1 counterEl.innerHTML = counter
acc + curr 1 mapTo subscribe 1 scan -1 mapTo
Increment Decrement -1 counterEl.innerHTML = counter
acc + curr 1 mapTo subscribe 0 scan -1 mapTo
Increment Decrement 0 counterEl.innerHTML = counter
acc + curr 1 mapTo subscribe 0 scan -1 mapTo
Increment Decrement 0 counterEl.innerHTML = counter
Async HTTP
Promise fetchOrders() .then((orders) => { orders.forEach((order) => { console.log(order); });
});
Promise fetchOrders() .then((orders) => { orders.forEach((order) => { console.log(order); });
});
Observable fetchOrders() .subscribe((orders) => { orders.forEach((order) => { console.log(order); });
});
Observable fetchOrders() .subscribe((orders) => { orders.forEach((order) => { console.log(order); });
}); Lazy unlike Promise
Why Observables?
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)); });
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)); });
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)); });
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)); });
Async async function printCustomerNames() { const orders = await fetchOrders();
const filtered = orders.filter( order => order.customerName === 'Tucker' ); const orderIds = filtered.map(order => order.id); orderIds.forEach(id => console.log(id)); }
Async async function printCustomerNames() { const orders = await fetchOrders();
const filtered = orders.filter( order => order.customerName === 'Tucker' ); const orderIds = filtered.map(order => order.id); orderIds.forEach(id => console.log(id)); }
Async async function printCustomerNames() { const orders = await fetchOrders();
const filtered = orders.filter( order => order.customerName === 'Tucker' ); const orderIds = filtered.map(order => order.id); orderIds.forEach(id => console.log(id)); }
fetchOrders() .mergeAll() .filter( order => order.customerName === 'Tucker' ) .map(order
=> order.id) .subscribe(id => console.log(id)); Observable
fetchOrders() .mergeAll() .filter( order => order.customerName === 'Tucker' ) .map(order
=> order.id) .subscribe(id => console.log(id)); Observable
fetchOrders() .mergeAll() .filter( order => order.customerName === 'Tucker' ) .map(order
=> order.id) .subscribe(id => console.log(id)); Observable
fetchOrders() .mergeAll() .filter( order => order.customerName === 'Tucker' ) .map(order
=> order.id) .subscribe(id => console.log(id)); Observable
fetchOrders() .mergeAll() .filter( order => order.customerName === 'Tucker' ) .map(order
=> order.id) .subscribe(id => console.log(id)); Observable
Cancellation
const promise = fetchOrders() .then((orders) => { orders.foreach((order) => {
console.log(order); }); }); promise.cancel(); Promise
const promise = fetchOrders() .then((orders) => { orders.foreach((order) => {
console.log(order); }); }); promise.cancel(); Promise
const promise = fetchOrders() .then((orders) => { orders.foreach((order) => {
console.log(order); }); }); promise.cancel(); Promise ×
const subscription = fetchOrders() .subscribe((orders) => { orders.forEach((order) => {
console.log(order); }); }); subscription.unsubscribe(); Observable
const subscription = fetchOrders() .subscribe((orders) => { orders.forEach((order) => {
console.log(order); }); }); subscription.unsubscribe(); Observable Cancel request
Create HTTP Requests
function fetchOrders() { return Observable.ajax.get('/orders'); } Built-in AJAX
function fetchOrders() { return Observable.ajax.get('/orders'); } Built-in AJAX
function fetchOrders() { return Observable.create((subscriber) => { fetchOrdersFromDb((orders) => {
subscriber.next(orders); subscriber.complete(); }); }); } Custom Observable Creation
function fetchOrders() { return Observable.create((subscriber) => { fetchOrdersFromDb((orders) => {
subscriber.next(orders); subscriber.complete(); }); }); } Custom Observable Creation
function fetchOrders() { return Observable.create((subscriber) => { fetchOrdersFromDb((orders) => {
subscriber.next(orders); subscriber.complete(); }); }); } Custom Observable Creation
function fetchOrders() { return Observable.create((subscriber) => { fetchOrdersFromDb((orders) => {
subscriber.next(orders); subscriber.complete(); }); }); } Custom Observable Creation
function fetchOrders() { return Observable.create((subscriber) => { fetchOrdersFromDb((orders) => {
subscriber.next(orders); subscriber.complete(); }); }); } Custom Observable Creation
function fetchOrders() { return Observable.create((subscriber) => { fetchOrdersFromDb((orders) => {
subscriber.next(orders); subscriber.complete(); }); }); } Custom Observable Creation
function fetchOrders() { return Observable.create((subscriber) => { fetchOrdersFromDb((orders) => {
subscriber.next(orders); subscriber.complete(); }); }); } Custom Observable Creation ?
Observable.of(1, 2, 3) .subscribe(x => console.log(x));
Observable.of(1, 2, 3) .subscribe(x => console.log(x)); Subscriber
Observable.of(1, 2, 3) .subscribe({ next: x => console.log(x), });
Observable.of(1, 2, 3) .subscribe({ next: x => console.log(x), complete: ()
=> console.log('Done!'), }); // 1 // 2 // 3 // Done!
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
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
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
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
Error Handling
Error Error Error Error Promises
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), });
const source = Observable.create((subscriber) => { subscriber.next(1); subscriber.error(new Error('Uh oh'));
subscriber.next(2); }); source.subscribe(x => console.log(x)); ?
const source = Observable.create((subscriber) => { subscriber.next(1); subscriber.error(new Error('Uh oh'));
subscriber.next(2); }); source.subscribe(x => console.log(x));
fetchOrders() .catch((e) => { logError(e); return Observable.of([]); }) .subscribe(x =>
console.log(x));
fetchOrders() .catch((e) => { logError(e); return Observable.of([]); }) .subscribe(x =>
console.log(x));
fetchOrders() .catch((e) => { logError(e); return Observable.of([]); }) .subscribe(x =>
console.log(x));
fetchOrders() .catch((e) => { logError(e); return Observable.of([]); }) .subscribe(x =>
console.log(x));
fetchOrders() .mergeAll() .filter( order => order.customerName === 'Tucker' ) .map(order
=> order.id) .subscribe(id => console.log(id)); Recall
fetchOrders() .mergeAll() .filter( order => order.customerName === 'Tucker' ) .map(order
=> order.id) .subscribe(id => console.log(id)); ? Recall
Observable.of([1, 2, 3]) .subscribe(x => console.log(x)); // [ 1, 2,
3 ]
Observable.of([1, 2, 3]) .subscribe(x => console.log(x)); // [ 1, 2,
3 ]
Observable.of([1, 2, 3]) .subscribe(x => console.log(x)); // [ 1, 2,
3 ]
Observable.of([1, 2, 3]) .mergeAll() .subscribe(x => console.log(x)); // 1 //
2 // 3
Observable.of([1, 2, 3]) .mergeAll() .subscribe(x => console.log(x)); // 1 //
2 // 3 Flatten
Observable.of([1, 2, 3]) .mergeAll() .subscribe(x => console.log(x)); // 1 //
2 // 3
subscribe mergeAll console.log [1, 2, 3]
subscribe mergeAll console.log [1, 2, 3]
subscribe mergeAll console.log [2, 3] 1
subscribe mergeAll console.log [2, 3] 1
subscribe mergeAll console.log [3] 2
subscribe mergeAll console.log [3] 2
subscribe mergeAll console.log 3
subscribe mergeAll console.log 3
Higher Order Observables
Observable.of(1, 2, 3) .map(n => n * 2) .subscribe(x =>
console.log(x)); // 2 // 4 // 6
Observable.of(1, 2, 3) .map(n => n * 2) .subscribe(x =>
console.log(x)); // 2 // 4 // 6 Delay?
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 }
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 }
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 }
Observable.of(1, 2, 3) .map(n => Observable.of(n * 2)) .mergeAll() .subscribe(x
=> console.log(x)); // 2 // 4 // 6
Observable.of(1, 2, 3) .map(n => Observable.of(n * 2)) .mergeAll() .subscribe(x
=> console.log(x)); // 2 // 4 // 6
Observable.of(1, 2, 3) .mergeMap(n => Observable.of(n * 2)) .subscribe(x =>
console.log(x)); // 2 // 4 // 6
subscribe mergeMap console.log Observable.of(n * 2)
subscribe mergeMap console.log Observable.of(n * 2) 1
subscribe mergeMap console.log Observable.of(n * 2) 1
subscribe mergeMap console.log Observable.of(n * 2) Observable.of(2) subscribe 2
subscribe mergeMap console.log Observable.of(n * 2) Observable.of(2) subscribe 2
subscribe mergeMap console.log Observable.of(n * 2) Observable.of(2) subscribe 2
Observable.of(1) .delay(1000) .subscribe(x => console.log(x)) // <tick> // 1
Observable.of(1) .delay(1000) .subscribe(x => console.log(x)) // <tick> // 1
Observable.of(1) .delay(1000) .subscribe(x => console.log(x)) // <tick> // 1
Observable.of(1) .delay(1000) .subscribe(x => console.log(x)) // <tick> // 1
Observable.of(1, 2, 3) .mergeMap(n => ( Observable.of(n * 2).delay(1000) ))
.subscribe(x => console.log(x)); // <tick> // 2 // 4 // 6
Observable.of(1, 2, 3) .mergeMap(n => ( Observable.of(n * 2).delay(1000) ))
.subscribe(x => console.log(x)); // <tick> // 2 // 4 // 6
Observable.of(1, 2, 3) .mergeMap(n => ( Observable.of(n * 2).delay(1000) ))
.subscribe(x => console.log(x)); // <tick> // 2 // 4 // 6
Observable.of(1, 2, 3) .mergeMap(n => ( Observable.of(n * 2).delay(1000) ))
.subscribe(x => console.log(x)); // <tick> // 2 // 4 // 6 ?
Concurrency
subscribe mergeMap console.log Observable.of(n * 2)
subscribe mergeMap console.log Observable.of(n * 2) 1
subscribe mergeMap console.log Observable.of(n * 2) 1
subscribe mergeMap console.log Observable.of(n * 2) 2
subscribe mergeMap console.log Observable.of(n * 2) 2 2
subscribe mergeMap console.log Observable.of(n * 2) 2 2
subscribe mergeMap console.log Observable.of(n * 2) 2 4
subscribe mergeMap console.log Observable.of(n * 2) 3 2 4
subscribe mergeMap console.log Observable.of(n * 2) 3 2 4
subscribe mergeMap console.log Observable.of(n * 2) 2 4 6
subscribe mergeMap console.log Observable.of(n * 2) 2 4 6
subscribe mergeMap console.log Observable.of(n * 2) 4 6 2
subscribe mergeMap console.log Observable.of(n * 2) 6 4
subscribe mergeMap console.log Observable.of(n * 2) 6
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
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
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
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
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
Observable.of(1, 2, 3) .concatMap(n => ( Observable.of(n * 2).delay(1000) ))
.subscribe(x => console.log(x)); // <tick> // 2 // <tick> // 4 // <tick> // 6
subscribe concatMap console.log Observable.of(n * 2)
subscribe console.log Observable.of(n * 2) 1 concatMap
subscribe console.log Observable.of(n * 2) 1 concatMap
subscribe console.log Observable.of(n * 2) 2 concatMap
subscribe console.log Observable.of(n * 2) 2 concatMap
subscribe console.log Observable.of(n * 2) 2 concatMap
subscribe console.log Observable.of(n * 2) 2 concatMap
subscribe console.log Observable.of(n * 2) 2 concatMap
subscribe console.log Observable.of(n * 2) 4 concatMap
subscribe console.log Observable.of(n * 2) 4 concatMap
subscribe console.log Observable.of(n * 2) 4 concatMap
subscribe console.log Observable.of(n * 2) 3 concatMap
subscribe console.log Observable.of(n * 2) 3 concatMap
subscribe console.log Observable.of(n * 2) 6 concatMap
subscribe console.log Observable.of(n * 2) 6 concatMap
subscribe console.log Observable.of(n * 2) 6 concatMap
Rate Limiting
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' } ]
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' } ]
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' } ]
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' } ]
Observable .ajax .get(url) subscribe console.log pluck bufferCount 'response' 3 concatMap
Observable .ajax .get(url) subscribe concatMap console.log pluck bufferCount 'response' 3
1
Observable .ajax .get(url) subscribe concatMap console.log pluck bufferCount 'response' 3
Observable .ajax .get(url) subscribe concatMap console.log pluck bufferCount 'response' 3
R Rate limit next request
Observable .ajax .get(url) subscribe concatMap console.log pluck bufferCount 'response' 3
R
Observable .ajax .get(url) subscribe concatMap console.log pluck bufferCount 'response' 3
R
Observable .ajax .get(url) subscribe concatMap console.log pluck bufferCount 'response' 3
O1
Observable .ajax .get(url) subscribe concatMap console.log pluck bufferCount 'response' 2
O1
Observable .ajax .get(url) subscribe concatMap console.log pluck bufferCount 'response' 2
O1 +
Observable .ajax .get(url) subscribe concatMap console.log pluck bufferCount 'response' 1
O1 O2
Observable .ajax .get(url) subscribe concatMap console.log pluck bufferCount 'response' 1
O1 O2 +
Observable .ajax .get(url) subscribe concatMap console.log pluck bufferCount 'response' 0
O1 O2 O3
Observable .ajax .get(url) subscribe concatMap console.log […] pluck bufferCount 'response'
0
• Declarative, lazy operations • Expressive event management • No
more error swallowing • Rate limiting and concurrent processing
• Declarative, lazy operations • Expressive event management • No
more error swallowing • Rate limiting and concurrent processing ✓ ✓ ✓ ✓
Resources
Browser compatibility: kangax.github.io/compat-table/es2016plus Node compatibility: node.green Async
Async babeljs.io
Observables github.com/ReactiveX/rxjs RxJS
Observables ECMAScript Proposal: github.com/tc39/proposal-observable Another spec implementation: github.com/zenparsing/zen-observable
THANKS! bit.ly/jazzcon-rise-of-async SLIDES: github.com/jfairbank/rise-of-async-js-talk CODE: Jeremy Fairbank @elpapapollo | gh/jfairbank