Non blocking calls with fork/join

the yield statement causes the generator to pause until the yielded effect resolves or rejects. If you look closely at this example:

function* watchFetch() {
  while ( yield take(FETCH_POSTS) ) {
    yield put( actions.requestPosts() )
    const posts = yield call(fetchApi, '/posts') // blocking call
    yield put( actions.receivePosts(posts) )
  }
}

the watchFetch generator will wait until yield call(fetchApi, '/posts') terminates. Imagine that the FETCH_POSTS action is fired from a Refresh button. If our application disables the button between each fetch (no concurrent fetches) then there is no issue, because we know that no FETCH_POSTS action will occur until we get the response from the fetchApi call.

But what happens if the application allows the user to click on Refresh without waiting for the current request to terminate?

The following example illustrates a possible sequence of the events

UI                              watchFetch
--------------------------------------------------------
FETCH_POSTS.....................call fetchApi........... waiting to resolve
........................................................
........................................................                     
FETCH_POSTS............................................. missed
........................................................
FETCH_POSTS............................................. missed
................................fetchApi returned.......
........................................................

When watchFetch is blocked on the fetchApi call, all FETCH_POSTS occurring in between the call and the response are missed.

To express non-blocking calls, we can use the fork function. A possible rewrite of the previous example with fork can be:

import { fork, call, take, put } from 'redux-saga'

function* fetchPosts() {
  yield put( actions.requestPosts() )
  const posts = yield call(fetchApi, '/posts')
  yield put( actions.receivePosts(posts) )
}

function* watchFetch() {
  while ( yield take(FETCH_POSTS) ) {
    yield fork(fetchPosts) // non blocking call
  }
}

fork, just like call, accepts function/generator calls.

yield fork(func, ...args)       // simple async functions (...) -> Promise
yield fork(generator, ...args)  // Generator functions

The result of yield fork(api) is a Task descriptor. To get the result of a forked Task in a later time, we use the join function

import { fork, join } from 'redux-saga'

function* child() { ... }

function *parent() {
  // non blocking call
  const task = yield fork(subtask, ...args)

  // ... later
  // now a blocking call, will resume with the outcome of task
  const result = yield join(task)

}

the task object exposes some useful methods

method return value
task.isRunning() true if the task hasn't yet returned or throwed an error
task.result() task return value. `undefined` if task is still running
task.error() task thrown error. `undefined` if task is still running
task.done a Promise which is either
  • resolved with task's return value
  • rejected with task's thrown error