redux-saga: four effects, one start call, and a concurrency choice nobody makes for you
An alternative side effect model for Redux apps
At a glance
- What is it?
- redux-saga runs a generator as a separate thread inside a Redux app so side effects become testable steps and failures become plain actions. The library is small and the wiring is four lines, but error handling, task cancellation and helper choice are all left to the code you write.
- Who is it for?
- Choose redux-saga when your side effects need to be readable and testable as generator steps and you want failures to reach the store as ordinary actions. Pass on it when the generator and runtime machinery costs more than the callback nesting it removes.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 1 day ago.
- What is it written in?
- Mainly JavaScript, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on November 14, 2026, and from our analysis. They are not legal advice.
Star history
- redux-saga/redux-saga22,410 ★ · -8
| Repository | 2026-09-21 | 2026-11-14 |
|---|---|---|
| redux-saga/redux-saga | 22.4k | 22.4k |
-8 Stars in the last 54 days, measured from the daily GitHub snapshot. Compare with other projects
Editorial analysis
A saga is a thread the store starts, pauses and cancels
redux-saga treats a side effect as work owned by a separate thread inside the application. The mental model the project gives: a saga is like a separate thread solely responsible for side effects, it is redux middleware, so that thread can be started, paused and cancelled from the main application with normal redux actions. It reads the full application state and dispatches actions back into the store.
The thread is written as an ES6 generator. Yielding effects instead of calling promises inline is what makes a fetch readable as a sequence of steps, and the project compares the result to `async`/`await` while noting that generators carry a few more features the library needs.
Installation is one package from npm, or yarn:
$ npm install redux-saga$ yarn add redux-sagaThe comparison the project makes is against `redux-thunk`, named as the thing you may have used for data fetching before. Against it, redux-saga claims three outcomes: no callback hell, asynchronous flows that are easy to test, and actions that stay pure. The claim is about shape rather than speed. How many requests run at once is where the real decisions start.
run() is the line that starts the thread, not applyMiddleware
The wiring is four statements. Middleware is created, mounted with `applyMiddleware`, and only then started:
import { createStore, applyMiddleware } from 'redux'
import createSagaMiddleware from 'redux-saga'
import reducer from './reducers'
import mySaga from './sagas'
// create the saga middleware
const sagaMiddleware = createSagaMiddleware()
// mount it on the Store
const store = createStore(reducer, applyMiddleware(sagaMiddleware))
// then run the saga
sagaMiddleware.run(mySaga)`run` is the load-bearing call. Mounting puts the saga system into the dispatch path, but the generator handed to `run` does nothing until that call happens, which is why the example places it after `createStore` rather than inside it. Because starting is a separate statement rather than part of `applyMiddleware`, a store can be built with the middleware attached and no task ever started, and the store itself has no way to report that. Watch for the symptom instead: actions dispatch, reducers run, and no fetch saga responds to `USER_FETCH_REQUESTED`.
takeEvery is what makes two responses race
The example saga file imports four things from `redux-saga/effects`: `call`, `put`, `takeEvery` and `takeLatest`. The comment above the root generator says it starts the worker on each dispatched `USER_FETCH_REQUESTED` action and allows concurrent fetches of user.
That concurrency is the part to read twice. Two `USER_FETCH_REQUESTED` actions start two `fetchUser` tasks, each calling the API and each putting its own `USER_FETCH_SUCCEEDED`. Nothing in that arrangement orders the two responses, so a slow first request can write after a fast second one and leave the store holding the older user. The same file imports `takeLatest`, which leaves the choice sitting in front of you. Whether a stale response is allowed to land is a product decision, and in this library it is made with a single word in a single import.
Every worker saga catches its own errors by hand
Failure handling is not central machinery. The whole error path is the catch branch of `fetchUser`:
function* fetchUser(action) {
try {
const user = yield call(Api.fetchUser, action.payload.userId)
yield put({ type: 'USER_FETCH_SUCCEEDED', user: user })
} catch (e) {
yield put({ type: 'USER_FETCH_FAILED', message: e.message })
}
}A rejected call becomes a plain action carrying `e.message`, so the failure reaches the store the same way a success does and a component can render it without importing the API layer. The cost is repetition. Nothing here catches errors on your behalf, so a worker without a try/catch has no path into the store at all, and a worker with one decides for itself whether to put a failure action, rethrow, or do both. The README points to a Troubleshooting page on the project site rather than describing a default, which is where you would look for the convention the example does not spell out.
regenerator-runtime has to be imported before redux-saga
Generators are the hard requirement, not Redux. For a browser without ES2015 generator support, the project says you must transpile them and provide a valid runtime such as the one at https://unpkg.com/regenerator-runtime/runtime.js, and that the runtime must be imported before redux-saga:
import 'regenerator-runtime/runtime'
// then
import sagaMiddleware from 'redux-saga'The ordering is the part that bites, because it is a runtime condition rather than a type error, and nothing about the import syntax forces it. For the `<script>` tag delivery path there is no module graph to order at all, which leaves transpiling and supplying a runtime entirely to whoever writes the page. The example folder `counter-vanilla` states the constraint in plain terms: the browser must support Generators, and latest versions of Chrome, Firefox and Edge qualify. That example keeps all of its source inlined in `index.html`, so a plain double-click run is the quickest way to find out whether a target browser is on the right side of that line.
The UMD global and the npm export need different calls
A UMD build sits in the `dist/` folder and is served from unpkg at https://unpkg.com/redux-saga/dist/redux-saga.umd.js, with a minified sibling at https://unpkg.com/redux-saga/dist/redux-saga.umd.min.js. Under that build the package is available as `ReduxSaga` on the window object, and creating the middleware from it takes a different call than the ES module path:
var sagaMiddleware = ReduxSaga.default()The `.default()` is worth copying exactly. The ES module path calls `createSagaMiddleware()` as a default import, while the window global reaches the same factory through a `default` property on the UMD namespace. Two call shapes for one package means the snippet you paste depends on how the page loads redux-saga, and a global reference that stops at the namespace has no second chance. The stated reason to use this path at all is not using Webpack or Browserify, which is also the only situation where you would notice the difference, since a bundled app never touches the window global.
lint covers packages/* and leaves every example unchecked
Working on the project runs through pnpm and a workspace layout covering `examples/*` and `packages/*`. From a fresh clone:
$ git clone https://github.com/redux-saga/redux-saga.git
$ cd redux-saga
$ pnpm install
$ pnpm testThe root `test` script is `vitest run`. Per-example scripts point into the workspace directories: `npm run counter`, `npm run cancellable-counter`, `npm run shop`, `npm run async`, `npm run real-world`. Two gaps are visible in the definitions. The `lint` script is scoped to `packages/*/src packages/*/test`, which excludes `examples/` entirely, so a demo can drift without any check touching it. And the real-world example, the one with webpack hot reloading, carries the comment that there are no tests yet, so it is the demo the project itself does not exercise.
Maintenance has recent evidence. The repository is not archived and the last push landed on 2026-10-25. Three packages shipped on 2026-07-26: [email protected], @redux-saga/[email protected] and @redux-saga/[email protected]. Releases run through `.changeset/` and `changeset publish`, the build pairs `preconstruct build` with `node ./scripts/combine-umd-builds.js`, and the README is maintained in six languages. The root package is MIT licensed and takes `redux` at `^4.0.4`.
Editorial conclusion
Choose redux-saga when your side effects need to be readable and testable as generator steps and you want failures to reach the store as ordinary actions. Pass on it when the generator and runtime machinery costs more than the callback nesting it removes. Before adopting, settle two things: whether your root saga uses takeEvery or takeLatest, because the shipped example allows concurrent fetches with nothing ordering them, and whether your browser target supports ES2015 generators or you must ship regenerator-runtime before redux-saga.
Frequently asked questions
What is a saga in Redux?
A saga is a generator function that owns side effects for a Redux application. redux-saga runs it as a separate thread inside the app, which can be started, paused and cancelled from the main application with normal redux actions.
What are the key differences between Redux Saga and Redux Thunk?
The project's own comparison names three outcomes against redux-thunk: you avoid callback hell, asynchronous flows are easy to test, and your actions stay pure. redux-saga reaches the store as middleware created with createSagaMiddleware and started by sagaMiddleware.run.
how to use redux saga
Install it with npm install redux-saga, create the middleware with createSagaMiddleware, mount it through applyMiddleware inside createStore, then start the root generator with sagaMiddleware.run. Worker sagas yield call and put, imported from redux-saga/effects.
what is yield in redux saga
A worker saga yields effects rather than calling services inline. The shipped example yields call(Api.fetchUser, action.payload.userId) and then put with type USER_FETCH_SUCCEEDED, so the fetch is described to the saga runtime rather than executed in the generator body.
is redux saga deprecated
The repository is not archived. The last push was on 2026-10-25, and [email protected] was published on 2026-07-26 alongside @redux-saga/[email protected] and @redux-saga/[email protected] on the same day. The README carries no deprecation notice.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/redux-saga-redux-saga)Star history chart for your README
[](https://hysenlabs.com/projects/redux-saga-redux-saga)Was this useful?