async redux actions with rxjs - react rally 2016

73

Upload: ben-lesh

Post on 13-Jan-2017

1.882 views

Category:

Technology


0 download

TRANSCRIPT

PowerPoint Presentation

Async Redux Actions With RxJS a love story. stateSometimes it returns a new state(state, action) => state + action.value

Remove mutation?8

Reducers in Reduxconst 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)AJAXWeb SocketsAnimationsWorkers, et al

A LOT of async resultscan be handled synchronouslyClick a button and update a valueSelect an option and update a valueGet a single AJAX request and update viewMouse moves updating some coordinates

Well, some async is harder than othersAJAX cancellationComposed AJAXDebounced form submissionsDrag and dropAdvanced 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

CallbacksThe 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 }); }); });});

Have a redux tie-in (dispatch instead of doStuff)

note threading of errors19

Promises provide a cleaner solutiongetSomeData(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 }); })

PromisesGuaranteed FutureImmutableSingle ValueCachingThese two features can be problematicin modern web applications

Promises cant be cancelledThis means youre executing code you dont want to

Why cancellation is importantAuto-completeChanging views/routes before data finishes loadingTearing down resources

Loading view data without cancellation

DaredevilLoading view data without cancellation

DaredevilThe Get DownSince you cant cancel the previous promise,youre stuck processing the responseand somehow signaling disinterestLoading view data without cancellation

Heres Daredevil!

DaredevilA better scenario

DaredevilThe Get Down

Ideally, when we make a new requestwe can abort the the old oneso its never handled and processedA 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 changingLaptops and desktops are very fastTabletsSmartphonesIndustrial devicesSmartTVsAppliancesWearables

Promises are a single value

Which of these are single value?User interactions (mouse, keyboard, etc)AJAXWeb SocketsAnimationsWorkers, et al

What do we use?

ObservablesA set of eventsAny number of valuesOver any amount of timeCancellableLazy

RxJSObservables and functions to create and compose Observables

RxJSLodash for async

RxJS provides many ways to create Observablesinterval(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 observablemyObservable.subscribe(x => console.log(x));

Subscribing to an observablemyObservable.subscribe( x => console.log(x), err => console.error(err));

Subscribing to an observablemyObservable.subscribe( x => console.log(x), err => console.error(err) , () => console.info(complete));

Subscribing to an observableconst 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?

Note promises are only 1 event, this is 0-N.43

Sets can be transformedmap, filter, reduce

Sets can be combinedconcat, merge, zip

Observables are sets with another dimension: TIMEbuffer, throttle, debounce, combineLatest

Observables are lazy so they can be resubscribedretry, repeat

There is an error path, so we can catch, just like promisemyObservable.catch(err => Observable.of(handled))

You can do just about anything with RxJS and Observables!

- redux documentation

WellRedux has amazing tooling, community and support around it.

Redux with middlewareprovides solid architecture patterns

Async in redux without middleware

Reducerinstance statehandler kicking off asyncrenderComponent

Async in redux with middleware(and react-redux)

ReducerEpicStateless Component

Lets combine RxJS and redux!

redux-observableEpic middleware for redux

Whats 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 });

Whats an Epic?Actions in, actions out

const pingPongEpic = (action$, store) => action$.ofType(PING) .map(action => ({ type: PONG });

Basic middleware setupimport { 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 debounceconst 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 debounceexport 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: Dont read all of this

Auto-Complete Plain JS

Auto-Complete Epicexport 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 actionsexport 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 GoodMakes it very easy to compose and control complex async tasks with RxJS and reduxCan use redux toolingYou dont end up managing your own Rx subscriptionsIf used with react-redux, makes all of your components stateless

The BadNeed to know redux in advanceShould learn RxJS in advanceRxJS has a bit of a learning curve

redux-observablehttps://github.com/redux-observable/redux-observable

Co-Author Jay Phelps

Twitter: @_jayphelpsGithub: jayphelps

Thank you!

@benlesh