Skip to content

Instantly share code, notes, and snippets.

@jimbol
Last active July 14, 2017 14:24
Show Gist options
  • Save jimbol/8e117ba6a407925f6ddcd39c2e5e78e1 to your computer and use it in GitHub Desktop.
Save jimbol/8e117ba6a407925f6ddcd39c2e5e78e1 to your computer and use it in GitHub Desktop.
Managed Flow vs. Branched Flow with Redux Saga

Managed Flow vs. Branched Flow with Redux Saga

Rather than dispatching actions to get us to the next step of an effect, we should manage the async flow in a single effect whenever possible.

Chaining effects and sagas in this manner leads to unexpected side effects.

GOOD

Managed Flow

Function called
v
|
|__.
.  |
.  |__.
.  .  |
.  .__|
.__|
|
|--> Dispatch action
|__.
.  |
.__|
|
|--> Dispatch action

The transformations are separate from the flow. We can create an effect which can be read from beginning to end to get a idea of the entire flow. This can be used both for synchonous functions or async generators. Facades can be used to wrap the specifics and can often be pure functions.

BAD

Branched flow

Function called
v
|
|__.
   |
   |
   |--> Dispatch actions

v
|
|__.
   |__.
      |
      |
      |--> Dispatch action

A given function gets responsibility for at least two things: Its given role and triggering the next item in the flow. This leads to implicit changes and unpredictable side-effects.

You can get lost in the chain. The starting point and ending point are difficult to find and follow.

Notes and Exceptions

Managed flow is not an end-all be-all approach to redux-sagas. Rather its a direction one can strive for. For example, you might have one entry point which delegates to different sagas based on some condition. Or, you might have an action that sets up some data, and sends it off to a generic saga.

Instead think, can my problem set be thought of as having a lifecycle? Where does that lifecycle begin and end? That is what the managed flow should encompass.

Example

A managed flow would look like this

yield takeEvery(ACTION_ONE, onActionOne);

function* onActionOne(action) {
  const firstActions = buildFirstActions(action);

  yield put(firstActions);
  
  const results = yield call(asyncRequest, action);

  if (results.error) {
    const errorActions = buildErrorActions(results, action);

    yield put(errorActions);
  }

  const successActions = buildSuccessActions(results, action);

  yield put(successActions);
}

While a forked flow would look like this

yield takeEvery(ACTION_ONE, onActionOne);
yield takeEvery(REQUEST_ACTION, onRequest);
yield takeEvery(ERROR_ACTION, onError);
yield takeEvery(SUCCESS_ACTION, onSuccess);

function* onActionOne(action) {
  const firstActions = buildFirstActions(action);

  yield put(firstActions);
  
  yield put(makeAsyncRequest, action);
}

function* onRequest(action) {
  const results = yield call(asyncRequest, action);

  if (results.error) {
    yield put(handleErrors(results, action))
  }

  yield put(handleSuccess(results, action))
}

function* onError(action) { ... }

function* onSuccess(action) { ... }
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment