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.
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.
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.
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.
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) { ... }