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.

Ba0adbbfa699d09f6ee7b4402e4dcf9f?s=128

Ankita Kulkarni

November 29, 2018
Tweet

Transcript

  1. LET’S FIGHT REDUX SIDE-EFFECTS SHOWDOWN ANKITA KULKARNI, RANGLE.IO @KULKARNIANKITA9

  2. REDUX IS THE PREFERRED 
 STATE-MANAGEMENT LIBRARY

  3. WHAT ARE 
 SIDE-EFFECTS?

  4. POPULARITY ⭐ REDUX THUNKS ⭐~11K REDUX OBSERVABLES ⭐~6K REDUX 


    SAGAS ⭐~16K
  5. LET’S LOOK AT LOGIN

  6. AT FIRST, 
 LOGIN IS SIMPLE

  7. None
  8. AT FIRST, LOGIN IS SIMPLE LOGIN SUCCESS LOGIN 
 FAILURE

    LOGIN REQUEST ActionType.LOGIN_REQUEST ActionType.LOGIN_FAILURE ActionType.LOGIN_SUCCESS
  9. 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, });
  10. 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 } }
  11. None
  12. THINGS GET REALLY INTERESTING WITH ASYNC… LOGIN SUCCESS LOGIN 


    FAILURE ASYNC 
 REQUEST LOGIN REQUEST (
  13. None
  14. REDUX THUNKS PROMISES REDUX 
 SAGAS WORKER + WATCHER REDUX

    OBSERVABLES EPIC (TYPE + OPERATORS) MENTAL MODEL
  15. EXAMPLE PLEASE…

  16. 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)) ); }; }
  17. 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)) ); }; }
  18. 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)) ); }; }
  19. 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); }
  20. 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))) );
  21. 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))) );
  22. 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))) );
  23. REAL LIFE IS HARD…

  24. 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…
  25. USERS ARE VERY NOTORIOUS

  26. LET’S LOOK AT A COMPLEX EXAMPLE

  27. None
  28. CANCELING REQUESTS

  29. THUNKS It is very hard to cancel requests in thunks

  30. 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); } }
  31. 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() );
  32. DOING MANY THINGS AT ONCE

  33. THIS IS JUST THE BEGINNING… LOG ANALYTICS LOAD MOVIES FOR

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

    CHECK ACCESS CONTROL LOAD USER PROFILE DO MORE STUFF… LOGIN SUCCESS
  35. 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)) ); }; }
  36. 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)) ); }; }
  37. 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) }
  38. 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
  39. 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
  40. None
  41. WINNER ✨ REDUX SAGAS
 ⭐

  42. None
  43. THANK YOU @KULKARNIANKITA9