This article is my understanding of saga pattern & redux-saga. Please feel free to let me know if there is anything wrong.
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.
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.