CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-redux-saga

Saga middleware for Redux to handle side effects using ES6 generators

Pending
Overview
Eval results
Files

helper-effects.mddocs/

Helper Effects

High-level helper effects built on top of basic effects for common patterns like handling every action, latest action, throttling, and debouncing.

Capabilities

takeEvery

Spawns a saga on each action dispatched to the Store that matches the pattern. Allows concurrent handling of actions.

/**
 * Spawn saga on each matching action (allows concurrency)
 * @param pattern - Action pattern to watch for
 * @param worker - Saga function to spawn for each action
 * @param args - Additional arguments passed to worker (action is always last)
 * @returns ForkEffect that never completes (runs forever)
 */
function takeEvery<P extends ActionPattern>(
  pattern: P,
  worker: (action: ActionMatchingPattern<P>) => any
): ForkEffect<never>;

function takeEvery<P extends ActionPattern, Fn extends (...args: any[]) => any>(
  pattern: P,
  worker: Fn,
  ...args: HelperWorkerParameters<ActionMatchingPattern<P>, Fn>
): ForkEffect<never>;

/**
 * Watch channel for messages
 * @param channel - Channel to watch
 * @param worker - Function to handle each message
 * @returns ForkEffect
 */
function takeEvery<T>(
  channel: TakeableChannel<T>, 
  worker: (item: T) => any
): ForkEffect<never>;

Usage Examples:

import { takeEvery, call, put } from "redux-saga/effects";

function* fetchUser(action) {
  try {
    const user = yield call(api.fetchUser, action.payload.userId);
    yield put({ type: 'USER_FETCH_SUCCEEDED', user });
  } catch (error) {
    yield put({ type: 'USER_FETCH_FAILED', error: error.message });
  }
}

function* watchUserRequests() {
  // Spawn fetchUser on every USER_FETCH_REQUESTED action
  // Multiple requests can run concurrently
  yield takeEvery('USER_FETCH_REQUESTED', fetchUser);
}

// With additional arguments
function* saveData(apiClient, action) {
  yield call([apiClient, 'save'], action.payload);
}

function* watchSaveRequests() {
  const apiClient = yield getContext('apiClient');
  yield takeEvery('SAVE_REQUESTED', saveData, apiClient);
}

takeLatest

Spawns a saga on each action, but automatically cancels any previous saga if it's still running. Only the latest saga runs.

/**
 * Spawn saga on each action, cancel previous if running (latest wins)
 * @param pattern - Action pattern to watch for
 * @param worker - Saga function to spawn
 * @param args - Additional arguments passed to worker
 * @returns ForkEffect that never completes
 */
function takeLatest<P extends ActionPattern>(
  pattern: P,
  worker: (action: ActionMatchingPattern<P>) => any
): ForkEffect<never>;

function takeLatest<P extends ActionPattern, Fn extends (...args: any[]) => any>(
  pattern: P,
  worker: Fn,
  ...args: HelperWorkerParameters<ActionMatchingPattern<P>, Fn>
): ForkEffect<never>;

function takeLatest<T>(
  channel: TakeableChannel<T>, 
  worker: (item: T) => any
): ForkEffect<never>;

Usage Examples:

import { takeLatest, call, put } from "redux-saga/effects";

function* searchUsers(action) {
  try {
    const results = yield call(api.searchUsers, action.payload.query);
    yield put({ type: 'SEARCH_SUCCEEDED', results });
  } catch (error) {
    yield put({ type: 'SEARCH_FAILED', error: error.message });
  }
}

function* watchSearchRequests() {
  // Cancel previous search if user types quickly
  // Only the latest search request will complete
  yield takeLatest('SEARCH_REQUESTED', searchUsers);
}

takeLeading

Spawns a saga on an action, then blocks until that saga completes before accepting new actions of the same pattern.

/**
 * Spawn saga and block until completion before accepting new actions
 * @param pattern - Action pattern to watch for
 * @param worker - Saga function to spawn
 * @param args - Additional arguments passed to worker
 * @returns ForkEffect that never completes
 */
function takeLeading<P extends ActionPattern>(
  pattern: P,
  worker: (action: ActionMatchingPattern<P>) => any
): ForkEffect<never>;

function takeLeading<P extends ActionPattern, Fn extends (...args: any[]) => any>(
  pattern: P,
  worker: Fn,
  ...args: HelperWorkerParameters<ActionMatchingPattern<P>, Fn>
): ForkEffect<never>;

function takeLeading<T>(
  channel: TakeableChannel<T>, 
  worker: (item: T) => any
): ForkEffect<never>;

Usage Examples:

import { takeLeading, call, put } from "redux-saga/effects";

function* processPayment(action) {
  try {
    const result = yield call(api.processPayment, action.payload);
    yield put({ type: 'PAYMENT_SUCCEEDED', result });
  } catch (error) {
    yield put({ type: 'PAYMENT_FAILED', error: error.message });
  }
}

function* watchPaymentRequests() {
  // Ignore additional payment requests while one is processing
  // Prevents double payments from rapid clicking
  yield takeLeading('PROCESS_PAYMENT', processPayment);
}

throttle

Spawns a saga on an action, then ignores subsequent actions for a specified time period while the saga is processing.

/**
 * Spawn saga and ignore actions for specified time period
 * @param ms - Milliseconds to throttle (ignore subsequent actions)
 * @param pattern - Action pattern to watch for
 * @param worker - Saga function to spawn
 * @param args - Additional arguments passed to worker
 * @returns ForkEffect that never completes
 */
