This library serves as an extension for the flow
logic that mobx provides, adding some key
features:
- Asynchronous logic for computed values.
- Cancelling ongoing logic for async computed values & actions.
- Useful effects to spawn and kill "sub-processes".
Declare your sagas in your observable and then use the decorate
function from mobx to specify
the logic you want to apply.
const user = observable({
channel: null,
messages: function* (channel) {
const messages = yield getMessagesFromChannel(channel);
return messages;
}
})
decorate(user, {
messages: latest([], () => user.channel)
})
This will make it so every change to the user.channel
property will trigger
getMessagesFromChannel
async request, which then overwrite the messages
property, now modifying
properties triggers async flows!
Use these to decorate
your properties in observable objects, you have to define an initial value
so it can be initialized and a trackingFn
that acts as a mobx reaction
, the result of this
function will be observed, and its value will passed to the generator every time it changes.
every(initialValue, trackingFn)
: will create a new instance of each saga every time it triggerslatest(initialValue, trackingFn)
: will cancel previous running instances every time it triggerschannel(initialValue, trackingFn)
: will queue new instances every time it triggers
You can also define saga actions the same way you would define flow
using the decorators.
Within sagas you can yield many kinds of sagaable expressions, depending on the type of the expression the saga will handle it differently:
- Iterables: the saga will start iterating over the given iterable.
- Promises: the saga will wait for the promise to resolve or reject.
- Arrays: the saga will handle every item of the array at the same time, useful for concurrency.
Creates a new non-blocking saga from the given input and appends it to the saga that spawns it. Useful when you need to keep track of child saga execution.
Creates a saga for each different input and races them, the first one to finish will return and cancels the rest.
A promise that resolves after the given ms, if val
is specified, it will resolve to that value.
Cancels the given saga, same as saga.cancel()
.
Hangs the saga forever.
Allows you to define custom cancellation logic for your blocking calls. When your saga is
cancelled the cancellation propagates to all of its children but this doesn't prevent external
logic from cancelling as well. This effect allows you to "subscribe" to the cancellation event and
fire up the handler
when it happens, this is useful to tell external logic that the saga is no
longer paying attention to its result and that it may stop what its doing.
Example:
// `request` adds an `.abort` method to its returned promise
function* upload() {
const result = yield teardown(
// this value is "thenable" and blocks the saga
request(url, file),
// will called if this saga is cancelled at any time
req => req.abort()
)
// if the saga is cancelled at this point the `.abort` method
// wont be called
return result
}
Creates a new saga from the given input
and starts running it immediately. The ctx
is an
object that can be accessed by the saga and by all child sagas of itself as well.
Saga.prototype.promise: Promise
: a promise that resolves when the saga (and all its children) finishes succesfully (no errors emitted), it will resolve if the saga is cancelled, but with no result value.Saga.prototype.cancel()
: cancels the saga (and all of its children).Saga.prototype.spawn(input)
: creates a child saga from the inputSaga.prototype.free()
: cancel all the child sagasSaga.prototype.running: Boolean
: is the saga still running.Saga.prototype.cancelled: Boolean
: was the saga cancelled.Saga.prototype.resolved: Boolean
: was the saga resolved.Saga.prototype.rejected: Boolean
: was the saga rejected.Saga.immedate(input: Iterable): Promise
: shorthand for creating a new saga and return the promise.
Allows you to define your own effects
payloadCreator: (...args)
: A function that will transform...args
passed to the effect into a single payload, you should define your effect's function signature using this function.handler: (saga: Saga, payload: any)
: The handler function will be invoked when the effect is yielded by a saga, thesaga
is the saga yielding the effect and thepayload
is the result of callingpayloadCreator
with the arguments passed to the effect when yielded. The handler can return a promise as well.
Example:
const race = effect(
// payloadCreator: concats all parameters into an array
(...inputs) => inputs,
// handler: invoked with the result of the payloadCreator
(saga, inputs) => {
const children = inputs.map(item => saga.spawn(item));
return Promise.race(children.map(child => child.promise))
.then(result => {
children.forEach(child => child.cancel());
return result;
});
}
);
import { Saga, latest, fork, delay } from 'mobx-saga'
Sagas can have child sagas, these will affect how your saga behaves. They can be created
by effects such as fork
, race
or latest
.
The following rules apply:
- When a child saga throws, so will the parent.
- When a parent saga cancels it will also cancel all of its children.
- When a parent saga finishes, it wont be resolved until all of its children finish as well.
- Cancelling a saga means it will never resolve or reject.
Sagas can throw errors as well as handle them, for most use cases the handling of errors should be exactly like async/await functions do. However if a child saga throws an error to its parent there's no try/catch to stop it, make sure to catch errors within the saga, otherwise they would just propagate indefinetly.
Wait for an amount of sagas to complete
function* main() {
try {
// resolves when the 3 are done
const [user, news, notifications] = yield [
getUser(),
getNewsfeed(),
getNotifications()
]
} catch (err) {
// if any of the 3 above emit an error will be handled here
}
}
function* pollNewsfeed() {
while (true) {
const news = yield getNewsfeed()
// ...
yield delay(1000)
}
}
function* main() {
// start non-blocking saga
const saga = yield fork(pollNewsfeed())
yield delay(8000)
// cancel the saga after 8 seconds
yield cancel(saga)
}