Saga middleware for Redux to handle side effects using ES6 generators
npx @tessl/cli install tessl/npm-redux-saga@1.3.0Redux-Saga is a library that makes application side effects (asynchronous things like data fetching and impure things like accessing the browser cache) easier to manage, more efficient to execute, easy to test, and better at handling failures. It uses ES6 generator functions to make asynchronous flows easy to read, write and test, creating a mental model where sagas act as separate threads solely responsible for side effects.
npm install redux-sagaimport createSagaMiddleware from "redux-saga";
import { take, put, call, fork, select } from "redux-saga/effects";For CommonJS:
const createSagaMiddleware = require("redux-saga").default;
const { take, put, call, fork, select } = require("redux-saga/effects");import { createStore, applyMiddleware } from "redux";
import createSagaMiddleware from "redux-saga";
import { take, put, call, fork } from "redux-saga/effects";
// Create saga middleware
const sagaMiddleware = createSagaMiddleware();
// Apply middleware to store
const store = createStore(
reducer,
applyMiddleware(sagaMiddleware)
);
// Example saga
function* fetchUserSaga(action) {
try {
const user = yield call(Api.fetchUser, action.payload.userId);
yield put({ type: 'USER_FETCH_SUCCEEDED', user });
} catch (e) {
yield put({ type: 'USER_FETCH_FAILED', message: e.message });
}
}
function* watchFetchUser() {
yield take('USER_FETCH_REQUESTED', fetchUserSaga);
}
// Start the saga
sagaMiddleware.run(watchFetchUser);Redux-Saga is built around several key concepts:
Core functionality for creating and configuring the Redux middleware that connects sagas to your Redux store.
interface SagaMiddlewareOptions<C extends object = {}> {
context?: C;
sagaMonitor?: SagaMonitor;
onError?(error: Error, errorInfo: ErrorInfo): void;
effectMiddlewares?: EffectMiddleware[];
channel?: MulticastChannel<Action>;
}
interface SagaMiddleware<C extends object = {}> extends Middleware {
run<S extends Saga>(saga: S, ...args: Parameters<S>): Task;
setContext(props: Partial<C>): void;
}
function createSagaMiddleware<C extends object>(
options?: SagaMiddlewareOptions<C>
): SagaMiddleware<C>;Core effects for the most common saga operations: waiting for actions, dispatching actions, calling functions, and managing context.
function take<A extends Action>(pattern?: ActionPattern<A>): TakeEffect;
function put<A extends Action>(action: A): PutEffect<A>;
function call<Fn extends (...args: any[]) => any>(
fn: Fn,
...args: Parameters<Fn>
): CallEffect<SagaReturnType<Fn>>;
function select<Fn extends (state: any, ...args: any[]) => any>(
selector?: Fn,
...args: Tail<Parameters<Fn>>
): SelectEffect;
function getContext(prop: string): GetContextEffect;
function setContext(props: object): SetContextEffect;Effects for managing concurrent execution, forking tasks, and coordinating multiple asynchronous operations.
function fork<Fn extends (...args: any[]) => any>(
fn: Fn,
...args: Parameters<Fn>
): ForkEffect<SagaReturnType<Fn>>;
function spawn<Fn extends (...args: any[]) => any>(
fn: Fn,
...args: Parameters<Fn>
): ForkEffect<SagaReturnType<Fn>>;
function join(task: Task): JoinEffect;
function cancel(task: Task): CancelEffect;
function all<T>(effects: T[]): AllEffect<T>;
function race<T>(effects: { [key: string]: T }): RaceEffect<T>;High-level helper effects built on top of basic effects for common patterns like handling every action, latest action, or throttling.
function takeEvery<P extends ActionPattern>(
pattern: P,
worker: (action: ActionMatchingPattern<P>) => any
): ForkEffect<never>;
function takeLatest<P extends ActionPattern>(
pattern: P,
worker: (action: ActionMatchingPattern<P>) => any
): ForkEffect<never>;
function takeLeading<P extends ActionPattern>(
pattern: P,
worker: (action: ActionMatchingPattern<P>) => any
): ForkEffect<never>;
function throttle<P extends ActionPattern>(
ms: number,
pattern: P,
worker: (action: ActionMatchingPattern<P>) => any
): ForkEffect<never>;
function debounce<P extends ActionPattern>(
ms: number,
pattern: P,
worker: (action: ActionMatchingPattern<P>) => any
): ForkEffect<never>;Channel system for communication between sagas and external event sources, enabling integration with WebSockets, DOM events, and other async data sources.
function channel<T extends NotUndefined>(buffer?: Buffer<T>): Channel<T>;
function eventChannel<T extends NotUndefined>(
subscribe: Subscribe<T>,
buffer?: Buffer<T>
): EventChannel<T>;
function multicastChannel<T extends NotUndefined>(): MulticastChannel<T>;
function actionChannel(
pattern: ActionPattern,
buffer?: Buffer<Action>
): ActionChannelEffect;Tools for testing sagas in isolation, including cloneable generators and mock tasks.
function cloneableGenerator<S extends Saga>(
saga: S
): (...args: Parameters<S>) => SagaIteratorClone;
function createMockTask(): MockTask;General utility functions for working with Redux-Saga effects and values.
function detach<T>(forkEffect: ForkEffect<T>): ForkEffect<T>;
function isEnd(value: any): value is END;Additional utility packages providing type checking, symbols, deferred promises, and monitoring capabilities.
// Type checking utilities
import * as is from "@redux-saga/is";
// Symbol constants
import { CANCEL, SAGA_ACTION, TASK } from "@redux-saga/symbols";
// Deferred promises
import deferred from "@redux-saga/deferred";
// Promise-based delay
import delay from "@redux-saga/delay-p";
// Saga monitoring
import createSagaMonitor from "@redux-saga/simple-saga-monitor";interface Action<T extends string = string> {
type: T;
}
interface AnyAction extends Action {
[extraProps: string]: any;
}
interface Task {
isRunning(): boolean;
result<T = any>(): T | undefined;
error(): any | undefined;
toPromise<T = any>(): Promise<T>;
cancel(): void;
setContext(props: object): void;
}
type ActionPattern<A extends Action = Action> =
| string
| string[]
| ((action: A) => boolean)
| A['type'][];
type NotUndefined = {} | null;
interface Saga<Args extends any[] = any[], Return = any> {
(...args: Args): SagaIterator<Return>;
}
interface SagaIterator<T = any> extends Iterator<any, T, any> {
readonly name: string;
}
interface Buffer<T> {
isEmpty(): boolean;
put(message: T): void;
take(): T | undefined;
}
interface TakeableChannel<T> {
take(cb: (message: T | END) => void): void;
}
interface PuttableChannel<T> {
put(message: T | END): void;
}
interface FlushableChannel<T> {
flush(cb: (items: T[] | END) => void): void;
}
interface Channel<T> extends TakeableChannel<T>, PuttableChannel<T>, FlushableChannel<T> {
close(): void;
}
interface MulticastChannel<T> extends Channel<T> {
take(cb: (message: T | END) => void, matcher?: Predicate<T>): void;
}
type Predicate<T> = (value: T) => boolean;
type END = { type: 'END' };
type EndType = END;type TakeEffect = SimpleEffect<'TAKE', TakeEffectDescriptor>;
type PutEffect<A extends Action = AnyAction> = SimpleEffect<'PUT', PutEffectDescriptor<A>>;
type CallEffect<RT = any> = SimpleEffect<'CALL', CallEffectDescriptor<RT>>;
type ForkEffect<RT = any> = SimpleEffect<'FORK', ForkEffectDescriptor<RT>>;
type SelectEffect = SimpleEffect<'SELECT', SelectEffectDescriptor>;
type GetContextEffect = SimpleEffect<'GET_CONTEXT', GetContextEffectDescriptor>;
type SetContextEffect = SimpleEffect<'SET_CONTEXT', SetContextEffectDescriptor>;
type AllEffect<T> = CombinatorEffect<'ALL', T>;
type RaceEffect<T> = CombinatorEffect<'RACE', T>;
interface SimpleEffect<T, P> {
type: T;
payload: P;
}
interface CombinatorEffect<T, P> {
type: T;
payload: P;
}
type ActionMatchingPattern<P> = P extends string
? Action<P>
: P extends (action: infer A) => boolean
? A extends Action ? A : Action
: Action;
type SagaReturnType<S extends Function> = S extends (...args: any[]) => SagaIterator<infer RT>
? RT
: S extends (...args: any[]) => Promise<infer RT>
? RT
: S extends (...args: any[]) => infer RT
? RT
: never;
type Tail<L extends any[]> = L extends [any, ...infer T] ? T : never;/** Symbol used for cancellation */
const CANCEL: string;
/** Special action type that terminates sagas */
const END: EndType;
/** Effect type constants */
const effectTypes: {
TAKE: 'TAKE';
PUT: 'PUT';
ALL: 'ALL';
RACE: 'RACE';
CALL: 'CALL';
CPS: 'CPS';
FORK: 'FORK';
JOIN: 'JOIN';
CANCEL: 'CANCEL';
SELECT: 'SELECT';
ACTION_CHANNEL: 'ACTION_CHANNEL';
CANCELLED: 'CANCELLED';
FLUSH: 'FLUSH';
GET_CONTEXT: 'GET_CONTEXT';
SET_CONTEXT: 'SET_CONTEXT';
};