March 01, 2020
Translated from the Korean original.
![]()
Have you ever reached for redux-saga to handle async work in redux?
“The mental model is that a saga is like a separate thread in your application that’s solely responsible for side effects. redux-saga is a redux middleware, which means this thread can be started, paused and cancelled from the main application with normal redux actions, it has access to the full redux application state and it can dispatch redux actions as well.”
Source: the redux-saga official docs
Put simply, redux-saga’s whole model comes down to this: a saga owns every effect. You can start, pause, or cancel work through ordinary Redux actions, it can read the state Redux manages, and it can dispatch actions of its own.
To really understand what that means, you first need to understand the Saga Pattern itself.
The Saga Pattern started getting attention alongside the rise of microservices. People read it a few different ways: MSDN, for one, treats Saga as the Process Manager inside the CQRS pattern. But the core idea is simple: eliminate the need for distributed transactions by giving every transaction its own compensating transaction.
A compensating transaction is the transaction that runs whenever another transaction fails.
Say we have a service that looks like this.

It takes an order from a user, then handles payment, inventory, and delivery, each stage owned by its own service.

A Saga is just a chain of local transactions, each one updating data inside its own service. The first transaction fires from an outside request, and every transaction after that only starts once the one before it finishes.
There are two common ways to implement a Saga transaction.

In the example above, the events flow like this.

Here’s how a rollback plays out under Events/Choreography.
Note. Every transaction carries an id, so every listener can tell immediately which transaction just fired.
Command/Orchestration introduces a dedicated Orchestrator that decides when each service acts and what it does. The Saga Orchestrator talks to each service through a command/reply pattern, handing off the work.
Here’s what that looks like.

The OSO owns every transaction the order needs. If something goes wrong, it sends commands out to each service telling them to roll back.
In practice, the Saga Orchestrator is built as a State Machine that tracks each command alongside the state it maps to.

The Orchestration Saga has a few clear advantages.
It has downsides too.
Command/Orchestration works well when services share a lot of events or context, or when Event Routing gets complicated.
Event Routing is just which service an event needs to reach, and which event should follow it.
Events/Choreography skips the overhead of managing an Orchestrator entirely, so it’s the better pick when the overall service footprint is small and events don’t depend heavily on each other.
So how does the Saga we just covered connect to redux-saga? redux-saga is the Orchestrator sitting between the actions that fire and the state Redux manages.
function sagaMiddleware({ getState, dispatch }) { // Initialize Saga
return next => action => { if (sagaMonitor && sagaMonitor.actionDispatched) { sagaMonitor.actionDispatched(action) } const result = next(action) // dispatch to the reducer channel.put(action) // notify the saga that the action was dispatched
return result }}Every action that passes through a saga hits the reducer first. Only after that does a communication channel called a channel let the saga know the action went out.
Let’s dig into this with an example.
Code from redux-saga’s Beginner Tutorial.
import { put, takeEvery, delay } from 'redux-saga/effects'
// Our worker Saga: will perform the async increment taskexport function* incrementAsync() { yield delay(1000) yield put({ type: 'INCREMENT' })}
// Our watcher Saga: spawn a new incrementAsync task on each INCREMENT_ASYNCexport function* watchIncrementAsync() { yield takeEvery('INCREMENT_ASYNC', incrementAsync)}Bring redux into the picture too, and the whole flow looks like this.

