redux-yield-effect
Declarative side effects for redux with generators
redux-yield-effect middleware allows to write action creators as easily testable side-effect free generators.
It provides extensible set of operators which allow to describe any possible side effect (API service call, action dispatch, etc.) as a plain javascript object that is handled and executed on the level of middleware, so that user code remains side-effect free.
Motivation
This library is strongly inspired by the awesome redux-saga project. Actually the API of the redux-yield-effect almost completely copies one from the redux-saga. But even though these libs have a lot of similarities, they are different in a very important aspect - the way of kicking off the effect generators. redux-saga promotes the approach of long-running daemon processes that are listening to an action/event to start/resume execution, whereas in redux-yield-effect you kick off the effect generator by simply dispatching it (approach similar to the redux-thunk). You may read more about the motivation behind it here.
Installation
npm install --save redux-yield-effect
Usage
Check complete example here
;;; const store = ; // dispatch business logic coroutinestore ; // main business logic coroutine { // load user address and product price in the background // "fork" calls a function or a coroutine and continues execution without waiting for a result // we will "join" that result later const fetchUserAddressFromDBTask = ; const fetchProductPriceFromDBTask = ; try // reserve the product // "call" calls a function or a coroutine and waits until it asynchronously resolves ; // fetch user payment information const userPaymentDetails = ; // "put" dispatches action ; // make the payment // here we "join" the result of the previously called function "fetchProductPriceFromDB", so wait until it is done const price = ; // here we "call" a coroutine (another generator that yields declarative effects) ; // add shipping address and complete order const address = ; ; const order = ; ; return order; catch error // if any of the yielded effects from the "try" block fails, we could catch that error here // cancel product reservation and report error ; ; // re-throw error to the caller throw error; } // payment coroutine { const validationResult = ; if validationResultstatus !== 'success' throw `card number is not valid`; ; ; ;}
How it works
Each effect creator (put
, call
, etc.) instead of performing real side effect returns just a plain object that describes the effect.
For example call(myApiService, 123, 'foo')
will produce:
type: 'YIELD_EFFECT_CALL' payload: func: myApiService args: 123 'foo'
When yield
ed from an action creator this effect description is picked up by the middleware and handed over to a corresponding effect processor based on the type
property. Effect processor performs that side effect and the eventual result/error of it is returned/thrown back to the action creator at the place the effect was yield
ed from.
This approach allows developer to write pure action creators that may define complex business logic with async execution flow, yet being trivial to test.
API
createYieldEffectMiddleware(customEffectProcessors?): Function
Creates redux middleware that handles effect coroutines.
customEffectProcessors: { [effectType: string]: [effectProcessor: Function] }
-optional
- object that specifies the mapping between custom effect's type and effectProcessor function. This allows to create and use your own or third party effect creators with redux-yield-effect
Effect creators
call(func, ...args): Effect
Creates an Effect that when performed should call func
with args
as arguments. When Effect yield
ed, if func
is a normal function, coroutine is suspended until the Promise returned by func
fulfilled. If func
is an effect coroutine then execution waits until coroutine returns.
func: Function = () => {Promise<any> | any} | GeneratorFunction
- function or effect coroutineargs: Array
- arguments to callfunc
with
fork(func, ...args): Effect
Creates an Effect that when performed should call func
with args
as arguments. Unlike call
doesn't suspend the execution flow, but instantly returns a Task object, that could further be used with join
effect creator to get the result of function call.
func: Function = () => {Promise} | GeneratorFunction
- function or effect coroutineargs: Array
- arguments to callfunc
with
join(task): Effect
Creates an Effect that when performed suspends the execution flow until previously forked task
is finished.
task
- object returned from a previousfork
call
put(action): Effect
Creates an Effect that when performed dispatches the action
with redux's store.dispatch
method.
action: Action
- action to dispatch
Custom effect creators
It is possible to create your own custom effect creators. Let's learn how to make it by example
// ======================== log.js ==========================// In order to create a custom effect creator you need to define three things: // 1. string constant, that represents the type of the Effectconst TYPE = '__YIELD_EFFECT_LOG__'; // 2. effect creator - function that returns Effect description object { return type: TYPE payload: message: message ;} // 3. effect processor - function that knows how to process certain Effect.// It should always return Promise { const message = effectpayloadmessage; return Promise;} // ======================= main.js ==========================;;; // Now we should let middleware know how to handle our Effect:const yieldEffectMiddleware = ; const store = ; store;