An alternative side effect model for Redux apps
MIT License
Bot releases are hidden (Show)
Published by Andarist almost 8 years ago
This release contains mostly deprecations, but only a mild ones. Mainly few things got better, more descriptive names to indicate their behaviour and helpers got their way into effects
. Of course as those are deprecations only, we are still planning to support old APIs for a time being, but as those do not require much change in the code they old versions will get removed eventually.
New features:
Fixes:
runSaga
APIDeprecations:
put.sync
got renamed to put.resolve
takem
got renamed to take.maybe
createSagaMiddleware({ onerror })
got fixed in the code (was documented as onError
) and should be used from now on as docs suggested - createSagaMiddleware({ onError })
(thanks to @kuy)takeEvery
, takeLatest
, throttle
) got their respective counterparts in redux-saga/effects
module, which should confuse people less and be more newcomers-friendlyimport { takeEvery } from 'redux-saga/effects'
// ...
yield* takeEvery('ACTION', worker) // this WON'T work, as effect is just an object
const task = yield takeEvery('ACTION', worker) // this WILL work like charm
-----
import { takeEvery } from 'redux-saga'
// ...
yield* takeEvery('ACTION', worker) // this will continue to work for now
const task = yield takeEvery('ACTION', worker) // and so will this
Also few typings were fixed, thanks to @yenshih, @CarsonF and @aikoven for veryfing.
And last (but not least) great thanks and much appreciation to everyone who has contributed to improving the docs!
Happy Christmas.
This release includes an Improved saga monitor API (see #609).
sagaMiddleware.run
or runSaga
.actionDispatched
trigger to the monitor contract. We need this in order to figure out saga/take effects that reacted to a given action. Also added a silent flag SAGA_ACTION
(non enumerable property) added to distinguish actions dispatched by sagas from others.Other changes
Published by Andarist almost 8 years ago
put
effect (thanks to @michaelgilley)race
s sometimes dropping buffered items in channelscancel
callback to cps
effect (thanks to @alxandr)Added new effect flush
(API docs). It can be used to clear buffered items in the channel and geting them back in a batch in the saga (thanks to @Andarist)
Added a new helper throttle
(API docs)(thanks to @Andarist)
Added a new buffer type expanding
(API docs) (thanks to @Andarist) . This new type also fixes the breaking change that was introduced by v0.11.1 with the new internal buffer implementation (see #511 [only if you need more details]).
TL/DR: Use buffer.fixed(limit)
when you need a bounded buffer (will throw an error when the untaked messages exceed the buffer limit). Use buffer.expanding(limit)
when you need an auto expanding buffer.
createSagaMiddleware supports a new (optional) option: onError
API docs to detect uncaught exceptions from Sagas (thanks to @secobarbital)
Saga helpers (takeEvery
, takeLatest
, throttle
) are now non blocking (thanks to @Andarist).
Saga helpers return Iterator objects. In previous versions they were treated just like other Iterators and Promises in redux-saga, the Saga calling a helper (either via yield
or yield*
) will block until the Iterator returns. But since Saga helpers never return (because they are looping forever ) this will also cause the calling Saga to block forever. So in previous versions, if you wanted to call a helper in a non blocking way you had to use the fork
effect
function* saga1() { ... }
function* saga2() { ... }
function* mainSaga() {
yield fork(takeEvery, 'ACTION_1', saga1)
yield fork(takeLatest, 'ACTION_2', saga2)
}
With this change, redux-saga handles the helpers like forks when used with yield
which mean you can call them directly like this
function* mainSaga() {
const task1 = yield takeEvery('ACTION_1', saga1)
const task2 = yield takeLatest('ACTION_2', saga2)
}
Note this will not work when used with the delegation form yield*
: eg yield* takeEvery(...)
will block the Generator. This is due to how yield*
works in Generators in JavaScript and can't be handled by redux-saga.
logger
to customize logging (see API docs for createSagaMiddleware)runSaga
The monitor argument is removed. Starting from this release, you must provide the monitor as a sagaMonitor
key in the option argument. runSaga
supports also the logger
option
runSaga(iterator, {
subscribe: ...,
dispatch: ...,
getState: ...,
+ sagaMonitor: monitor,
+ logger: ...
},
- monitor
)
This restores the pre 0.10 behavior for handling put
s whose result is a Promise (i.e. dispatch an action which will be handled by a middleware and returns a Promise). In pre 0.10 releases, the put
doesn't wait for the returned promise to resolve;
Starting from this release
put
doesn't block waiting the returned promise to resolveput.sync(...)
which will wait until the Promise resolve (or rejects) before resumingFor background see #336
Other changes
takeEvery/takeLatest
(thanks to @jscinoz)Changes
END
action by string type
, not by reference, to allow middleware-transformed actionsA patch release fixing some issues
This is a major release. Make sure to read the following notes because there are some breaking changes
It's no longer possible to start the Sagas in the applyMiddleware
phase. Starting from this version Sagas must be started using the sagaMiddleware.run(saga, ...args)
method. For a background see #235
Before this release, we used the following to start Sagas
import { createStore, applyMiddleware } from 'redux'
import createSagaMiddleware from 'redux-saga'
import reducer from './reducers'
import rootSaga from './sagas'
const store = createStore(
reducer,
applyMiddleware(createSagaMiddleware(rootSaga))
)
This is no longer valid. Instead you'll have to use the following
import { createStore, applyMiddleware } from 'redux'
import createSagaMiddleware from 'redux-saga'
import reducer from './reducers'
import rootSaga from './sagas'
const sagaMiddleware = createSagaMiddleware()
const store = createStore(
reducer,
applyMiddleware(sagaMiddleware)
)
sagaMiddleware.run(rootSaga)
Also see examples for an alternative way which enahnces the store with a runSaga
method
Removed deprecated getState
argument passed to Sagas. You can use the select
Effect to get the store state
In prior release, all forked tasks were detached from their parents. The parent Saga terminates as soon as its own body of instructions is fully executed and errors from forked tasks do not propagate up to their parents which in some situations caused errors from forks to be swallowed. Also cancellation of a Parent task do not automatically propagate to child tasks, which means one have to maintain the list of running tasks manually and take care of cancelling them if the parent was cancelled
This new release includes a new fork model. By default, all forked tasks are attached to their parents. The whole parent + direct and indirect children form an execution tree (vs execution path in sequential programming). This implies new semantics from the prior releases
To create detached forks which follow the old model. You can use the new effect spawn
.
The release introduces a new model for Cancellation propagation. In prior releases, Cancellation of a task throws a SagaCancellationException
inside the cancelled task. Starting from this release, Cancellations no longer throw inside cancelled tasks. This means that special cancellation handling no longer interfere with Error handling. In prior release, any try/catch block needed this chek
function* saga() {
try {
yield call(someApi)
yield put(SuccessAction())
} catch(err) {
// ensure this is not a Cancellation error before handling the error
// We do not want to dispatch an Error on cancellations
if(!isCancel(err))
yield put(ErrorAction())
}
}
Starting with this release; the check is no longer necessary. If you want to react to cancellations, you can do it inside finally block. A new effect cancelled()
is provided to check if the Saga has been cancelled
function* saga() {
try {
yield call(someApi)
yield put(SuccessAction())
} catch(err) {
yield put(ErrorAction())
} finally {
if(yield cancelled()) {
// logic proper to cancellation
}
}
}
So SagaCancellationException
class and isCancelError
function were removed.
Also Cancellation are now simply logged using console.info
in dev mode instead of warnings
For background see #266
The redux-saga concurrency model introduces a new abstraction channel. You can now use take
and put
with other sources than the Store actions. Esp. eventChannel
allows sagas to take from external event sources. You can find a simple example in the cancellable-counter repo example. For more info see API docs.
END
(support SSR and Universal Sagas)This release introduces a special action END
which can be used to notify that an event source has terminated. You can also dispatch an END
to notify Sagas that no more Store actions will be fired. This can be handy in Server Side rendering to break the while(true)
loop inside watchers.The real-world example in the repo shows a possible ways to implement universal Sagas and SSR.
END
actions are automatically handled by the middleware and cause a Saga blocked on a take effect to automatically terminate (but it'll still wait for its forked tasks which provides support for SSR). To catch END
values you can use the new Effect takem
(aka takeMaybe). Using takem
you get the explicit END
so you can handle it manually. See #255 for more infos
Monitor actions are no longer fired by default in dev mode. To activate monitoring, you'll have to pass the {sagaMonitor}
option explicitly to the middleware factory. Also monitor events are now dispatched to the Saga monitor via direct method calls instead of dispatching actions to the Store. See repo examples for usage examples.
yield [take(...), take(...)]
delay(ms, [val])
functionrunSaga
: remove deprecated StoreIO
Task
object gets a new property:isCancelled
A small patch with the following merges
yield* takeEvery
causing 'not a function' error'. (issue #197)takeEvery
and takeLatest
. Now if you do a yield takeEvery(...)
(instead of yield* takeEvery(...)
). You'll get a better logging (previously sagaMonitor logged 'anonymous')This patch removes all Symbol
references from the source code. Some reported issues were related to buggy/incomplete/absent Symbol
support which caused subtle and hard to track bugs on the source code.
The lib uses now simple namespaced strings to identify internal constants
Well, perhaps this is the fastest patch in the release history.
So following the advice of @gaearon. I decided to drop the getState
effect and only keep select
. To get the entire Store's state, simply use yield select()
without arguments
import { getState } from 'redux-saga/effects'
function* saga() {
// No longer valid with 0.9.1
yield getState()
}
import { select } from 'redux-saga/effects'
function* saga() {
// get the entire state
yield select()
}
getState()
param passed to the root Sagas is now deprecated (this is the getState
argument passed to the root Sagas started by the middleware, not to confound with the dropped getState
effect above)select
effects