The Saga listens for the INCREMENT_ASYNC action and yields the delay and put effects. What a saga yields, and what it actually returns, is a plain JavaScript object.
The middleware picks up that effect and acts on it. In the example above, the first yield delay pauses execution and waits out the full second.
Note. redux-saga splits effects into blocking and non-blocking ones. A blocking effect waits for the work to finish; a non-blocking one moves on without waiting.
callis the classic blocking effect, andforkis the classic non-blocking one.
As mentioned, a saga yields effects, and what it returns is a JavaScript object. Here’s the code redux-saga uses internally to build those effects.
const makeEffect = (type, payload) => ({ [IO]: true, combinator: false, type, payload,})
export function call(fnDescriptor, ...args) { // Validate...
return makeEffect(effectTypes.CALL, getFnCallDescriptor(fnDescriptor, args))}
export function fork(fnDescriptor, ...args) { // Validate...
return makeEffect(effectTypes.FORK, getFnCallDescriptor(fnDescriptor, args))}
export function race(effects) { const eff = makeEffect(effectTypes.RACE, effects) eff.combinator = true return eff}Much like an action creator function, each of redux-saga’s effects is just an object built by makeEffect(...). Once you return an effect object carrying the description of what work needs to happen, the middleware is what actually goes and does it.
What happens when the same event keeps firing back to back? How does a saga orchestrate that? redux-saga hands you the takeLatest API for exactly this.
export default function takeLatest(patternOrChannel, worker, ...args) { const yTake = { done: false, value: take(patternOrChannel) } const yFork = ac => ({ done: false, value: fork(worker, ...args, ac) }) const yCancel = task => ({ done: false, value: cancel(task) }) // Set action and task
return fsmIterator( { q1() { return { nextState: 'q2', effect: yTake, stateUpdater: setAction } }, q2() { return task ? { nextState: 'q3', effect: yCancel(task) } : { nextState: 'q1', effect: yFork(action), stateUpdater: setTask } }, q3() { return { nextState: 'q1', effect: yFork(action), stateUpdater: setTask } }, }, 'q1', `takeLatest(${safeName(patternOrChannel)}, ${worker.name})`, )}Starting from q1, every time the same event fires again, it cancels (yCancel) whatever was running before and forks into the next state. Just as the Orchestrator Pattern rolls back through commands, a saga manages its effects through cancellation.
Because redux-saga manages side effects as plain effect objects, testing it is easy. You end up writing tests that basically walk through the saga one step at a time.
Take the code below as an example.
export function* fetchHelloWorld() { try { const helloText = yield select(helloSelector.text);
const { data } = yield call( getHello, helloText, );
yield put(helloWorldActions.success(data)); } catch(error) {
yield put(helloWorldActions.fail(error.status)); }}Let’s treat every yield in this code as its own Step and write a test around that.
describe('HelloWorldsaga', () => { it('should dispatch success action', async () => { // Given const testRequest = {}; const testResult ={ data: { text: 'Mock Text' }, };
const gen = fetchHelloWorld(); // 0 // Then expect(gen.next().value).toEqual(select(helloSelector.text)); // 1 expect(gen.next(testRequest).value).toEqual( call(getHello, testRequest) ); // 2 expect(gen.next(testResult).value).toEqual( put(helloWorldActions.success(testResult.data)) ); // 3 expect(gen.next().done).toBeTruthy(); // 4 });});fetchHelloWorld saga to gen.gen’s next step matches the select(helloSelector.text) effect.call. Since call takes fn and args, we pass testRequest into gen.next(the call step), then check the result actually matches call(getHello, testRequest).call gets dispatched as a success action. Again, we pass the pre-mocked testResult in as the argument to gen.next.fetchHelloWorld, so next() comes back done at this step.For more, check out redux-saga: testing and Jbee’s post Testing the Store and Business Logic.
That covers the Saga Pattern and redux-saga. A saga only issues commands; the middleware is what actually carries out the work. That Command/Orchestration shape can be a great fit for managing lots of events or context between services, and complex event routing.
And because the middleware’s whole job is taking whatever value a saga yields and acting on it, testing turns out to be easy too, as covered in the Test section.
https://medium.com/@jeanpan/saga-pattern-redux-saga-e694a31576ab
https://blog.couchbase.com/saga-pattern-implement-business-transactions-using-microservices-part/
https://docs.microsoft.com/en-us/previous-versions/msp-n-p/jj591569(v=pandp.10)?redirectedfrom=MSDN
https://www.youtube.com/watch?v=UxpREAHZ7Ck&index=5&list=PLZl3coZhX98oeg76bUDTagfySnBJin3FE