KEMBAR78
Async Redux Actions With RxJS - React Rally 2016 | PPTX
Async Redux Actions With RxJS
… a love story. <3
Ben Lesh
RxJS 5 Lead
Senior Engineer
Netflix UI Platform Team
Twitter: @benlesh
GitHub: blesh
What is redux?
• Red UX – making your whole site red
• A shelter specializing in abandoned pet ducks
• SF startup for bio-engineered foie gras
• An evil recycling program for unwanted ducks
What is redux?
• A library for managing state with reducers
• Basic functional programming wizardry
brought to the masses
• A brilliant piece of work by Dan Abramov (and
many others)
What’s a reducer?
A simple function that takes state
and an action, and returns a new
state
What’s a reducer?
• Sometimes it returns the same state
(state, action) => state
• Sometimes it returns a new state
(state, action) => state + action.value
Reducers in Redux
const reducer = (state = { value: 0 }, action) => {
switch (action.type) {
case ‘INCREMENT’:
return { value: state.value + 1 };
case ‘DECREMENT’:
return { value: state.value – 1 };
default:
return state;
}
};
Reducers are great for managing state!
An action notifies you of a change,
you return a new state! So easy!
Redux reducers handle state
transitions, but they must be
handled synchronously
But what about async?
• User interactions (mouse, keyboard, etc)
• AJAX
• Web Sockets
• Animations
• Workers, et al
A LOT of async results
can be handled synchronously
• Click a button and update a value
• Select an option and update a value
• Get a single AJAX request and update view
• Mouse moves updating some coordinates
Well, some async
is harder than others…
• AJAX cancellation
• Composed AJAX
• Debounced form submissions
• Drag and drop
• Advanced web socket use
What do these “harder” async
stories have in common?
Composing multiple async sources.
…and cancellation!.
In redux, we use middleware to
manage async
Most redux middlewares use
callbacks or promises
Callbacks
The most primitive way to handle asynchrony in
JavaScript is with callbacks
getSomeData((data) => {
dispatch({ type: ‘I_HAVE_DATA’, data });
});
Callback Hell
(aka “the flying V”)
getSomeData(id, (data) => {
dispatch({ type: ‘SOME_DATA’, data });
getSomeData(data.parentId, (parent) => {
dispatch({ type: ‘MORE_DATA’, data });
getSomeData(parent.parentId, (grandparent) => {
dispatch({ type: ‘DATAX3_YOLO_LOL’, data });
});
});
});
Promises provide a cleaner solution
getSomeData(id)
.then(data => {
dispatch({ type: ‘SOME_DATA’, data });
return getSomeData(data.parentId);
})
.then(data => {
dispatch({ type: ‘MORE_DATA’, data });
return getSomeData(data.parentId);
})
.then(data => {
dispatch({ type: ‘DATAX3_YOLO_LOL’, data });
})
Promises
• Guaranteed Future
• Immutable
• Single Value
• Caching
These two features can be problematic
in modern web applications
Promises can’t be cancelled
This means you’re executing code
you don’t want to
Why cancellation is important
• Auto-complete
• Changing views/routes before data finishes
loading
• Tearing down resources
Loading view data without cancellation
Daredevil
Loading view data without cancellation
Daredevil
The Get Down
Since you can’t cancel the previous promise,
you’re stuck processing the response
and somehow signaling “disinterest”
Loading view data without cancellation
Here’s Daredevil!
Daredevil
A better scenario
Daredevil
The Get Down
Ideally, when we make a new request
we can abort the the old one
so it’s never handled and processed
A better scenario
Thanks, Kent C Dodds!
But these days “resources” are
cheap, right?
Netflix targets devices that are 465x
slower than your laptop
Times are changing
• Laptops and desktops are very fast
• Tablets
• Smartphones
• Industrial devices
• SmartTVs
• Appliances
• Wearables
Promises are a single value
Which of these are single value?
• User interactions (mouse, keyboard, etc)
• AJAX
• Web Sockets
• Animations
• Workers, et al
What do we use?
Observables
• A set of events
• Any number of values
• Over any amount of time
• Cancellable
• Lazy
RxJS
Observables and functions to create
and compose Observables
RxJS
“Lodash for async”
RxJS provides many ways to create
Observables
• interval(1000)
• fromEvent(button, ‘click’)
• from([1, 2, 3, 4]);
• of(‘hello’);
• ajax.getJSON(‘http://example.com’);
• webSocket(‘ws://echo.websocket.com’);
• many, many more…
Subscribing to an observable
myObservable.subscribe(x => console.log(x));
Subscribing to an observable
myObservable.subscribe(
x => console.log(x),
err => console.error(err)
);
Subscribing to an observable
myObservable.subscribe(
x => console.log(x),
err => console.error(err) ,
() => console.info(‘complete’)
);
Subscribing to an observable
const subscription = myObservable.subscribe(
x => console.log(x),
err => console.error(err) ,
() => console.info(‘complete’)
);
subscription.unsubscribe();
We have sets of events that we
can cancel!
Okay, now what?
Sets can be transformed
map, filter, reduce
Sets can be combined
concat, merge, zip
Observables are sets with
another dimension: TIME
buffer, throttle, debounce,
combineLatest
Observables are lazy so they can
be resubscribed
retry, repeat
There is an error path, so we can
catch, just like promise
myObservable.catch(err => Observable.of(‘handled’))
You can do just about anything
with RxJS and Observables!
- redux documentation
Well…
Redux has amazing tooling,
community and support around it.
Redux with middleware
provides solid architecture patterns
Async in redux without middleware
Reducer
instance state
handler kicking off async
render
Component
Async in redux with middleware
(and react-redux)
Reducer
Epic
Stateless Component
Let’s combine RxJS and redux!
redux-observable
Epic middleware for redux
What’s an “Epic”?
A function that takes a stream of all actions
dispatched, and returns a stream of actions to
dispatch.
const pingPongEpic = (action$, store) =>
action$.ofType(‘PING’)
.map(action => ({ type: ‘PONG’ });
What’s an “Epic”?
“Actions in, actions out”
const pingPongEpic = (action$, store) =>
action$.ofType(‘PING’)
.map(action => ({ type: ‘PONG’ });
Basic middleware setup
import { createStore, applyMiddlware } from ‘redux’;
import { createEpicMiddleware } from ‘redux-observable’;
import reducerFn, { epicFn } from ‘./redux/updown’;
const epicMiddleware = createEpicMiddleware(epicFn);
const store = createStore(
reducerFn,
applyMiddleware(epicMiddleware)
);
Idiomatic redux increment decrement
with added debounce
const upEpic = (action$, store) =>
action$.ofType(‘UP’)
.debounceTime(1000)
.map(() => ({ type: ‘INCREMENT’ }));
const downEpic = (action$, store) =>
action$.ofType(‘DOWN’)
.debounceTime(1000)
.map(() => ({ type: ‘DECREMENT’ }));
export const updownEpic = combineEpics(upEpic, downEpic);
Idiomatic redux increment decrement
with added debounce
export default const updown = (state = { value: 0 }, action) =>
{
switch (action.type) {
case ‘INCREMENT’:
return { value: state.value + 1 };
case ‘DECREMENT’:
return { value: state.value – 1 };
default:
return state;
}
};
Idiomatic redux increment decrement
with added debounce
WARNING: Don’t read all of this…
Auto-Complete Plain JS
Auto-Complete Epic
export const autoCompleteEpic = (action$, store) =>
action$.ofType(‘LOAD_QUERY’)
.debounceTime(500)
.switchMap(action =>
ajax.getJSON(`http://endpoint/q=${action.value}`)
.map(results => ({ type: ‘QUERY_RESULTS’, results }))
);
compose in cancellation via dispatched
actions
export const autoCompleteEpic = (action$, store) =>
action$.ofType(‘LOAD_QUERY’)
.debounceTime(500)
.switchMap(action =>
ajax.getJSON(`http://endpoint/q=${action.value}`)
.map(results => ({ type: ‘QUERY_RESULTS’, results }))
.takeUntil(action$.ofType(‘CANCEL_QUERY’))
);
Multiplexed Socket Plain JS
Multiplexed Socket Epic
const socket = new WebSocketSubject('ws://stock/endpoint');
const stockTickerEpic = (action$, store) =>
action$.ofType('GET_TICKER_STREAM')
.mergeMap(action =>
socket.multiplex(
() => ({ sub: action.ticker }),
() => ({ unsub: action.ticker }),
msg => msg.ticker === action.ticker
)
.retryWhen(
err => window.navigator.onLine ?
Observable.timer(1000) :
Observable.fromEvent(window, 'online')
)
.takeUntil(
action$.ofType('CLOSE_TICKER_STREAM')
.filter(closeAction => closeAction.ticker === action.ticker)
)
.map(tick => ({ type: 'TICKER_TICK', tick }))
);
The Good
• Makes it very easy to compose and control
complex async tasks with RxJS and redux
• Can use redux tooling
• You don’t end up managing your own Rx
subscriptions
• If used with react-redux, makes all of your
components stateless
The Bad
• Need to know redux in advance
• Should learn RxJS in advance
• RxJS has a bit of a learning curve
redux-observable
https://github.com/redux-observable/redux-observable
Co-Author Jay Phelps
Twitter: @_jayphelps
Github: jayphelps
Thank you!
@benlesh

Async Redux Actions With RxJS - React Rally 2016

  • 2.
    Async Redux ActionsWith RxJS … a love story. <3
  • 3.
    Ben Lesh RxJS 5Lead Senior Engineer Netflix UI Platform Team Twitter: @benlesh GitHub: blesh
  • 4.
    What is redux? •Red UX – making your whole site red • A shelter specializing in abandoned pet ducks • SF startup for bio-engineered foie gras • An evil recycling program for unwanted ducks
  • 6.
    What is redux? •A library for managing state with reducers • Basic functional programming wizardry brought to the masses • A brilliant piece of work by Dan Abramov (and many others)
  • 7.
    What’s a reducer? Asimple function that takes state and an action, and returns a new state
  • 8.
    What’s a reducer? •Sometimes it returns the same state (state, action) => state • Sometimes it returns a new state (state, action) => state + action.value
  • 9.
    Reducers in Redux constreducer = (state = { value: 0 }, action) => { switch (action.type) { case ‘INCREMENT’: return { value: state.value + 1 }; case ‘DECREMENT’: return { value: state.value – 1 }; default: return state; } };
  • 10.
    Reducers are greatfor managing state! An action notifies you of a change, you return a new state! So easy!
  • 11.
    Redux reducers handlestate transitions, but they must be handled synchronously
  • 12.
    But what aboutasync? • User interactions (mouse, keyboard, etc) • AJAX • Web Sockets • Animations • Workers, et al
  • 13.
    A LOT ofasync results can be handled synchronously • Click a button and update a value • Select an option and update a value • Get a single AJAX request and update view • Mouse moves updating some coordinates
  • 14.
    Well, some async isharder than others… • AJAX cancellation • Composed AJAX • Debounced form submissions • Drag and drop • Advanced web socket use
  • 15.
    What do these“harder” async stories have in common? Composing multiple async sources. …and cancellation!.
  • 16.
    In redux, weuse middleware to manage async
  • 17.
    Most redux middlewaresuse callbacks or promises
  • 18.
    Callbacks The most primitiveway to handle asynchrony in JavaScript is with callbacks getSomeData((data) => { dispatch({ type: ‘I_HAVE_DATA’, data }); });
  • 19.
    Callback Hell (aka “theflying V”) getSomeData(id, (data) => { dispatch({ type: ‘SOME_DATA’, data }); getSomeData(data.parentId, (parent) => { dispatch({ type: ‘MORE_DATA’, data }); getSomeData(parent.parentId, (grandparent) => { dispatch({ type: ‘DATAX3_YOLO_LOL’, data }); }); }); });
  • 20.
    Promises provide acleaner solution getSomeData(id) .then(data => { dispatch({ type: ‘SOME_DATA’, data }); return getSomeData(data.parentId); }) .then(data => { dispatch({ type: ‘MORE_DATA’, data }); return getSomeData(data.parentId); }) .then(data => { dispatch({ type: ‘DATAX3_YOLO_LOL’, data }); })
  • 21.
    Promises • Guaranteed Future •Immutable • Single Value • Caching These two features can be problematic in modern web applications
  • 22.
    Promises can’t becancelled This means you’re executing code you don’t want to
  • 23.
    Why cancellation isimportant • Auto-complete • Changing views/routes before data finishes loading • Tearing down resources
  • 24.
    Loading view datawithout cancellation
  • 25.
    Daredevil Loading view datawithout cancellation
  • 26.
    Daredevil The Get Down Sinceyou can’t cancel the previous promise, you’re stuck processing the response and somehow signaling “disinterest” Loading view data without cancellation Here’s Daredevil!
  • 27.
  • 28.
    Daredevil The Get Down Ideally,when we make a new request we can abort the the old one so it’s never handled and processed A better scenario
  • 29.
  • 30.
    But these days“resources” are cheap, right? Netflix targets devices that are 465x slower than your laptop
  • 31.
    Times are changing •Laptops and desktops are very fast • Tablets • Smartphones • Industrial devices • SmartTVs • Appliances • Wearables
  • 32.
    Promises are asingle value
  • 33.
    Which of theseare single value? • User interactions (mouse, keyboard, etc) • AJAX • Web Sockets • Animations • Workers, et al
  • 34.
  • 35.
    Observables • A setof events • Any number of values • Over any amount of time • Cancellable • Lazy
  • 36.
    RxJS Observables and functionsto create and compose Observables
  • 37.
  • 38.
    RxJS provides manyways to create Observables • interval(1000) • fromEvent(button, ‘click’) • from([1, 2, 3, 4]); • of(‘hello’); • ajax.getJSON(‘http://example.com’); • webSocket(‘ws://echo.websocket.com’); • many, many more…
  • 39.
    Subscribing to anobservable myObservable.subscribe(x => console.log(x));
  • 40.
    Subscribing to anobservable myObservable.subscribe( x => console.log(x), err => console.error(err) );
  • 41.
    Subscribing to anobservable myObservable.subscribe( x => console.log(x), err => console.error(err) , () => console.info(‘complete’) );
  • 42.
    Subscribing to anobservable const subscription = myObservable.subscribe( x => console.log(x), err => console.error(err) , () => console.info(‘complete’) ); subscription.unsubscribe();
  • 43.
    We have setsof events that we can cancel! Okay, now what?
  • 44.
    Sets can betransformed map, filter, reduce
  • 45.
    Sets can becombined concat, merge, zip
  • 46.
    Observables are setswith another dimension: TIME buffer, throttle, debounce, combineLatest
  • 47.
    Observables are lazyso they can be resubscribed retry, repeat
  • 48.
    There is anerror path, so we can catch, just like promise myObservable.catch(err => Observable.of(‘handled’))
  • 49.
    You can dojust about anything with RxJS and Observables!
  • 50.
  • 51.
    Well… Redux has amazingtooling, community and support around it.
  • 52.
    Redux with middleware providessolid architecture patterns
  • 53.
    Async in reduxwithout middleware Reducer instance state handler kicking off async render Component
  • 54.
    Async in reduxwith middleware (and react-redux) Reducer Epic Stateless Component
  • 55.
  • 56.
  • 57.
    What’s an “Epic”? Afunction that takes a stream of all actions dispatched, and returns a stream of actions to dispatch. const pingPongEpic = (action$, store) => action$.ofType(‘PING’) .map(action => ({ type: ‘PONG’ });
  • 58.
    What’s an “Epic”? “Actionsin, actions out” const pingPongEpic = (action$, store) => action$.ofType(‘PING’) .map(action => ({ type: ‘PONG’ });
  • 59.
    Basic middleware setup import{ createStore, applyMiddlware } from ‘redux’; import { createEpicMiddleware } from ‘redux-observable’; import reducerFn, { epicFn } from ‘./redux/updown’; const epicMiddleware = createEpicMiddleware(epicFn); const store = createStore( reducerFn, applyMiddleware(epicMiddleware) );
  • 60.
    Idiomatic redux incrementdecrement with added debounce const upEpic = (action$, store) => action$.ofType(‘UP’) .debounceTime(1000) .map(() => ({ type: ‘INCREMENT’ })); const downEpic = (action$, store) => action$.ofType(‘DOWN’) .debounceTime(1000) .map(() => ({ type: ‘DECREMENT’ })); export const updownEpic = combineEpics(upEpic, downEpic);
  • 61.
    Idiomatic redux incrementdecrement with added debounce export default const updown = (state = { value: 0 }, action) => { switch (action.type) { case ‘INCREMENT’: return { value: state.value + 1 }; case ‘DECREMENT’: return { value: state.value – 1 }; default: return state; } };
  • 62.
    Idiomatic redux incrementdecrement with added debounce
  • 63.
    WARNING: Don’t readall of this…
  • 64.
  • 65.
    Auto-Complete Epic export constautoCompleteEpic = (action$, store) => action$.ofType(‘LOAD_QUERY’) .debounceTime(500) .switchMap(action => ajax.getJSON(`http://endpoint/q=${action.value}`) .map(results => ({ type: ‘QUERY_RESULTS’, results })) );
  • 66.
    compose in cancellationvia dispatched actions export const autoCompleteEpic = (action$, store) => action$.ofType(‘LOAD_QUERY’) .debounceTime(500) .switchMap(action => ajax.getJSON(`http://endpoint/q=${action.value}`) .map(results => ({ type: ‘QUERY_RESULTS’, results })) .takeUntil(action$.ofType(‘CANCEL_QUERY’)) );
  • 67.
  • 68.
    Multiplexed Socket Epic constsocket = new WebSocketSubject('ws://stock/endpoint'); const stockTickerEpic = (action$, store) => action$.ofType('GET_TICKER_STREAM') .mergeMap(action => socket.multiplex( () => ({ sub: action.ticker }), () => ({ unsub: action.ticker }), msg => msg.ticker === action.ticker ) .retryWhen( err => window.navigator.onLine ? Observable.timer(1000) : Observable.fromEvent(window, 'online') ) .takeUntil( action$.ofType('CLOSE_TICKER_STREAM') .filter(closeAction => closeAction.ticker === action.ticker) ) .map(tick => ({ type: 'TICKER_TICK', tick })) );
  • 69.
    The Good • Makesit very easy to compose and control complex async tasks with RxJS and redux • Can use redux tooling • You don’t end up managing your own Rx subscriptions • If used with react-redux, makes all of your components stateless
  • 70.
    The Bad • Needto know redux in advance • Should learn RxJS in advance • RxJS has a bit of a learning curve
  • 71.
  • 72.
    Co-Author Jay Phelps Twitter:@_jayphelps Github: jayphelps
  • 73.

Editor's Notes

  • #9 Remove mutation?
  • #20 Have a redux tie-in (dispatch instead of doStuff) note threading of errors
  • #44 Note promises are only 1 event, this is 0-N.