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

Let's fight - Redux side-effects showdown

Let's fight - Redux side-effects showdown

Even in today’s world, handling asynchronous tasks is hard. There are several ways to handle them, but is there a single best way? Redux is the preferred state management pattern in apps today. By taking a basic example of login, we will compare the two in a showdown format and see the differences.

Ankita Kulkarni

November 29, 2018
Tweet

More Decks by Ankita Kulkarni

Other Decks in Technology

Transcript

  1. AT FIRST, LOGIN IS SIMPLE LOGIN SUCCESS LOGIN 
 FAILURE

    LOGIN REQUEST ActionType.LOGIN_REQUEST ActionType.LOGIN_FAILURE ActionType.LOGIN_SUCCESS
  2. DECLARING AN ACTION const loginRequest = (username, password) => ({

    type: ActionType.LOGIN_REQUEST, payload: { username, password } }); const loginSuccess = user => ({ type: ActionType.LOGIN_SUCCESS, payload: user, }); const loginFailure = err => ({ type: ActionType.LOGIN_FAILURE, payload: err, error: true, });
  3. LISTEN TO THOSE ACTIONS IN REDUCER // Reducer that takes

    in initialState and action function login(state = initialState, action) { switch (action.type) { case ActionType.LOGIN_SUCCESS: return { user: action.payload.user } case ActionType.LOGIN_FAILURE: return { err: action.payload.err, user: null} default: // always return the state if something were to go wrong! return state } }
  4. REDUX THUNKS PROMISES REDUX 
 SAGAS WORKER + WATCHER REDUX

    OBSERVABLES EPIC (TYPE + OPERATORS) MENTAL MODEL
  5. THUNKS const loginThunk = (username, password) => dispatch => {

    dispatch(loginRequest(username, password)); return function (dispatch) { return loginUserApiCall(username, password).then(user => { dispatch(loginSuccess(user)); }, err => dispatch(loginFailure(err)) ); }; }
  6. THUNKS const loginThunk = (username, password) => dispatch => {

    dispatch(loginRequest(username, password)); return function (dispatch) { return loginUserApiCall(username, password).then(user => { dispatch(loginSuccess(user)); }, err => dispatch(loginFailure(err)) ); }; }
  7. THUNKS const loginThunk = (username, password) => dispatch => {

    dispatch(loginRequest(username, password)); return function (dispatch) { return loginUserApiCall(username, password).then(user => { dispatch(loginSuccess(user)); }, err => dispatch(loginFailure(err)) ); }; }
  8. SAGAS import { call, put, take } from 'redux-saga/effects'; function*

    loginUserWorker(action) { try { const { username, password } = action.payload; const user = yield call(loginUserApiCall, username, password); yield put(loginSuccess(user)); } catch (e) { yield put(loginFailure(e)); } } function* loginUserWatcher() { yield take(ActionType.LOGIN_REQUEST, loginUserWorker); }
  9. OBSERVABLES import { of } from "rxjs"; import { mergeMap,

    mapTo, map } from "rxjs/operators"; const loginRequestEpic = action$ => action$.pipe( ofType(ActionType.LOGIN_REQUEST), mergeMap(action => { const { username, password } = action.payload; return loginUserApiCall(username, password) }), mergeMap(res => of(loginSuccess(res))) catchErr(err => of(loginFailure(err))) );
  10. OBSERVABLES import { of } from "rxjs"; import { mergeMap,

    mapTo, map } from "rxjs/operators"; const loginRequestEpic = action$ => action$.pipe( ofType(ActionType.LOGIN_REQUEST), mergeMap(action => { const { username, password } = action.payload; return loginUserApiCall(username, password) }), mergeMap(res => of(loginSuccess(res))) catchErr(err => of(loginFailure(err))) );
  11. OBSERVABLES import { of } from "rxjs"; import { mergeMap,

    mapTo, map } from "rxjs/operators"; const loginRequestEpic = action$ => action$.pipe( ofType(ActionType.LOGIN_REQUEST), mergeMap(action => { const { username, password } = action.payload; return loginUserApiCall(username, password) }), mergeMap(res => of(loginSuccess(res))) catchErr(err => of(loginFailure(err))) );
  12. THIS IS HOW IT ENDS UP LOOKING LIKE… LOGIN SUCCESS

    LOGIN 
 FAILURE ASYNC 
 REQUEST LOGIN REQUEST ( LOG ANALYTICS LOAD DATA FOR DASHBOARD CHECK ACCESS CONTROL LOAD USER PROFILE DO MORE STUFF…
  13. SAGAS import { call, put, fork, cancel, takeEvery } from

    'redux-saga/effects'; function* watchMovieWorker(action) { try { const { query } = action.payload; const user = yield call(loadMovieApiCall, query); yield put(watchMovieSuccess(user)); } catch (e) { yield put(watchMovieFailure(e)); } } function* watchMovieWatcher() { while (yield take(ActionType.WATCH_MOVIE_REQUEST)){ const bgWatchMovieTask = yield fork(watchMovieWorker); yield take(ActionType.WATCH_MOVIE_CANCELLED); yield cancel(bgWatchMovieTask); } }
  14. OBSERVABLES const watchMovieRequestEpic = (action$, state$) => action$.pipe( ofType(ActionType.WATCH_MOVIE_REQUEST), delay(2000),

    mergeMap(action => { const { query } = action.payload; return loadMovieApiCall(query); }), mergeMap(res => of(watchMovieSuccess(res))), takeUntil(action$.pipe(ofType(ActionType.WATCH_MOVIE_CANCELLED))), catchError(err => { return of(watchMovieFailure(err)) }), repeat() );
  15. THIS IS JUST THE BEGINNING… LOG ANALYTICS LOAD MOVIES FOR

    DASHBOARD CHECK ACCESS CONTROL LOAD USER PROFILE DO MORE STUFF… LOGIN SUCCESS
  16. LET’S FOCUS ON 2 LOG ANALYTICS LOAD MOVIES FOR DASHBOARD

    CHECK ACCESS CONTROL LOAD USER PROFILE DO MORE STUFF… LOGIN SUCCESS
  17. THUNKS const netflixThunk = (username, password) => dispatch => {

    dispatch(loginRequest(username, password)); return function (dispatch) { return loginUserApiCall(username, password).then(user => { dispatch(loginSuccess(user)); return watchMovieApiCall().then(movie => { dispatch(watchMovieSuccess(movie)); return logAnalytics().then(data => { dispatch(logAnalyticsSuccess(data)); }, err => dispatch(logAnalyticsFailure(err)); }, err => dispatch(watchMovieFailure(err)); }, err => dispatch(loginFailure(err)) ); }; }
  18. const netflixThunk = (username, password) => dispatch => { dispatch(loginRequest(username,

    password)); return function (dispatch) { return loginUserApiCall(username, password).then(user => { dispatch(loginSuccess(user)); return watchMoviesApiCall().then(movie => { dispatch(watchMovieSuccess(movie)); return logAnalytics().then(data => { dispatch(logAnalyticsSuccess(data)); }, err => dispatch(logAnalyticsFailure(err)); }, err => dispatch(watchMovieFailure(err)); }, err => dispatch(loginFailure(err)) ); }; }
  19. SAGAS function* watchMovieWorker() { ... } function* logAnalyticsWorker() { ...

    } function* watcher() { const movies = yield* watchMovieWorker() yield put(ActionType.WATCH_MOVIE) const analytics = yield* logAnalyticsWorker() yield put(ActionType.LOG_ANALYTICS) }
  20. OBSERVABLES const logAnalyticsEpic = action$ => action$ .ofType(ActionType.LOGIN_SUCCESS) .reduce(logAnalytics) .flatMap(events

    => Observable.merge( events, ActionType.LOG_ANALYTICS)); const watchMovieEpic = action$ => action$ .ofType(ActionType.LOGIN_SUCCESS) .reduce(watchMovie) .map(movies => ActionType.WATCH_MOVIES) LOGIN SUCCESS
  21. SUMMARY Sagas: Pros: ‣ Got to dispatch a separate action

    to run ‣ ES6 generator knowledge ‣ Easy to test ‣ Has cancellation ‣ Flexible with saga effects - fork, spawn, cancel, throttle Cons: ‣ ES6 generator knowledge ‣ Can be harder to maintain if not done right ‣ As app scales, harder to do cancellation Thunks: Pros: ‣ Really easy to understand ‣ Can be used for smaller projects Cons: ‣ Return promises ‣ Promises cannot be cancelled ‣ Hard to test as you are testing functions ‣ Tightly coupled ‣ Hard to reuse ‣ is a function that returns a function ‣ Call back hell ‣ Too much nesting ‣ Code readability ‣ No way to cancel, debounce, retry Observables: Pros: ‣ You got to filter from a set of actions ‣ Rx js knowledge ‣ Can be transformed ‣ Can be re-used very easily ‣ Built-in debounce, throttle, ‣ Are lazy so you can retry, repeat ‣ Epic - actions in, actions out ‣ Are very powerful Cons: ‣ Testing is a bit more difficult than sagas ‣ RxJS has a huge learning curve ‣ Not all apps need that much power