Glossary
Actionβ
An Action is essentially a well-known Redux action or a simple function wrapper. Methods from the "models" like Zustand can also be wrapped as actions. These actions serve as potential triggers for Sagas. We wait for an action to occur and then call a saga. For example:
import {createAction} from '@reduxjs/toolkit';
// create Redux action
const reduxAction = createAction<string>('actions/changeNewTodo');
// watcher saga
const watcher = createSaga(async function() {
// listen for action to happen and trigger `testSaga`
this.takeLatest(reduxAction, testSaga);
});
const testSaga = createSaga(async function() {
// ...
});
import {create} from 'zustand';
import {createAction} from '@promise-saga/plugin-default';
// create an outside action
const action = createAction<void, [string]>();
export const useTodosStore = create()((set, get) => ({
zustandEmptyAction: createAction(), // create an empty action
zustandAction: createAction((text: string) => { // wrap an existing function
set((state) => ({
// ...
}));
}),
// ...
// watcher saga
watcher: createSaga(async function() {
// listen for action to happen and trigger `testSaga`
this.takeLatest(get().zustandEmptyAction, get().testSaga);
}),
testSaga: createSaga(async function(action) {
// ...
}),
}));
See createAction, createAsyncAction for more information.
Pluginβ
An Plugin is an object that aggregates all the Effects you wish to be injected into the this
context of a Saga.
There are now 9 plugins available: default
, angular
, axios
, fetch
, redux
, react
, react-query
, svelte
, and zustand
. Although the react
plugin does not inject any effects, it provides helpers like useSaga
. It is still referred to as an plugin to externalize the dependency.
For example, plugins may look like this:
// fetch plugin
export const plugin = {
higher: {
fetch: fetchFn,
},
};
// redux plugin
export const createPlugin = (channel = defaultChannel) => ({
main: {
getStore,
channel,
},
lower: {
dispatch,
select,
take,
},
higher: {
debounce,
takeEvery,
takeLatest,
takeLeading,
throttle,
},
});
export const plugin = createPlugin();
Effect implementations inside Promise Saga also have access to this
, enabling higher effects to utilize lower effects within the this
context to implement more advanced logic. Lower effects receive main effects in this
.
Additionally, Promise Saga includes several out-of-the-box effects like call and callFn, among others. These are defined at specific levels; for example, they are considered lower effects. Consequently, every plugin's higher effects will have access to lower effects such as call
and callFn
in their this
.
For instructions on setting up sagas with plugins, see the basics section.
Effectβ
An Effect is a special function used to perform Saga logic, accessible through this
inside createSaga
. A basic effect is an asynchronous function. For instance, the implementation of the delay effect is similar to the following example:
export const delay = (ms?: number) => (
new Promise((resolve, reject) => setTimeout(resolve, ms))
);
and can used inside createSaga
like this:
const saga = createSaga(async function() {
await this.delay(500); // simple effect called
console.log('done');
});
See more about effects in the basics section.
EventEmitterβ
EventEmitter emits Actions and thus enables waiting for actions within a Saga.
Typically, it is advisable to use a single EventEmitter object for a project, referred to as defaultChannel. However, it can be customized, allowing you to have different createSaga
instances with various channels operating underneath. For more detailed information, refer to createSaga and createCreateSaga.
Higher Effect Hookβ
A Higher Effect Hook is a higher Effect wrapper, represented as a hook. Plugins for Angular, React, Redux, Svelte and Vue offer the following hooks: useTakeEvery, useTakeLatest, useTakeLeading, useDebounce, and useThrottle. For example:
export function TodosContainer() {
useDebounce(500, Todos.actions.toggleTodo, toggleTodo); // higher effect hook
return <>Todos</>;
}
See more about higher effect hooks in the basics section.
Sagaβ
A Saga is a cancellable asynchronous function that takes arguments and can be cancelled by effects invoked within it. It returns a SagaIterator, which is a cancellable promise.
export type Saga<T = void, P extends any[] = any[]> =
(...args: P) => SagaIterator<T>;
SagaIteratorβ
A SagaIterator is a cancellable promise returned from each Saga and nearly every effect, as almost all of them are asynchronous and cancellable.
export type SagaIterator<T = any> = Promise<T> & {
cancel(): void;
cancelled(): boolean;
};
Treeβ
A Tree is an object used to maintain the parent-child relationships of Sagas. Every saga call generates a new TreeNode at the next level in the Tree, starting from the Root (level = 0).
Level 0 Root
/ \
Level 1: SagaA SagaSpawn
/ \
Level 2: SagaB SagaC
/ \
Level 3: SagaD SagaE
Almost every Effect attaches a child saga to its parent within a Tree, except for spawn - this effect attaches the child saga directly to the Root. If a saga is cancelled, all its children, as defined in the Tree, are also cancelled.
Typically, it is advisable to use a single Tree object for a project - see defaultTree. However, this is not a strict rule; you can have different createSaga
instances with different Trees operating underneath. For more detailed information, refer to createSaga and createCreateSaga.
TreeNodeβ
SagaTreeNode is an object containing:
iter?: SagaIterator
- The promise, which runs instantly. It isundefined
at the root level.saga?: Saga
- The async function that produced the promise. It isundefined
at the root level.parent?: SagaTreeNode
- A link to the parentSagaTreeNode
. It isundefined
at the root level.children: Set<SagaTreeNode>
- Links to childSagaTreeNodes
.level: number
- The level. It is0
at the root level.
Normally, you would want to access the SagaTreeNode
API while handling errors in sagas.