Saga pattern & redux-saga

Jean Pan
3 min readOct 23, 2016

--

This article is my understanding of saga pattern & redux-saga. Please feel free to let me know if there is anything wrong.

http://kotaku.com/adventure-time-explained-1703199261

Saga Pattern

The term saga was introduced by Hector Garcia-Molina to define a mechanism to handle system failures in long lived transaction.

From Clarifying the Saga Pattern: A Saga is a distribution of long-living transactions where steps may interleave, each with associated compensating transactions providing a compensation path across databases in the occurrence of a fault that may or may not compensate the entire chain back to the originator.

http://kellabyte.com/2012/05/30/clarifying-the-saga-pattern/

Above figure shows saga compensating due to a failed transaction (T4).

If all the transactions succeed: T1 — T2 — T3 … — Tn but if Tn failed : T1 — T2 — T3 … — Tn — Cn — C(n-1)… — C1

Watch this talk to learn more about saga pattern.

redux-saga

redux-saga is a redux middleware for handling side effects.

How does redux-saga work ?

redux-saga is implemented with generator functions. Generator functions can be paused and resumed and yield multiple values.

To understand how redux-saga works, let’s see the example directly and compare with redux-thunk.

Example : Request data from a url when an UI component is clicked

redux-thunk

function fetchUrl(url) {
return (dispatch) => {
dispatch({
type: 'FETCH_REQUEST'
});

fetch(url).then(data => dispatch({
type: 'FETCH_SUCCESS',
data
}));
}
}

Dispatch fetchUrl when UI component is clicked.

dispatch(
fetchUrl(url)
);

To avoid React components being pollute with business logic, redux-thunk is the simplest way to do that, However, it may have some problem …

  • Difficult to test : to test the task logic, need to mock all invoked functions.
  • Difficult to coordinate concurrent tasks : how to cancel the pending fetch ?
  • Scattered business logic

redux-saga

UI components dispatch plain object actions instead invoking the tasks directly.

dispatch({
type: 'FETCH_REQUEST',
url: /* ... */}
);

Create a saga that watches for the dispatched action and forks the task.

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

// The watcher: watch actions and coordinate worker tasks
function* watchFetchRequests() {
while(true) {
const action = yield take('FETCH_REQUEST');

yield fork(fetchUrl, action.url);
}
}

// The worker: perform the requested task
function* fetchUrl(url) {
const data = yield call(fetch, url);

yield put({
type: 'FETCH_SUCCESS',
data
});
}

Note all function from redux-saga is pure functions and yields plain Javascript object called effect. For example, call(fetchUrl, url) returns { type: CALL, function: fetchUrl, args: [url] }. The effects are created through functions but are executed in the middleware.

The separation between effect creation and execution makes testing simple.

const generator = fetchUrl('http://api');const expected = call(fetch, 'http://api');
const actual = generator.next().value;
assert.deepEqual(expected, actual);

Because of generators, we have much more flexibility to coordinate concurrent tasks.

import { take, fork, cancel, call, put } from 'redux-saga/effects';// cancel any pending fetch whenever request a new fetch
function* watchFetchRequests() {
let currentTask;

while(true) {
const action = yield take('FETCH_REQUEST');

if(currentTask) {
yield cancel(currentTask);
}

currentTask = yield fork(fetchUrl, action.url);
}
}

Why redux-saga ?

  • Declarative effects: all operations in redux-saga yield plain Javascript objects, which makes it easier to test the business logic.
  • Advanced async control flow : simply describe async flow with sync style and familiar control flow constructs.
  • Concurrency management : provide primitives and operators to manage concurrency between tasks; able to fork multiple background tasks in parallel and cancel a running task.
  • Side effect handler : all side effects are moved into sagas, UI components do not perform any business logic but only dispatch actions to notify what happened.

Conclusion

Saga pattern is a design pattern to handle system failures in long lived transaction by dividing the long lived transaction into a set of independent sub-transactions with compensating sub-transactions.

redux-saga is a redux middleware for handling side effects which make React Redux App more testable, maintainable, and reusable.

Reference

--

--

Jean Pan

Software Engineer @Indeed. Ex-Yahoo! 東京在住中.