Redux-Saga V.S. Redux-Observable 
===
### Redux-Saga V.S. Redux-Observable 
1. Metal Model
2. Side by side Comparison
3. Which one do I prefer and Why?
4. Futher Reading and Discussion
---
### Metal Model
> Saga = **Worker** + **Watcher**
> Rxjs = **Epic( Type + Operators )**
----
> Saga = **Worker** + **Watcher**
```javascript
import API from '...'
function* Watcher(){
    yield takeEvery('do_thing', Worker)
}
function* Worker() { 
    const users = yield API.get('/api/users')
    yield put({type:'done', users})
}
```
----
> Rxjs = **Epic( Type + Operators )**
```javascript
import API from '...'
const Epic = action$ => 
    action$
        .ofType('do_thing')
        .flatMap(()=>API.get('/api/users'))
        .map(users=>({type:'done', users}))
```
---
### Side by side Comparison
----
> Demo Part (1/3)
1. Fetch User
2. Fetch User (cancelable)
3. Do three things in sequence
4. Login, Logout, Cancel (with redux)
----
> Fetch User from **++/api/users/1++**
> Saga
```javascript
import axios from 'axios' 
function* watchSaga(){
  yield takeEvery('fetch_user', fetchUser) // waiting for action (fetch_user)
}
function* fetchUser(action){
    try {
        yield put({type:'fetch_user_ing'})
        const response = yield call(axios.get,'/api/users/1')
        yield put({type:'fetch_user_done',user:response.data})
  } catch (error) {
        yield put({type:'fetch_user_error',error})
  }
}
```
----
> Fetch User from **++/api/users/1++**
> Rxjs
```javascript
import axios from 'axios'
const fetchUserEpic = action$ => 
    action$
        .ofType('fetch_user')
        .map(()=>({type:'fetch_user_ing'}))
        .flatMap(()=>axios.get('/api/users/1'))
        .map(response=>response.data)
        .map(user=>({type:'fetch_user_done', user}))
```
----
> Fetch User from **++/api/users/1++** (cancelable)
> Saga
```javascript
import { take, put, call, fork, cancel } from 'redux-saga/effects'
import API from '...'
function* fetchUser() {
    yield put({type:'fetch_user_ing'})
    const user = yield call(API)
    yield put({type:'fetch_user_done', user})  
}
function* Watcher() {
    while(yield take('fetch_user')){
        const bgSyncTask = yield fork(fetchUser)
        yield take('fetch_user_cancel')        
        yield cancel(bgSyncTask)
    }
}
```
----
> Fetch User from **++/api/users/1++** (cancelable)
> Rxjs
```javascript
const fetchUserEpic = action$ =>
    actions$
        .ofType('fetch_user')
        .map(()=>({type:'fetch_user_ing'}))
        .flatMap(()=>{
            return Observable
                .ajax
                .get('/api/user/1')
                .map(user => ({ type: 'fetch_user_done', user }))
                .takeUntil(action$.ofType('fetch_user_cancel'))
        })
```
----
> Do three things in sequence
> Saga
```javascript
function* worker1() { ... }
function* worker2() { ... }
function* worker3() { ... }
function* watcher() {
  const score1 = yield* worker1()
  yield put(({type:'show_score', score1})
  const score2 = yield* worker2()
  yield put(({type:'show_score', score2})
  const score3 = yield* worker3()
  yield put(({type:'show_score', score3})
}
```
----
> Do three things in sequence
> Rxjs
```javascript
const score1Epic = action$ =>
    action$
        .ofType('score1')
        .reduce(worker1)
        .flatMap(score=>{
             return Observable.merge(
                {type:'show_score', score},
                {type:'score2'}
             )
         })
const score2Epic = action$ =>
    action$
        .ofType('score2')
        .reduce(worker2)
        .flatMap(score=>{
            return Observable.merge(
                {type:'show_score', score},
                {type:'score3'}
            )
        })
 const score3Epic = action$ =>
    action$
        .ofType('score3')
        .reduce(worker3)
        .map(score=>({type:'show_score', score}))
           
```
----
> Login, token, Logout, Cancel
> (with redux)
```javascript
const store = {
    token: null,
    isFetching: false
}
const tokenReducer = (state=null, action) => {
    switch (action.type) {
        case 'login_success':
            return action.token
        case 'login_error':
        case 'login_cancel':
        case 'logout'
            return null
        default:
            return state
    }
}
const isFetching = (state=false, action) => {
    switch (action.type) {
        case 'login_request':
            return true
        case 'login_success':
        case 'login_error':
        case 'login_cancel':
        case 'logout'
            return false
        default:
            return state
    }
}
```
----
> Login, token, Logout, Cancel
> Saga
```javascript
import { take, put, call, fork, cancel } from 'redux-saga/effects'
import Api from '...'
function* loginWatcher() {
    const { user, password } = yield take('login_request')
        , task = yield fork(authorize, user, password)
        , action = yield take(['logout', 'login_error'])
    
    if (action.type === 'logout') {
        yield cancel(task)
        yield put({type:'login_cancel'})
    }
  
}
function* authorize(user, password) {
    try {
        const token = yield call(Api.getUserToken, user, password)
        yield put({type: 'login_success', token})
  } catch (error) {
        yield put({type: 'login_error', error})
  }
}
```
----
> Login, token, Logout, Cancel
> Rxjs
```javascript
const authEpic = action$ => 
    action$
        .ofType('login_request')
        .flatMap(({payload:{user,password}})=>
            Observable
                .ajax
                .get('/api/userToken', { user, password })
                .map(({token}) => ({ type: 'login_success', token }))
                .takeUntil(action$.ofType('login_cancel', 'logout'))
                .catch(error => Rx.Observable.of({type:'login_error', error}))
```
---
> Demo Part 2/3
----
> Logger
```javascript
// ----- Saga ----- \\
while (true) {
    const action = yield take('*')
        , state = yield select()
console.log('action:', action)
console.log('state:', state)
}
// ----- Rxjs ----- \\
.do(value=>console.log(value))
```
----
> Take latest request 
```javascript
// ----- Saga ----- \\
takeLatest()
// ----- Rxjs ----- \\
.switchMap()
```
----
> Retry with delay (1000ms)
```javascript
// ----- Saga ----- \\
... still thinking about it
// ----- Rxjs ----- \\
.retryWhen(errors=>{
    return errors.delay(1000).scan((errorCount, err)=>{
        if(errorCount < 3) return errorCount + 1
        throw err              
    }, 0)
})
```
----
> Error Handling
```javascript
// ----- Saga ----- \\
try {
    // ... do things 
} catch (error) {
    yield put({ type:'fetch_user_error',error })
}
// ----- Rxjs ----- \\
.catch(error => Observable.of({ type:'fetch_user_error', error }))
```
----
> Running Tasks In Parallel 
```javascript
// ----- Saga ----- \\
import { call } from 'redux-saga/effects'
const [users, repos]  = yield [
  call(fetch, '/users'),
  call(fetch, '/repos')
]
// ----- Rxjs ----- \\
.flatMap(()=>{
	return Observable.merge(promiseA, promiseB)
})
```
----
> Throttling, Debouncing, Retrying
> Saga
```javascript
// ---- Throttling ---- \\
yield throttle(500, 'input_change', fun)
// ---- Debouncing ---- \\
yield call(delay, 500)
// ---- Retrying ---- \\
function* retryAPI(data) {
  for(let i = 0; i < 3; i++) {
    try {
      const apiResponse = yield call(apiRequest, { data });
      return apiResponse;
    } catch(err) {
      if(i < 3) {
        yield call(delay, 2000);
      }
    }
  }
  throw new Error('API request failed');
}
```
----
> Throttling, Debouncing, Retrying
> Saga
```javascript
// ---- Throttling ---- \\
.throtleTime(1000)
// ---- Debouncing ---- \\
.debouncing(1000)
// ---- Retrying ---- \\
.retry(3)
```
---
> Part (3/3)
> Test 
----
> Saga
```javascript
{
  CALL: {
    fn: Api.fetch,  <<<<<<<<<<<<< make sure call with the right fn
    args: ['./products'] <<<<<<<< make sure call with the right args
  }
}
expect(
    fetchUser('api/users/1').next().value
).to.eql( 
    call(axios.get, 'api/users/1') 
)
```
----
> Rxjs
```javascript
// fake store ...
// ...
// ...
//   https://redux-observable.js.org/docs/recipes/WritingTests.html
```
---
### Which One Do I prefer?
----
> Coding Style
#### Saga
> 1. Effects as data
> 2. (Imperative style) 
> Tell Program **HOW** to do things
> Take time to reason about code
#### Rxjs
    
