https://mskims.github.io/redux-saga-in-korean/ - 공식문서

https://stackoverflow.com/questions/53798546/redux-saga-difference-between-yield-array-to-fork-in-the-root-saga - fork vs call vs 그냥 호출

병렬 태스크 실행

yield문은 비동기 컨트롤 플로우를 간단하고 선형적으로 나타내기에 좋습니다. 하지만 우리는 병렬 처리가 필요한 경우도 있습니다. 우리는 다음을 단순화하기 어렵습니다:

// wrong, effects will be executed in sequence
const users  = yield call(fetch, '/users'),
      repos = yield call(fetch, '/repos')

왜냐하면, 두 번째 이펙트는 첫번째 call이 resolve되기 전까지는 실행되지 않을 것이기 때문입니다. 대신에, 우리는 이렇게 써야 합니다:

import { all, call } from 'redux-saga/effects'

// correct, effects will get executed in parallel
const [users, repos]  = yield all[
  call(fetch, '/users'),
  call(fetch, '/repos')
]

위와 같이 이펙트의 배열을 yield하면, 제너레이터는 모든 이펙트들이 resolve되거나, 어느 하나라도 reject될 때까지 봉쇄(blocked)됩니다 (Promise.all의 방식처럼요).

에러 핸들링

이 섹션에선 이전 예제의 실패 케이스들을 어떻게 다룰지 볼겁니다. Api.fetch 함수가 어떤 이유때문에 fetch 가 실패했을때, reject 된 Promise 를 리턴한다고 가정합시다.

우리의 Saga 안에서 PRODUCTS_REQUEST_FAILED 액션을 스토어에 dispatch 함으로써 이런 에러들을 다룰겁니다.

Saga 내에서 친숙한 try/catch 문법을 써서 에러들을 잡아낼 수 있습니다.

import Api from './path/to/api'
import { call, put } from 'redux-saga/effects'

// ...

function* fetchProducts() {
  try {
    const products = yield call(Api.fetch, '/products')
    yield put({ type: 'PRODUCTS_RECEIVED', products })
  }
  catch(error) {
    yield put({ type: 'PRODUCTS_REQUEST_FAILED', error })
  }
}

실패하는 경우도 테스트하기 위해서, 제너레이터의 throw 메소드를 사용할겁니다.

import { call, put } from 'redux-saga/effects'
import Api from '...'

const iterator = fetchProducts()

// expects a call instruction
assert.deepEqual(
  iterator.next().value,
  call(Api.fetch, '/products'),
  "fetchProducts should yield an Effect call(Api.fetch, './products')"
)

// create a fake error
const error = {}

// expects a dispatch instruction
assert.deepEqual(
  iterator.throw(error).value,
  put({ type: 'PRODUCTS_REQUEST_FAILED', error }),
  "fetchProducts should yield an Effect put({ type: 'PRODUCTS_REQUEST_FAILED', error })"
)

이 경우에선, throw 메소드로 가짜 에러를 전달하고 있습니다. 이렇게 된다면 제너레이터는 현재 흐름을 중단하고 catch 블록을 실행할겁니다.

무조건 API 에러들을 try/catch 블록 안에서 다뤄야 한다는건 아닙니다. 에러 상태를 리턴하는 평범한 값을 돌려주는 API 서비스를 만들수도 있습니다. 예를들면, Promise rejection 들을 catch 하여 에러필드를 가진 객체를 만들수도 있습니다.

import Api from './path/to/api'
import { call, put } from 'redux-saga/effects'

function fetchProductsApi() {
  return Api.fetch('/products')
    .then(response => ({ response }))
    .catch(error => ({ error }))
}

function* fetchProducts() {
  const { response, error } = yield call(fetchProductsApi)
  if (response)
    yield put({ type: 'PRODUCTS_RECEIVED', products: response })
  else
    yield put({ type: 'PRODUCTS_REQUEST_FAILED', error })
}