redux-saga

A Redux middleware that models complex async side effects as testable, cancellable sagas using ES6 generators.

Library
npm
v1.5.1
22,422stars
MIT License

Repository Health

Pre-computed score based on development activity, maintenance, community, maturity, and trend momentum.How we score it →
81/100Excellent
Development Activity76
Maintenance72
Community76
Maturity60
Momentum40

Technical Analysis

AI-assessed by reading the actual repository — architecture, code quality, innovation, and documentation.How we score it →
80/100Excellent
Architecture88
Code Quality85
Innovation82
Learning Curve65

redux-saga is a middleware for Redux that gives asynchronous side effects — data fetching, browser storage access, and other impure operations — a dedicated, testable home outside of your action creators and components. Instead of scattering promises and callbacks through the app, side effects are described as plain JavaScript objects (“effects”) that a generator function yields, and the middleware’s runtime interprets and executes them.

The mental model is that a saga behaves like a separate thread solely responsible for side effects: it can be started, paused, and cancelled using ordinary Redux actions, it can read the full application state, and it can dispatch new actions back into the store. Because sagas are built on ES6 generators rather than promises or async/await, redux-saga gets capabilities that are hard to express with Promise-based approaches alone — most notably cooperative cancellation and declarative effect composition that can be unit-tested by simply asserting on the yielded values, with no mocking of fetch or timers required.

The project is a pnpm-based monorepo split into small, focused packages (@redux-saga/core, @redux-saga/is, @redux-saga/symbols, @redux-saga/deferred, @redux-saga/delay-p, @redux-saga/types, @redux-saga/testing-utils) with the redux-saga npm package itself acting as a thin convenience wrapper that re-exports @redux-saga/core. It has powered production Redux applications for a decade and remains a common answer to “how do I handle non-trivial async flows in Redux” for teams that outgrow thunks.

What You Get

  • A sagaMiddleware factory that mounts onto applyMiddleware and a .run() method to start root sagas against the live store
  • A declarative effects API (call, put, take, takeEvery, takeLatest, fork, spawn, race, all, select, cancel, cancelled) for expressing control flow without hand-rolled promise chains
  • Cooperative task cancellation that propagates down through forked child tasks and cleans up via generator finally blocks
  • Channels (stdChannel, eventChannel, buffered channels) for decoupling saga producers from consumers and modeling event streams
  • A sagaMonitor hook for wiring in devtools/logging that observes every effect triggered, resolved, rejected, or cancelled
  • First-class TypeScript typings (@redux-saga/types) and dedicated testing utilities (@redux-saga/testing-utils) for asserting on yielded effects without mocking network calls

Common Use Cases

  • Fetching data in response to a dispatched action and dispatching success/failure actions back, with built-in support for cancelling an in-flight request when a newer one arrives (takeLatest)
  • Coordinating multi-step async workflows — chained API calls, retries with backoff, polling — as sequential, readable generator code instead of nested promise callbacks
  • Debouncing or throttling user-triggered actions before they reach the network
  • Bridging WebSocket or other push-based event sources into the Redux action stream via eventChannel
  • Testing complex async business logic by asserting on the effects a saga yields, with no need to stub fetch, timers, or the Redux store itself

Under The Hood

Architecture The runtime lives almost entirely in @redux-saga/core, centered on packages/core/src/internal/proc.js, which implements a recursive generator-driving function (proc/next/digestEffect) that steps a saga’s generator forward, dispatches each yielded effect to effectRunnerMap based on its IO symbol tag, and resumes the generator with the effect’s resolved value or thrown error. Task bookkeeping (newTask.js, forkQueue.js, task-status.js) tracks parent/child relationships between forked sagas so that cancelling a parent task recursively cancels its children, and a scheduler.js semaphore batches synchronous re-entrant calls to avoid stack overflows on tight effect loops. middleware.js is a thin adapter that wires this engine into Redux’s applyMiddleware contract, forwarding dispatched actions into a stdChannel that root sagas can take from. This separation — a Redux-agnostic effect interpreter plus a small Redux adapter — is what let the project later split @redux-saga/core’s helper types (is, symbols, deferred, delay-p) into standalone packages other libraries can depend on independently.

Tech Stack The project is a pnpm workspace monorepo built with @preconstruct/cli (via preconstruct build) to produce multiple entry-point bundles per package (ESM, CJS, and a combined UMD build assembled by a custom combine-umd-builds.js script), and versioned/published via Changesets. Source is plain JavaScript with generator syntax, transpiled through Babel (@babel/preset-env, babel-plugin-annotate-pure-calls for tree-shaking hints) rather than TypeScript, while a separate @redux-saga/types package and types/*.test.ts files under packages/core provide and validate the public TypeScript surface. Tests run on Jest with babel-jest; linting uses a flat ESLint config (eslint.config.js) with Husky + lint-staged enforcing checks pre-commit. Runnable examples (counter, shopping cart, real-world) are built with Webpack 4 against a plain react/react-redux stack, illustrating integration rather than shipping as part of the published package.

Code Quality Core behavior is covered by 35+ Jest test files under packages/core/__tests__ (channel semantics, cancellation, the interpreter’s task/fork-queue behavior, scheduler ordering, saga helpers, monitoring hooks), plus a dedicated types/*.test.ts suite that exercises the TypeScript definitions for effects, channels, runSaga, and middleware — an explicit signal the maintainers treat type-level correctness as testable surface, not just documentation. Error handling in the core interpreter is deliberate rather than incidental: sagaError.js tracks which effect crashed to produce actionable stack traces, and proc.js distinguishes cancellation, termination, and thrown-error resumption paths explicitly rather than collapsing them into generic try/catch. Naming is consistent with Redux community conventions (effectRunnerMap, io-helpers, sagaHelpers), and CI plus Husky pre-commit hooks enforce lint and formatting before merge.

What Makes It Unique redux-saga’s defining technical choice is building on ES6 generators instead of async/await or plain Promises, specifically to get cooperative cancellation and effect introspection that Promise-based control flow cannot express cleanly — a yielded effect is just a plain object the runtime can inspect, log, replay, or cancel before it ever executes, which is what makes the library’s assert-on-yielded-effects testing style possible without mocking I/O. The channel abstraction generalizes this beyond simple request/response side effects into a general pub/sub primitive for bridging any external event source into a saga’s take calls, and the fork-queue/task-cancellation model gives structured-concurrency-like guarantees (a cancelled parent reliably cancels all of its descendants) that predates similar guarantees becoming mainstream in other async ecosystems.

Join founders buildingwith open source

Opinionated takes, migration guides, cost-saving tips, and insights from the open source ecosystem.

Subscribe on Substack
Join 750+ subscribers

Search