@redux-saga/core
Version:
Saga middleware for Redux to handle Side Effects
1,326 lines (1,250 loc) • 52.1 kB
TypeScript
// TypeScript Version: 4.2
import {
ActionPattern,
Effect,
Buffer,
CombinatorEffect,
CombinatorEffectDescriptor,
SimpleEffect,
END,
Pattern,
Task,
StrictEffect,
ActionMatchingPattern,
SagaIterator,
} from '@redux-saga/types'
import { FlushableChannel, PuttableChannel, TakeableChannel, Action, AnyAction } from './index'
export { ActionPattern, Effect, Pattern, SimpleEffect, StrictEffect }
export 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'
}
/**
* Creates an Effect description that instructs the middleware to wait for a
* specified action on the Store. The Generator is suspended until an action
* that matches `pattern` is dispatched.
*
* The result of `yield take(pattern)` is an action object being dispatched.
*
* `pattern` is interpreted using the following rules:
*
* - If `take` is called with no arguments or `'*'` all dispatched actions are
* matched (e.g. `take()` will match all actions)
*
* - If it is a function, the action is matched if `pattern(action)` is true
* (e.g. `take(action => action.entities)` will match all actions having a
* (truthy) `entities` field.)
* > Note: if the pattern function has `toString` defined on it, `action.type`
* > will be tested against `pattern.toString()` instead. This is useful if
* > you're using an action creator library like redux-act or redux-actions.
*
* - If it is a String, the action is matched if `action.type === pattern` (e.g.
* `take(INCREMENT_ASYNC)`
*
* - If it is an array, each item in the array is matched with aforementioned
* rules, so the mixed array of strings and function predicates is supported.
* The most common use case is an array of strings though, so that
* `action.type` is matched against all items in the array (e.g.
* `take([INCREMENT, DECREMENT])` and that would match either actions of type
* `INCREMENT` or `DECREMENT`).
*
* The middleware provides a special action `END`. If you dispatch the END
* action, then all Sagas blocked on a take Effect will be terminated regardless
* of the specified pattern. If the terminated Saga has still some forked tasks
* which are still running, it will wait for all the child tasks to terminate
* before terminating the Task.
*/
export function take(pattern?: ActionPattern): TakeEffect
export function take<A extends Action>(pattern?: ActionPattern<A>): TakeEffect
/**
* Same as `take(pattern)` but does not automatically terminate the Saga on an
* `END` action. Instead all Sagas blocked on a take Effect will get the `END`
* object.
*
* #### Notes
*
* `takeMaybe` got its name from the FP analogy - it's like instead of having a
* return type of `ACTION` (with automatic handling) we can have a type of
* `Maybe(ACTION)` so we can handle both cases:
*
* - case when there is a `Just(ACTION)` (we have an action)
* - the case of `NOTHING` (channel was closed*). i.e. we need some way to map
* over `END`
*
* internally all `dispatch`ed actions are going through the `stdChannel` which
* is getting closed when `dispatch(END)` happens
*/
export function takeMaybe(pattern?: ActionPattern): TakeEffect
export function takeMaybe<A extends Action>(pattern?: ActionPattern<A>): TakeEffect
export type TakeEffect = SimpleEffect<'TAKE', TakeEffectDescriptor>
export interface TakeEffectDescriptor {
pattern: ActionPattern
maybe?: boolean
}
/**
* Creates an Effect description that instructs the middleware to wait for a
* specified message from the provided Channel. If the channel is already
* closed, then the Generator will immediately terminate following the same
* process described above for `take(pattern)`.
*/
export function take<T>(channel: TakeableChannel<T>, multicastPattern?: Pattern<T>): ChannelTakeEffect<T>
/**
* Same as `take(channel)` but does not automatically terminate the Saga on an
* `END` action. Instead all Sagas blocked on a take Effect will get the `END`
* object.
*/
export function takeMaybe<T>(channel: TakeableChannel<T>, multicastPattern?: Pattern<T>): ChannelTakeEffect<T>
export type ChannelTakeEffect<T> = SimpleEffect<'TAKE', ChannelTakeEffectDescriptor<T>>
export interface ChannelTakeEffectDescriptor<T> {
channel: TakeableChannel<T>
pattern?: Pattern<T>
maybe?: boolean
}
/**
* Spawns a `saga` on each action dispatched to the Store that matches
* `pattern`.
*
* #### Example
*
* In the following example, we create a basic task `fetchUser`. We use
* `takeEvery` to start a new `fetchUser` task on each dispatched
* `USER_REQUESTED` action:
*
* import { takeEvery } from `redux-saga/effects`
*
* function* fetchUser(action) {
* ...
* }
*
* function* watchFetchUser() {
* yield takeEvery('USER_REQUESTED', fetchUser)
* }
*
* #### Notes
*
* `takeEvery` is a high-level API built using `take` and `fork`. Here is how
* the helper could be implemented using the low-level Effects
*
* const takeEvery = (patternOrChannel, saga, ...args) => fork(function*() {
* while (true) {
* const action = yield take(patternOrChannel)
* yield fork(saga, ...args.concat(action))
* }
* })
*
* `takeEvery` allows concurrent actions to be handled. In the example above,
* when a `USER_REQUESTED` action is dispatched, a new `fetchUser` task is
* started even if a previous `fetchUser` is still pending (for example, the
* user clicks on a `Load User` button 2 consecutive times at a rapid rate, the
* 2nd click will dispatch a `USER_REQUESTED` action while the `fetchUser` fired
* on the first one hasn't yet terminated)
*
* `takeEvery` doesn't handle out of order responses from tasks. There is no
* guarantee that the tasks will terminate in the same order they were started.
* To handle out of order responses, you may consider `takeLatest` below.
*
* @param pattern for more information see docs for `take(pattern)`
* @param saga a Generator function
* @param args arguments to be passed to the started task. `takeEvery` will add
* the incoming action to the argument list (i.e. the action will be the last
* argument provided to `saga`)
*/
export function takeEvery<P extends ActionPattern>(
pattern: P,
worker: (action: ActionMatchingPattern<P>) => any,
): ForkEffect<never>
export function takeEvery<P extends ActionPattern, Fn extends (...args: any[]) => any>(
pattern: P,
worker: Fn,
...args: HelperWorkerParameters<ActionMatchingPattern<P>, Fn>
): ForkEffect<never>
export function takeEvery<A extends Action>(pattern: ActionPattern<A>, worker: (action: A) => any): ForkEffect<never>
export function takeEvery<A extends Action, Fn extends (...args: any[]) => any>(
pattern: ActionPattern<A>,
worker: Fn,
...args: HelperWorkerParameters<A, Fn>
): ForkEffect<never>
/**
* You can also pass in a channel as argument and the behaviour is the same as
* `takeEvery(pattern, saga, ...args)`.
*/
export function takeEvery<T>(channel: TakeableChannel<T>, worker: (item: T) => any): ForkEffect<never>
export function takeEvery<T, Fn extends (...args: any[]) => any>(
channel: TakeableChannel<T>,
worker: Fn,
...args: HelperWorkerParameters<T, Fn>
): ForkEffect<never>
/**
* Spawns a `saga` on each action dispatched to the Store that matches
* `pattern`. And automatically cancels any previous `saga` task started
* previously if it's still running.
*
* Each time an action is dispatched to the store. And if this action matches
* `pattern`, `takeLatest` starts a new `saga` task in the background. If a
* `saga` task was started previously (on the last action dispatched before the
* actual action), and if this task is still running, the task will be
* cancelled.
*
* #### Example
*
* In the following example, we create a basic task `fetchUser`. We use
* `takeLatest` to start a new `fetchUser` task on each dispatched
* `USER_REQUESTED` action. Since `takeLatest` cancels any pending task started
* previously, we ensure that if a user triggers multiple consecutive
* `USER_REQUESTED` actions rapidly, we'll only conclude with the latest action
*
* import { takeLatest } from `redux-saga/effects`
*
* function* fetchUser(action) {
* ...
* }
*
* function* watchLastFetchUser() {
* yield takeLatest('USER_REQUESTED', fetchUser)
* }
*
* #### Notes
*
* `takeLatest` is a high-level API built using `take` and `fork`. Here is how
* the helper could be implemented using the low-level Effects
*
* const takeLatest = (patternOrChannel, saga, ...args) => fork(function*() {
* let lastTask
* while (true) {
* const action = yield take(patternOrChannel)
* if (lastTask) {
* yield cancel(lastTask) // cancel is no-op if the task has already terminated
* }
* lastTask = yield fork(saga, ...args.concat(action))
* }
* })
*
* @param pattern for more information see docs for [`take(pattern)`](#takepattern)
* @param saga a Generator function
* @param args arguments to be passed to the started task. `takeLatest` will add
* the incoming action to the argument list (i.e. the action will be the last
* argument provided to `saga`)
*/
export function takeLatest<P extends ActionPattern>(
pattern: P,
worker: (action: ActionMatchingPattern<P>) => any,
): ForkEffect<never>
export function takeLatest<P extends ActionPattern, Fn extends (...args: any[]) => any>(
pattern: P,
worker: Fn,
...args: HelperWorkerParameters<ActionMatchingPattern<P>, Fn>
): ForkEffect<never>
export function takeLatest<A extends Action>(pattern: ActionPattern<A>, worker: (action: A) => any): ForkEffect<never>
export function takeLatest<A extends Action, Fn extends (...args: any[]) => any>(
pattern: ActionPattern<A>,
worker: Fn,
...args: HelperWorkerParameters<A, Fn>
): ForkEffect<never>
/**
* You can also pass in a channel as argument and the behaviour is the same as
* `takeLatest(pattern, saga, ...args)`.
*/
export function takeLatest<T>(channel: TakeableChannel<T>, worker: (item: T) => any): ForkEffect<never>
export function takeLatest<T, Fn extends (...args: any[]) => any>(
channel: TakeableChannel<T>,
worker: Fn,
...args: HelperWorkerParameters<T, Fn>
): ForkEffect<never>
/**
* Spawns a `saga` on each action dispatched to the Store that matches
* `pattern`. After spawning a task once, it blocks until spawned saga completes
* and then starts to listen for a `pattern` again.
*
* In short, `takeLeading` is listening for the actions when it doesn't run a
* saga.
*
* #### Example
*
* In the following example, we create a basic task `fetchUser`. We use
* `takeLeading` to start a new `fetchUser` task on each dispatched
* `USER_REQUESTED` action. Since `takeLeading` ignores any new coming task
* after it's started, we ensure that if a user triggers multiple consecutive
* `USER_REQUESTED` actions rapidly, we'll only keep on running with the leading
* action
*
* import { takeLeading } from `redux-saga/effects`
*
* function* fetchUser(action) {
* ...
* }
*
* function* watchLastFetchUser() {
* yield takeLeading('USER_REQUESTED', fetchUser)
* }
*
* #### Notes
*
* `takeLeading` is a high-level API built using `take` and `call`. Here is how
* the helper could be implemented using the low-level Effects
*
* const takeLeading = (patternOrChannel, saga, ...args) => fork(function*() {
* while (true) {
* const action = yield take(patternOrChannel);
* yield call(saga, ...args.concat(action));
* }
* })
*
* @param pattern for more information see docs for [`take(pattern)`](#takepattern)
* @param saga a Generator function
* @param args arguments to be passed to the started task. `takeLeading` will
* add the incoming action to the argument list (i.e. the action will be the
* last argument provided to `saga`)
*/
export function takeLeading<P extends ActionPattern>(
pattern: P,
worker: (action: ActionMatchingPattern<P>) => any,
): ForkEffect<never>
export function takeLeading<P extends ActionPattern, Fn extends (...args: any[]) => any>(
pattern: P,
worker: Fn,
...args: HelperWorkerParameters<ActionMatchingPattern<P>, Fn>
): ForkEffect<never>
export function takeLeading<A extends Action>(pattern: ActionPattern<A>, worker: (action: A) => any): ForkEffect<never>
export function takeLeading<A extends Action, Fn extends (...args: any[]) => any>(
pattern: ActionPattern<A>,
worker: Fn,
...args: HelperWorkerParameters<A, Fn>
): ForkEffect<never>
/**
* You can also pass in a channel as argument and the behaviour is the same as
* `takeLeading(pattern, saga, ...args)`.
*/
export function takeLeading<T>(channel: TakeableChannel<T>, worker: (item: T) => any): ForkEffect<never>
export function takeLeading<T, Fn extends (...args: any[]) => any>(
channel: TakeableChannel<T>,
worker: Fn,
...args: HelperWorkerParameters<T, Fn>
): ForkEffect<never>
export type HelperWorkerParameters<T, Fn extends (...args: any[]) => any> = Last<Parameters<Fn>> extends T
? AllButLast<Parameters<Fn>>
: Parameters<Fn>
interface ThunkDispatch<State, ExtraThunkArg, BasicAction extends Action> {
<ReturnType>(thunkAction: ThunkAction<ReturnType, State, ExtraThunkArg, BasicAction>): ReturnType
<Action extends BasicAction>(action: Action): Action
<ReturnType, Action extends BasicAction>(
action: Action | ThunkAction<ReturnType, State, ExtraThunkArg, BasicAction>,
): Action | ReturnType
}
export type ThunkAction<ReturnType, State, ExtraThunkArg, BasicAction extends Action> = (
dispatch: ThunkDispatch<State, ExtraThunkArg, BasicAction>,
getState: () => State,
extraArgument: ExtraThunkArg,
) => ReturnType
/**
* Creates an Effect description that instructs the middleware to dispatch an
* action to the Store. This effect is non-blocking, any errors that are
* thrown downstream (e.g. in a reducer) will bubble back into the saga.
*
* @param action [see Redux `dispatch` documentation for complete info](https://redux.js.org/api/store#dispatchaction)
*/
export function put<A extends Action>(action: A): PutEffect<A>
export function put<ReturnType = any, State = any, ExtraThunkArg = any, BasicAction extends Action = Action>(
action: ThunkAction<ReturnType, State, ExtraThunkArg, BasicAction>,
): PutEffect<BasicAction>
/**
* Just like `put` but the effect is blocking (if promise is returned from
* `dispatch` it will wait for its resolution) and will bubble up errors from
* downstream.
*
* @param action [see Redux `dispatch` documentation for complete info](https://redux.js.org/api/store#dispatchaction)
*/
export function putResolve<A extends Action>(action: A): PutEffect<A>
export function putResolve<ReturnType = any, State = any, ExtraThunkArg = any, BasicAction extends Action = Action>(
action: ThunkAction<ReturnType, State, ExtraThunkArg, BasicAction>,
): PutEffect<BasicAction>
export type PutEffect<A extends Action = AnyAction> = SimpleEffect<'PUT', PutEffectDescriptor<A>>
export interface PutEffectDescriptor<A extends Action> {
action: A
channel: null
resolve?: boolean
}
/**
* Creates an Effect description that instructs the middleware to put an action
* into the provided channel.
*
* This effect is blocking if the put is *not* buffered but immediately consumed
* by takers. If an error is thrown in any of these takers it will bubble back
* into the saga.
*
* @param channel a `Channel` Object.
* @param action [see Redux `dispatch` documentation for complete info](https://redux.js.org/api/store#dispatchaction)
*/
export function put<T>(channel: PuttableChannel<T>, action: T | END): ChannelPutEffect<T>
export type ChannelPutEffect<T> = SimpleEffect<'PUT', ChannelPutEffectDescriptor<T>>
export interface ChannelPutEffectDescriptor<T> {
action: T
channel: PuttableChannel<T>
}
/**
* Creates an Effect description that instructs the middleware to call the
* function `fn` with `args` as arguments.
*
* #### Notes
*
* `fn` can be either a *normal* or a Generator function.
*
* The middleware invokes the function and examines its result.
*
* If the result is an Iterator object, the middleware will run that Generator
* function, just like it did with the startup Generators (passed to the
* middleware on startup). The parent Generator will be suspended until the
* child Generator terminates normally, in which case the parent Generator is
* resumed with the value returned by the child Generator. Or until the child
* aborts with some error, in which case an error will be thrown inside the
* parent Generator.
*
* If `fn` is a normal function and returns a Promise, the middleware will
* suspend the Generator until the Promise is settled. After the promise is
* resolved the Generator is resumed with the resolved value, or if the Promise
* is rejected an error is thrown inside the Generator.
*
* If the result is not an Iterator object nor a Promise, the middleware will
* immediately return that value back to the saga, so that it can resume its
* execution synchronously.
*
* When an error is thrown inside the Generator, if it has a `try/catch` block
* surrounding the current `yield` instruction, the control will be passed to
* the `catch` block. Otherwise, the Generator aborts with the raised error, and
* if this Generator was called by another Generator, the error will propagate
* to the calling Generator.
*
* @param fn A Generator function, or normal function which either returns a
* Promise as result, or any other value.
* @param args An array of values to be passed as arguments to `fn`
*/
export function call<Fn extends (...args: any[]) => any>(
fn: Fn,
...args: Parameters<Fn>
): CallEffect<SagaReturnType<Fn>>
/**
* Same as `call([context, fn], ...args)` but supports passing a `fn` as string.
* Useful for invoking object's methods, i.e.
* `yield call([localStorage, 'getItem'], 'redux-saga')`
*/
export function call<Ctx extends { [P in Name]: (this: Ctx, ...args: any[]) => any }, Name extends string>(
ctxAndFnName: [Ctx, Name],
...args: Parameters<Ctx[Name]>
): CallEffect<SagaReturnType<Ctx[Name]>>
/**
* Same as `call([context, fn], ...args)` but supports passing `context` and
* `fn` as properties of an object, i.e.
* `yield call({context: localStorage, fn: localStorage.getItem}, 'redux-saga')`.
* `fn` can be a string or a function.
*/
export function call<Ctx extends { [P in Name]: (this: Ctx, ...args: any[]) => any }, Name extends string>(
ctxAndFnName: { context: Ctx; fn: Name },
...args: Parameters<Ctx[Name]>
): CallEffect<SagaReturnType<Ctx[Name]>>
/**
* Same as `call(fn, ...args)` but supports passing a `this` context to `fn`.
* This is useful to invoke object methods.
*/
export function call<Ctx, Fn extends (this: Ctx, ...args: any[]) => any>(
ctxAndFn: [Ctx, Fn],
...args: Parameters<Fn>
): CallEffect<SagaReturnType<Fn>>
/**
* Same as `call([context, fn], ...args)` but supports passing `context` and
* `fn` as properties of an object, i.e.
* `yield call({context: localStorage, fn: localStorage.getItem}, 'redux-saga')`.
* `fn` can be a string or a function.
*/
export function call<Ctx, Fn extends (this: Ctx, ...args: any[]) => any>(
ctxAndFn: { context: Ctx; fn: Fn },
...args: Parameters<Fn>
): CallEffect<SagaReturnType<Fn>>
export type CallEffect<RT = any> = SimpleEffect<'CALL', CallEffectDescriptor<RT>>
export interface CallEffectDescriptor<RT> {
context: any
fn: (...args: any[]) => SagaIterator<RT> | Promise<RT> | RT
args: any[]
}
export 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
/**
* Alias for `call([context, fn], ...args)`.
*/
export function apply<Ctx extends { [P in Name]: (this: Ctx, ...args: any[]) => any }, Name extends string>(
ctx: Ctx,
fnName: Name,
args: Parameters<Ctx[Name]>,
): CallEffect<SagaReturnType<Ctx[Name]>>
export function apply<Ctx, Fn extends (this: Ctx, ...args: any[]) => any>(
ctx: Ctx,
fn: Fn,
args: Parameters<Fn>,
): CallEffect<SagaReturnType<Fn>>
type Cast<A, B> = A extends B ? A : B
type AnyFunction = (...args: any[]) => any
type RequireCpsCallback<Fn extends (...args: any[]) => any> = Last<Parameters<Fn>> extends CpsCallback<any> ? Fn : never
type RequireCpsNamedCallback<Ctx, Name extends keyof Ctx> = Last<
Parameters<Cast<Ctx[Name], AnyFunction>>
> extends CpsCallback<any>
? Name
: never
/**
* Creates an Effect description that instructs the middleware to invoke `fn` as
* a Node style function.
*
* @param fn a Node style function. i.e. a function which accepts in addition to
* its arguments, an additional callback to be invoked by `fn` when it
* terminates. The callback accepts two parameters, where the first parameter
* is used to report errors while the second is used to report successful
* results
* @param args an array to be passed as arguments for `fn`
*/
export function cps<Fn extends (cb: CpsCallback<any>) => any>(fn: Fn): CpsEffect<ReturnType<Fn>>
export function cps<Fn extends (...args: any[]) => any>(
fn: RequireCpsCallback<Fn>,
...args: CpsFunctionParameters<Fn>
): CpsEffect<ReturnType<Fn>>
/**
* Same as `cps([context, fn], ...args)` but supports passing a `fn` as string.
* Useful for invoking object's methods, i.e.
* `yield cps([localStorage, 'getItem'], 'redux-saga')`
*/
export function cps<Ctx extends { [P in Name]: (this: Ctx, ...args: any[]) => void }, Name extends string>(
ctxAndFnName: [Ctx, RequireCpsNamedCallback<Ctx, Name>],
...args: CpsFunctionParameters<Ctx[Name]>
): CpsEffect<ReturnType<Ctx[Name]>>
/**
* Same as `cps([context, fn], ...args)` but supports passing `context` and
* `fn` as properties of an object, i.e.
* `yield cps({context: localStorage, fn: localStorage.getItem}, 'redux-saga')`.
* `fn` can be a string or a function.
*/
export function cps<Ctx extends { [P in Name]: (this: Ctx, ...args: any[]) => void }, Name extends string>(
ctxAndFnName: { context: Ctx; fn: RequireCpsNamedCallback<Ctx, Name> },
...args: CpsFunctionParameters<Ctx[Name]>
): CpsEffect<ReturnType<Ctx[Name]>>
/**
* Same as `cps(fn, ...args)` but supports passing a `this` context to `fn`.
* This is useful to invoke object methods.
*/
export function cps<Ctx, Fn extends (this: Ctx, ...args: any[]) => void>(
ctxAndFn: [Ctx, RequireCpsCallback<Fn>],
...args: CpsFunctionParameters<Fn>
): CpsEffect<ReturnType<Fn>>
/**
* Same as `cps([context, fn], ...args)` but supports passing `context` and
* `fn` as properties of an object, i.e.
* `yield cps({context: localStorage, fn: localStorage.getItem}, 'redux-saga')`.
* `fn` can be a string or a function.
*/
export function cps<Ctx, Fn extends (this: Ctx, ...args: any[]) => void>(
ctxAndFn: { context: Ctx; fn: RequireCpsCallback<Fn> },
...args: CpsFunctionParameters<Fn>
): CpsEffect<ReturnType<Fn>>
export type CpsFunctionParameters<Fn extends (...args: any[]) => any> = Last<Parameters<Fn>> extends CpsCallback<any>
? AllButLast<Parameters<Fn>>
: never
export interface CpsCallback<R> {
(error: any, result: R): void
cancel?(): void
}
export type CpsEffect<RT> = SimpleEffect<'CPS', CallEffectDescriptor<RT>>
/**
* Creates an Effect description that instructs the middleware to perform a
* *non-blocking call* on `fn`
*
* returns a `Task` object.
*
* #### Note
*
* `fork`, like `call`, can be used to invoke both normal and Generator
* functions. But, the calls are non-blocking, the middleware doesn't suspend
* the Generator while waiting for the result of `fn`. Instead as soon as `fn`
* is invoked, the Generator resumes immediately.
*
* `fork`, alongside `race`, is a central Effect for managing concurrency
* between Sagas.
*
* The result of `yield fork(fn ...args)` is a `Task` object. An object
* with some useful methods and properties.
*
* All forked tasks are *attached* to their parents. When the parent terminates
* the execution of its own body of instructions, it will wait for all forked
* tasks to terminate before returning.
*
* Errors from child tasks automatically bubble up to their parents. If any
* forked task raises an uncaught error, then the parent task will abort with
* the child Error, and the whole Parent's execution tree (i.e. forked tasks +
* the *main task* represented by the parent's body if it's still running) will
* be cancelled.
*
* Cancellation of a forked Task will automatically cancel all forked tasks that
* are still executing. It'll also cancel the current Effect where the cancelled
* task was blocked (if any).
*
* If a forked task fails *synchronously* (ie: fails immediately after its
* execution before performing any async operation), then no Task is returned,
* instead the parent will be aborted as soon as possible (since both parent and
* child execute in parallel, the parent will abort as soon as it takes notice
* of the child failure).
*
* To create *detached* forks, use `spawn` instead.
*
* @param fn A Generator function, or normal function which returns a Promise as result
* @param args An array of values to be passed as arguments to `fn`
*/
export function fork<Fn extends (...args: any[]) => any>(
fn: Fn,
...args: Parameters<Fn>
): ForkEffect<SagaReturnType<Fn>>
/**
* Same as `fork([context, fn], ...args)` but supports passing a `fn` as string.
* Useful for invoking object's methods, i.e.
* `yield fork([localStorage, 'getItem'], 'redux-saga')`
*/
export function fork<Ctx extends { [P in Name]: (this: Ctx, ...args: any[]) => any }, Name extends string>(
ctxAndFnName: [Ctx, Name],
...args: Parameters<Ctx[Name]>
): ForkEffect<SagaReturnType<Ctx[Name]>>
/**
* Same as `fork([context, fn], ...args)` but supports passing `context` and
* `fn` as properties of an object, i.e.
* `yield fork({context: localStorage, fn: localStorage.getItem}, 'redux-saga')`.
* `fn` can be a string or a function.
*/
export function fork<Ctx extends { [P in Name]: (this: Ctx, ...args: any[]) => any }, Name extends string>(
ctxAndFnName: { context: Ctx; fn: Name },
...args: Parameters<Ctx[Name]>
): ForkEffect<SagaReturnType<Ctx[Name]>>
/**
* Same as `fork(fn, ...args)` but supports passing a `this` context to `fn`.
* This is useful to invoke object methods.
*/
export function fork<Ctx, Fn extends (this: Ctx, ...args: any[]) => any>(
ctxAndFn: [Ctx, Fn],
...args: Parameters<Fn>
): ForkEffect<SagaReturnType<Fn>>
/**
* Same as `fork([context, fn], ...args)` but supports passing `context` and
* `fn` as properties of an object, i.e.
* `yield fork({context: localStorage, fn: localStorage.getItem}, 'redux-saga')`.
* `fn` can be a string or a function.
*/
export function fork<Ctx, Fn extends (this: Ctx, ...args: any[]) => any>(
ctxAndFn: { context: Ctx; fn: Fn },
...args: Parameters<Fn>
): ForkEffect<SagaReturnType<Fn>>
export type ForkEffect<RT = any> = SimpleEffect<'FORK', ForkEffectDescriptor<RT>>
export interface ForkEffectDescriptor<RT> extends CallEffectDescriptor<RT> {
detached?: boolean
}
/**
* Same as `fork(fn, ...args)` but creates a *detached* task. A detached task
* remains independent from its parent and acts like a top-level task. The
* parent will not wait for detached tasks to terminate before returning and all
* events which may affect the parent or the detached task are completely
* independents (error, cancellation).
*/
export function spawn<Fn extends (...args: any[]) => any>(
fn: Fn,
...args: Parameters<Fn>
): ForkEffect<SagaReturnType<Fn>>
/**
* Same as `spawn([context, fn], ...args)` but supports passing a `fn` as string.
* Useful for invoking object's methods, i.e.
* `yield spawn([localStorage, 'getItem'], 'redux-saga')`
*/
export function spawn<Ctx extends { [P in Name]: (this: Ctx, ...args: any[]) => any }, Name extends string>(
ctxAndFnName: [Ctx, Name],
...args: Parameters<Ctx[Name]>
): ForkEffect<SagaReturnType<Ctx[Name]>>
/**
* Same as `spawn([context, fn], ...args)` but supports passing `context` and
* `fn` as properties of an object, i.e.
* `yield spawn({context: localStorage, fn: localStorage.getItem}, 'redux-saga')`.
* `fn` can be a string or a function.
*/
export function spawn<Ctx extends { [P in Name]: (this: Ctx, ...args: any[]) => any }, Name extends string>(
ctxAndFnName: { context: Ctx; fn: Name },
...args: Parameters<Ctx[Name]>
): ForkEffect<SagaReturnType<Ctx[Name]>>
/**
* Same as `spawn(fn, ...args)` but supports passing a `this` context to `fn`.
* This is useful to invoke object methods.
*/
export function spawn<Ctx, Fn extends (this: Ctx, ...args: any[]) => any>(
ctxAndFn: [Ctx, Fn],
...args: Parameters<Fn>
): ForkEffect<SagaReturnType<Fn>>
/**
* Same as `spawn([context, fn], ...args)` but supports passing `context` and
* `fn` as properties of an object, i.e.
* `yield spawn({context: localStorage, fn: localStorage.getItem}, 'redux-saga')`.
* `fn` can be a string or a function.
*/
export function spawn<Ctx, Fn extends (this: Ctx, ...args: any[]) => any>(
ctxAndFn: { context: Ctx; fn: Fn },
...args: Parameters<Fn>
): ForkEffect<SagaReturnType<Fn>>
/**
* Creates an Effect description that instructs the middleware to wait for the
* result of a previously forked task.
*
* #### Notes
*
* `join` will resolve to the same outcome of the joined task (success or
* error). If the joined task is cancelled, the cancellation will also propagate
* to the Saga executing the join effect. Similarly, any potential callers of
* those joiners will be cancelled as well.
*
* @param task A `Task` object returned by a previous `fork`
*/
export function join(task: Task): JoinEffect
/**
* Creates an Effect description that instructs the middleware to wait for the
* results of previously forked tasks.
*
* @param tasks A `Task` is the object returned by a previous `fork`
*/
export function join(tasks: Task[]): JoinEffect
export type JoinEffect = SimpleEffect<'JOIN', JoinEffectDescriptor>
export type JoinEffectDescriptor = Task | Task[]
/**
* Creates an Effect description that instructs the middleware to cancel a
* previously forked task.
*
* #### Notes
*
* To cancel a running task, the middleware will invoke `return` on the
* underlying Generator object. This will cancel the current Effect in the task
* and jump to the finally block (if defined).
*
* Inside the finally block, you can execute any cleanup logic or dispatch some
* action to keep the store in a consistent state (e.g. reset the state of a
* spinner to false when an ajax request is cancelled). You can check inside the
* finally block if a Saga was cancelled by issuing a `yield cancelled()`.
*
* Cancellation propagates downward to child sagas. When cancelling a task, the
* middleware will also cancel the current Effect (where the task is currently
* blocked). If the current Effect is a call to another Saga, it will be also
* cancelled. When cancelling a Saga, all *attached forks* (sagas forked using
* `yield fork()`) will be cancelled. This means that cancellation effectively
* affects the whole execution tree that belongs to the cancelled task.
*
* `cancel` is a non-blocking Effect. i.e. the Saga executing it will resume
* immediately after performing the cancellation.
*
* For functions which return Promise results, you can plug your own
* cancellation logic by attaching a `[CANCEL]` to the promise.
*
* The following example shows how to attach cancellation logic to a Promise
* result:
*
* import { CANCEL } from 'redux-saga'
* import { fork, cancel } from 'redux-saga/effects'
*
* function myApi() {
* const promise = myXhr(...)
*
* promise[CANCEL] = () => myXhr.abort()
* return promise
* }
*
* function* mySaga() {
*
* const task = yield fork(myApi)
*
* // ... later
* // will call promise[CANCEL] on the result of myApi
* yield cancel(task)
* }
*
* redux-saga will automatically cancel jqXHR objects using their `abort` method.
*
* @param task A `Task` object returned by a previous `fork`
*/
export function cancel(task: Task): CancelEffect
/**
* Creates an Effect description that instructs the middleware to cancel
* previously forked tasks.
*
* #### Notes
*
* It wraps the array of tasks in cancel effects, roughly becoming the
* equivalent of `yield tasks.map(t => cancel(t))`.
*
* @param tasks A `Task` is the object returned by a previous `fork`
*/
export function cancel(tasks: Task[]): CancelEffect
/**
* Creates an Effect description that instructs the middleware to cancel a task
* in which it has been yielded (self-cancellation). It allows to reuse
* destructor-like logic inside a `finally` blocks for both outer
* (`cancel(task)`) and self (`cancel()`) cancellations.
*
* #### Example
*
* function* deleteRecord({ payload }) {
* try {
* const { confirm, deny } = yield call(prompt);
* if (confirm) {
* yield put(actions.deleteRecord.confirmed())
* }
* if (deny) {
* yield cancel()
* }
* } catch(e) {
* // handle failure
* } finally {
* if (yield cancelled()) {
* // shared cancellation logic
* yield put(actions.deleteRecord.cancel(payload))
* }
* }
* }
*/
export function cancel(): CancelEffect
export type CancelEffect = SimpleEffect<'CANCEL', CancelEffectDescriptor>
export type CancelEffectDescriptor = Task | Task[] | SELF_CANCELLATION
type SELF_CANCELLATION = '@@redux-saga/SELF_CANCELLATION'
/**
* Creates an effect that instructs the middleware to invoke the provided
* selector on the current Store's state (i.e. returns the result of
* `selector(getState(), ...args)`).
*
* If `select` is called without argument (i.e. `yield select()`) then the
* effect is resolved with the entire state (the same result of a `getState()`
* call).
*
* > It's important to note that when an action is dispatched to the store, the
* middleware first forwards the action to the reducers and then notifies the
* Sagas. This means that when you query the Store's State, you get the State
* **after** the action has been applied. However, this behavior is only
* guaranteed if all subsequent middlewares call `next(action)` synchronously.
* If any subsequent middleware calls `next(action)` asynchronously (which is
* unusual but possible), then the sagas will get the state from **before** the
* action is applied. Therefore it is recommended to review the source of each
* subsequent middleware to ensure it calls `next(action)` synchronously, or
* else ensure that redux-saga is the last middleware in the call chain.
*
* #### Notes
*
* Preferably, a Saga should be autonomous and should not depend on the Store's
* state. This makes it easy to modify the state implementation without
* affecting the Saga code. A saga should preferably depend only on its own
* internal control state when possible. But sometimes, one could find it more
* convenient for a Saga to query the state instead of maintaining the needed
* data by itself (for example, when a Saga duplicates the logic of invoking
* some reducer to compute a state that was already computed by the Store).
*
* For example, suppose we have this state shape in our application:
*
* state = {
* cart: {...}
* }
*
* We can create a *selector*, i.e. a function which knows how to extract the
* `cart` data from the State:
*
* `./selectors`
*
* export const getCart = state => state.cart
*
*
* Then we can use that selector from inside a Saga using the `select` Effect:
*
* `./sagas.js`
*
* import { take, fork, select } from 'redux-saga/effects'
* import { getCart } from './selectors'
*
* function* checkout() {
* // query the state using the exported selector
* const cart = yield select(getCart)
*
* // ... call some API endpoint then dispatch a success/error action
* }
*
* export default function* rootSaga() {
* while (true) {
* yield take('CHECKOUT_REQUEST')
* yield fork(checkout)
* }
* }
*
* `checkout` can get the needed information directly by using
* `select(getCart)`. The Saga is coupled only with the `getCart` selector. If
* we have many Sagas (or React Components) that needs to access the `cart`
* slice, they will all be coupled to the same function `getCart`. And if we now
* change the state shape, we need only to update `getCart`.
*
* @param selector a function `(state, ...args) => args`. It takes the current
* state and optionally some arguments and returns a slice of the current
* Store's state
* @param args optional arguments to be passed to the selector in addition of
* `getState`.
*/
export function select(): SelectEffect
export function select<Fn extends (state: any, ...args: any[]) => any>(
selector: Fn,
...args: Tail<Parameters<Fn>>
): SelectEffect
export type SelectEffect = SimpleEffect<'SELECT', SelectEffectDescriptor>
export interface SelectEffectDescriptor {
selector(state: any, ...args: any[]): any
args: any[]
}
/**
* Creates an effect that instructs the middleware to queue the actions matching
* `pattern` using an event channel. Optionally, you can provide a buffer to
* control buffering of the queued actions.
*
* #### Example
*
* The following code creates a channel to buffer all `USER_REQUEST` actions.
* Note that even the Saga may be blocked on the `call` effect. All actions that
* come while it's blocked are automatically buffered. This causes the Saga to
* execute the API calls one at a time
*
* import { actionChannel, call } from 'redux-saga/effects'
* import api from '...'
*
* function* takeOneAtMost() {
* const chan = yield actionChannel('USER_REQUEST')
* while (true) {
* const {payload} = yield take(chan)
* yield call(api.getUser, payload)
* }
* }
*
* @param pattern see API for `take(pattern)`
* @param buffer a `Buffer` object
*/
export function actionChannel(pattern: ActionPattern, buffer?: Buffer<Action>): ActionChannelEffect
export type ActionChannelEffect = SimpleEffect<'ACTION_CHANNEL', ActionChannelEffectDescriptor>
export interface ActionChannelEffectDescriptor {
pattern: ActionPattern
buffer?: Buffer<Action>
}
/**
* Creates an effect that instructs the middleware to flush all buffered items
* from the channel. Flushed items are returned back to the saga, so they can be
* utilized if needed.
*
* #### Example
*
* function* saga() {
* const chan = yield actionChannel('ACTION')
*
* try {
* while (true) {
* const action = yield take(chan)
* // ...
* }
* } finally {
* const actions = yield flush(chan)
* // ...
* }
* }
*
* @param channel a `Channel` Object.
*/
export function flush<T>(channel: FlushableChannel<T>): FlushEffect<T>
export type FlushEffect<T> = SimpleEffect<'FLUSH', FlushEffectDescriptor<T>>
export type FlushEffectDescriptor<T> = FlushableChannel<T>
/**
* Creates an effect that instructs the middleware to return whether this
* generator has been cancelled. Typically you use this Effect in a finally
* block to run Cancellation specific code
*
* #### Example
*
* function* saga() {
* try {
* // ...
* } finally {
* if (yield cancelled()) {
* // logic that should execute only on Cancellation
* }
* // logic that should execute in all situations (e.g. closing a channel)
* }
* }
*/
export function cancelled(): CancelledEffect
export type CancelledEffect = SimpleEffect<'CANCELLED', CancelledEffectDescriptor>
export type CancelledEffectDescriptor = {}
/**
* Creates an effect that instructs the middleware to update its own context.
* This effect extends saga's context instead of replacing it.
*/
export function setContext<C extends object>(props: C): SetContextEffect<C>
export type SetContextEffect<C extends object> = SimpleEffect<'SET_CONTEXT', SetContextEffectDescriptor<C>>
export type SetContextEffectDescriptor<C extends object> = C
/**
* Creates an effect that instructs the middleware to return a specific property
* of saga's context.
*/
export function getContext(prop: string): GetContextEffect
export type GetContextEffect = SimpleEffect<'GET_CONTEXT', GetContextEffectDescriptor>
export type GetContextEffectDescriptor = string
/**
* Returns an effect descriptor to block execution for `ms` milliseconds and return `val` value.
*/
export function delay<T = true>(ms: number, val?: T): CallEffect<T>
/**
* Spawns a `saga` on an action dispatched to the Store that matches `pattern`.
* After spawning a task it's still accepting incoming actions into the
* underlying `buffer`, keeping at most 1 (the most recent one), but in the same
* time holding up with spawning new task for `ms` milliseconds (hence its name -
* `throttle`). Purpose of this is to ignore incoming actions for a given
* period of time while processing a task.
*
* #### Example
*
* In the following example, we create a basic task `fetchAutocomplete`. We use
* `throttle` to start a new `fetchAutocomplete` task on dispatched
* `FETCH_AUTOCOMPLETE` action. However since `throttle` ignores consecutive
* `FETCH_AUTOCOMPLETE` for some time, we ensure that user won't flood our
* server with requests.
*
* import { call, put, throttle } from `redux-saga/effects`
*
* function* fetchAutocomplete(action) {
* const autocompleteProposals = yield call(Api.fetchAutocomplete, action.text)
* yield put({type: 'FETCHED_AUTOCOMPLETE_PROPOSALS', proposals: autocompleteProposals})
* }
*
* function* throttleAutocomplete() {
* yield throttle(1000, 'FETCH_AUTOCOMPLETE', fetchAutocomplete)
* }
*
* #### Notes
*
* `throttle` is a high-level API built using `take`, `fork` and
* `actionChannel`. Here is how the helper could be implemented using the
* low-level Effects
*
* const throttle = (ms, pattern, task, ...args) => fork(function*() {
* const throttleChannel = yield actionChannel(pattern, buffers.sliding(1))
*
* while (true) {
* const action = yield take(throttleChannel)
* yield fork(task, ...args, action)
* yield delay(ms)
* }
* })
*
* @param ms length of a time window in milliseconds during which actions will
* be ignored after the action starts processing
* @param pattern for more information see docs for `take(pattern)`
* @param saga a Generator function
* @param args arguments to be passed to the started task. `throttle` will add
* the incoming action to the argument list (i.e. the action will be the last
* argument provided to `saga`)
*/
export function throttle<P extends ActionPattern>(
ms: number,
pattern: P,
worker: (action: ActionMatchingPattern<P>) => any,
): ForkEffect<never>
export function throttle<P extends ActionPattern, Fn extends (...args: any[]) => any>(
ms: number,
pattern: P,
worker: Fn,
...args: HelperWorkerParameters<ActionMatchingPattern<P>, Fn>
): ForkEffect<never>
export function throttle<A extends Action>(
ms: number,
pattern: ActionPattern<A>,
worker: (action: A) => any,
): ForkEffect<never>
export function throttle<A extends Action, Fn extends (...args: any[]) => any>(
ms: number,
pattern: ActionPattern<A>,
worker: Fn,
...args: HelperWorkerParameters<A, Fn>
): ForkEffect<never>
/**
* You can also pass in a channel as argument and the behaviour is the same as
* `throttle(ms, pattern, saga, ...args)`.
*/
export function throttle<T>(ms: number, channel: TakeableChannel<T>, worker: (item: T) => any): ForkEffect<never>
export function throttle<T, Fn extends (...args: any[]) => any>(
ms: number,
channel: TakeableChannel<T>,
worker: Fn,
...args: HelperWorkerParameters<T, Fn>
): ForkEffect<never>
/**
* Spawns a `saga` on an action dispatched to the Store that matches `pattern`.
* Saga will be called after it stops taking `pattern` actions for `ms`
* milliseconds. Purpose of this is to prevent calling saga until the actions
* are settled off.
*
* #### Example
*
* In the following example, we create a basic task `fetchAutocomplete`. We use
* `debounce` to delay calling `fetchAutocomplete` saga until we stop receive
* any `FETCH_AUTOCOMPLETE` events for at least `1000` ms.
*
* import { call, put, debounce } from `redux-saga/effects`
*
* function* fetchAutocomplete(action) {
* const autocompleteProposals = yield call(Api.fetchAutocomplete, action.text)
* yield put({type: 'FETCHED_AUTOCOMPLETE_PROPOSALS', proposals: autocompleteProposals})
* }
*
* function* debounceAutocomplete() {
* yield debounce(1000, 'FETCH_AUTOCOMPLETE', fetchAutocomplete)
* }
*
* #### Notes
*
* `debounce` is a high-level API built using `take`, `delay` and `fork`. Here
* is how the helper could be implemented using the low-level Effects
*
* const debounce = (ms, pattern, task, ...args) => fork(function*() {
* while (true) {
* let action = yield take(pattern)
*
* while (true) {
* const { debounced, _action } = yield race({
* debounced: delay(ms),
* _action: take(pattern)
* })
*
* if (debounced) {
* yield fork(worker, ...args, action)
* break
* }
*
* action = _action
* }
* }
* })
*
* @param ms defines how many milliseconds should elapse since the last time
* `pattern` action was fired to call the `saga`
* @param pattern for more information see docs for `take(pattern)`
* @param saga a Generator function
* @param args arguments to be passed to the started task. `debounce` will add
* the incoming action to the argument list (i.e. the action will be the last
* argument provided to `saga`)
*/
export function debounce<P extends ActionPattern>(
ms: number,
pattern: P,
worker: (action: ActionMatchingPattern<P>) => any,
): ForkEffect<never>
export function debounce<P extends ActionPattern, Fn extends (...args: any[]) => any>(
ms: number,
pattern: P,
worker: Fn,
...args: HelperWorkerParameters<ActionMatchingPattern<P>, Fn>
): ForkEffect<never>
export function debounce<A extends Action>(
ms: number,
pattern: ActionPattern<A>,
worker: (action: A) => any,
): ForkEffect<never>
export function debounce<A extends Action, Fn extends (...args: any[]) => any>(
ms: number,
pattern: ActionPattern<A>,
worker: Fn,
...args: HelperWorkerParameters<A, Fn>
): ForkEffect<never>
/**
* You can also pass in a channel as argument and the behaviour is the same as
* `debounce(ms, pattern, saga, ...args)`.
*/
export function debounce<T>(ms: number, channel: TakeableChannel<T>, worker: (item: T) => any): ForkEffect<never>
export function debounce<T, Fn extends (...args: any[]) => any>(
ms: number,
channel: TakeableChannel<T>,
worker: Fn,
...args: HelperWorkerParameters<T, Fn>
): ForkEffect<never>
/**
* Creates an Effect description that instructs the middleware to call the
* function `fn` with `args` as arguments. In case of failure will try to make
* another call after `delay` milliseconds, if a number of attempts < `maxTries`.
*
* #### Example
*
* In the following example, we create a basic task `retrySaga`. We use `retry`
* to try to fetch our API 3 times with 10 second interval. If `request` fails
* first time than `retry` will call `request` one more time while calls count
* less than 3.
*
* import { put, retry } from 'redux-saga/effects'
* import { request } from 'some-api';
*
* function* retrySaga(data) {
* try {
* const SECOND = 1000
* const response = yield retry(3, 10 * SECOND, request, data)
* yield put({ type: 'REQUEST_SUCCESS', payload: response })
* } catch(error) {
* yield put({ type: 'REQUEST_FAIL', payload: { error } })
* }
* }
*
* @param maxTries maximum calls count.
* @param delay length of a time window in milliseconds between `fn` calls.
* @param fn A Generator function, or normal function which either returns a
* Promise as a result, or any other value.
* @param args An array of values to be passed as arguments to `fn`
*/
export function retry<Fn extends (...args: any[]) => any>(
maxTries: number,
delayLength: number,
fn: Fn,
...args: Parameters<Fn>
): CallEffect<SagaReturnType<Fn>>
/**
* Creates an Effect description that instructs the middleware to run multiple
* Effects in parallel and wait for all of them to complete. It's quite the
* corresponding API to standard
* [`Promise#all`](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Promise/all).
*
* #### Example
*
* The following example runs two blocking calls in parallel:
*
* import { fetchCustomers, fetchProducts } from './path/to/api'
* import { all, call } from `redux-saga/effects`
*
* function* mySaga() {
* const [customers, products] = yield all([
* call(fetchCustomers),
* call(fetchProducts)
* ])
* }
*/
export function all<T>(effects: T[]): AllEffect<T>
/**
* The same as `all([...effects])` but let's you to pass in a dictionary object
* of effects with labels, just like `race(effects)`
*
* @param effects a dictionary Object of the form {label: effect, ...}
*/
export function all<T>(effects: { [key: string]: T }): AllEffect<T>
export type AllEffect<T> = CombinatorEffect<'ALL', T>
export type AllEffectDescriptor<T