function throttle<P extends ActionPattern>(
  ms: number,
  pattern: P,
  worker: (action: ActionMatchingPattern<P>) => any
): ForkEffect<never>;

function throttle<P extends ActionPattern, Fn extends (...args: any[]) => any>(
  ms: number,
  pattern: P,
  worker: Fn,
  ...args: HelperWorkerParameters<ActionMatchingPattern<P>, Fn>
): ForkEffect<never>;

function throttle<T>(
  ms: number,
  channel: TakeableChannel<T>, 
  worker: (item: T) => any
): ForkEffect<never>;

Usage Examples:

import { throttle, call, put } from "redux-saga/effects";

function* saveUserPreferences(action) {
  try {
    yield call(api.savePreferences, action.payload);
    yield put({ type: 'PREFERENCES_SAVED' });
  } catch (error) {
    yield put({ type: 'PREFERENCES_SAVE_FAILED', error: error.message });
  }
}

function* watchPreferenceChanges() {
  // Save preferences but throttle to max once per 2 seconds
  // Prevents excessive API calls when user changes multiple settings
  yield throttle(2000, 'PREFERENCES_CHANGED', saveUserPreferences);
}

debounce

Spawns a saga only after actions stop being dispatched for a specified time period. Resets the timer on each new action.

/**
 * Spawn saga after actions stop for specified time period
 * @param ms - Milliseconds to wait after last action
 * @param pattern - Action pattern to watch for
 * @param worker - Saga function to spawn
 * @param args - Additional arguments passed to worker
 * @returns ForkEffect that never completes
 */
function debounce<P extends ActionPattern>(
  ms: number,
  pattern: P,
  worker: (action: ActionMatchingPattern<P>) => any
): ForkEffect<never>;

function debounce<P extends ActionPattern, Fn extends (...args: any[]) => any>(
  ms: number,
  pattern: P,
  worker: Fn,
  ...args: HelperWorkerParameters<ActionMatchingPattern<P>, Fn>
): ForkEffect<never>;

function debounce<T>(
  ms: number,
  channel: TakeableChannel<T>, 
  worker: (item: T) => any
): ForkEffect<never>;

Usage Examples:

import { debounce, call, put } from "redux-saga/effects";

function* performAutoSave(action) {
  try {
    yield call(api.autoSave, action.payload.document);
    yield put({ type: 'AUTO_SAVE_SUCCEEDED' });
  } catch (error) {
    yield put({ type: 'AUTO_SAVE_FAILED', error: error.message });
  }
}

function* watchDocumentChanges() {
  // Auto-save document 1 second after user stops typing
  yield debounce(1000, 'DOCUMENT_CHANGED', performAutoSave);
}

function* searchSuggestions(action) {
  const suggestions = yield call(api.getSearchSuggestions, action.payload.query);
  yield put({ type: 'SUGGESTIONS_LOADED', suggestions });
}

function* watchSearchInput() {
  // Fetch suggestions 300ms after user stops typing
  yield debounce(300, 'SEARCH_INPUT_CHANGED', searchSuggestions);
}

retry

Creates an effect that attempts to call a function multiple times with delays between attempts on failure.

/**
 * Retry function call with delay between attempts on failure
 * @param maxTries - Maximum number of attempts
 * @param delayLength - Milliseconds to wait between attempts
 * @param fn - Function to retry
 * @param args - Arguments to pass to function
 * @returns CallEffect that resolves with successful result or throws final error
 */
function retry<Fn extends (...args: any[]) => any>(
  maxTries: number,
  delayLength: number,
  fn: Fn,
  ...args: Parameters<Fn>
): CallEffect<SagaReturnType<Fn>>;

Usage Examples:

import { retry, call, put } from "redux-saga/effects";

function* fetchUserWithRetry(action) {
  try {
    // Retry up to 3 times with 2 second delays
    const user = yield retry(3, 2000, api.fetchUser, action.payload.userId);
    yield put({ type: 'USER_FETCH_SUCCEEDED', user });
  } catch (error) {
    // All retries failed
    yield put({ type: 'USER_FETCH_FAILED', error: error.message });
  }
}

function* uploadFileWithRetry(action) {
  try {
    // Retry upload 5 times with 5 second delays
    const result = yield retry(
      5, 
      5000, 
      api.uploadFile, 
      action.payload.file,
      action.payload.options
    );
    yield put({ type: 'UPLOAD_SUCCEEDED', result });
  } catch (error) {
    yield put({ type: 'UPLOAD_FAILED', error: error.message });
  }
}

Implementation Notes

Helper effects are high-level APIs built using lower-level effects:

  • takeEvery = fork(function*() { while(true) { const action = yield take(pattern); yield fork(worker, action); } })
  • takeLatest = Like takeEvery but with cancellation of previous task
  • takeLeading = Like takeEvery but with blocking until completion
  • throttle = takeEvery with timing-based action filtering
  • debounce = takeEvery with timer reset on each action

These helpers provide common async patterns while maintaining full saga capabilities like cancellation and error handling.

Install with Tessl CLI

npx tessl i tessl/npm-redux-saga

docs

basic-effects.md

channels.md

concurrency-effects.md

helper-effects.md

index.md

middleware.md

testing.md

utilities.md

tile.json