Basic concepts
Sagasβ
Sagas were presumably first described in 1987 as a concept and were further nicely discussed in 2015 as reversible long-lived transactions in databases. Within the React community, Redux Saga has long been the dominant approach, providing great inspiration for handling side effects in applications.
Promise Saga introduces a set of simplified requirements:
Sagas are cancellableβ
We use AbortController under the hood to achieve cancellation. Sagas can call each other, forming a tree structure where they become parents or children to one another. If a Saga is cancelled, all of its child Sagas are also cancelled.
Sagas are cancellable on timeβ
There are instances when you may need to execute the finally
block of a try..finally
structure immediately after a Saga has been errored or cancelled. Promise Saga ensures that this is handled correctly and promptly.
Effectsβ
We provide many built-in functions to control the flow of sagas. Using these functions, you can:
- Call another saga, function, or promise using call, callFn and callPromise.
- Wait for actions to occur and continue saga execution using take, takeLatest, debounce, throttle, and others.
- Introduce delays using the delay effect.
- Perform a race between effects or wait for all sagas to complete using race and all.
See the usage examples below and refer to the API reference for a full list of effects.
import {createSaga} from '...';
export const listenTodoToggles = createSaga(async function() {
// wait for Redux action to happen
await this.take(Todos.actions.toggleTodo);
// ...
});
export const listenTodoToggles = createSaga(async function() {
// apply 500ms debouncing to a `toggleTodo` action to call `toggleTodo` saga
this.debounce(500, Todos.actions.toggleTodo, toggleTodo);
// ...
});
Effects are divided into lower-level and higher-level effects. For example, the higher-level debounce effect is based on a combination of lower-level effects, such as call, delay, race, and take.
Refer to the recipes section for examples.
Pluginsβ
Promise Saga provides a set of plugins, such as those for Redux and Zustand. These plugins can be used to seamlessly integrate Promise Saga with different state management libraries.
import {createCreateSaga} from '@promise-saga/core';
import {plugin} from '@promise-saga/plugin-redux';
// Here we create a `createSaga` wrapper to apply plugins to every sagas created
export const createSaga = createCreateSaga({plugin});
Use mergePlugins
to apply multiple plugins simultaneously:
import {createCreateSaga, mergePlugins} from '@promise-saga/core';
import reduxPlugin from '@promise-saga/plugin-redux';
import fetchPlugin from '@promise-saga/plugin-fetch';
const plugin = mergePlugins(reduxPlugin, fetchPlugin);
export const createSaga = createCreateSaga({plugin});
Most of the plugins introduce new effects within createSaga
.
In-component usageβ
Promise Saga allows you to manually call and cancel sagas within a component. Sagas will be automatically cancelled when the component unmounts:
import {useSaga} from '@promise-saga/core';
export function TodosContainer() {
const listenTogglesFlow = useSaga(listenTodoToggles); // use a saga
// render a checkbox to control saga canceling
return (
<input
type="checkbox"
onChange={listenTogglesFlow.toggle}
checked={listenTogglesFlow.isRunning}
/>
);
}
Higher Effect Hooksβ
Using Redux and Zustand plugins gives you the opportunity to call sagas like this:
import {useDebounce} from '...';
export function TodosContainer() {
useDebounce(500, Todos.actions.toggleTodo, toggleTodo); // higher effect hook
return <Todos />;
}
This method might allow you to skip creating a separate watcher saga within a watcher/worker pattern.