> 1. Events as data
> 2. (Declarative Style)
> Tell Program **WHAT** things to do
> Easy to reason about code
    
----
> Function Reusability
#### Saga
> Low
#### Rxjs
    
> High
    
----
> Test 
#### Saga
> * Unit Test(easy)
> * Intergration Test(easy)
> * Test might fail if the order change
#### Rxjs
> * Unit Test(Very easy)
> * Intergration Test(Require Mocking)
> * Test will not fail(same end result)
----
> Learn Saga
> * Redux (1 day)
> * Generator (1 day)
> * Redux-Saga (few hours)
> * Skill is not transferable
> Total Learning Time:
> > 3~5 days
----
> Learn Rxjs
> * Redux (1 day) 
> * Rxjs (few days)
> * Functional Programming (2 days)
> * Redux-Observable (1 day)
> * Skill is transferable
        
> Total Learing Time: 
> > 1~2 weeks
      
---
### Futher Reading 
> merge()
> map()
> filter()
> scan()
> combineLatest()
> concat()
> do()
> more ...
> Visual Learning  http://rxmarbles.com/
----
### Futher Discussion
> Saga = **Effects** as data
> Rxjs = **Events** as data
> ++**Saga**++ doesn't actually perform the side effects itself. Instead, the helper functions will create objects that represents the intent of side effect. Then the ++**internal**++ library performs it for you". 
```javascript
call (http.get, '/users/1') // the task
CALL: { fn: http.get,  args: ['/users/1'] } // the intent
```
> This makes testing extremely easy, without the need for mocking.
> 
----
###### tags: `React`, `Redux`, `Redux-Saga`, `Redux-Observable`
             
            {"metaMigratedAt":"2023-06-14T12:18:01.896Z","metaMigratedFrom":"Content","title":"Redux-Saga V.S. Redux-Observable","breaks":true,"contributors":"[{\"id\":\"daa56725-1949-4470-a4db-476f571b3248\",\"add\":75,\"del\":0}]"}