ReduxSwiftSaga is a side effect model for ReduxSwift apps. It is heavily inspired by redux-saga and tries to be for ReduxSwift what redux-saga is for Redux.
This framework is probably not suited for production and is intended as a proof of concept.
- Asynchronous Flow
- Generator-like Syntax
- Declarative Side Effects
- Type Safe
- Concurrent and Parallel
- Experimental!
"The mental model is that a saga is like a separate thread in your application that's solely responsible for side effects." redux-sagas's documentation
An effect is an instance that describes an action without actually running it. Effects are meant to be run by the middleware when a saga tells it so. This framework includes 4 types of basic effects: Put, Take, Select and Call. Let's get to know them better.
This effect takes a redux action as his single parameter. When ran, it dispatches it to the Store.
_ = Put.init(SOME_ACTION())
This effect is generic and depends on a redux action type. When ran, it waits for an action with such type to be dispatched and saves it.
let t = Take<WAITED_ACION>()
t.action // action instance saved
This effect takes a mapper function of generic types. When ran, it maps the Store state through the mapper and stores the result.
let s = Select<State, Int> {$0.someCounter}
s.selection // result as an Int
This effect takes a promise-style function that should call it's first argument with Either a result or an error. The result and error types are generic.
let c = Call<String, String> {done
if problem {
done(Either<String,String>.Left("problem :("))
} else {
done(Either<String,String>.Right("no problem :)"))
}
}
c.result // Either
A Saga represents a single side-effects generator that runs concurrently with your application. It's only function is to tell which side-effect to run next.
This framework provides a Saga class that is meant to be overridden. The start method is where all your logic should go. The yield method is how you tell you want some effect to be run. When ran, it blocks the saga until the effect is done running.
This saga waits for an action of type UserFetchRequested to be dispatched to the Store. Then is calls some function to fetch the users data. At last, it dispatches a UserFetchSucceeded or a UserFetchFailed action to the Store depending on the call result.
class FetchUser: Saga {
typealias E = Either<User, String>
override func start() {
let take = yield(Take<UserFetchRequested>())
let call = yield(Call<User, String>{done in
if let user = fetch(take.action.id) {
done(E.Right(user))
} else {
done(E.Left("sorry :("))
}
})
switch call.result! {
case .Right(let user):
yield(Put(UserFetchSucceeded(user: user)))
case .Left(let error):
yield(Put(UserFetchFailed(error: error)))
default: break
}
}
}
let sagas = Sagas<State>.init()
let store = Store<State>.init(reducer: reducer, state: nil, middlewares: [sagas.middleware])
sagas.run(FetchUser.init()) // runs your saga in another thread
sagas.start() // start running effects from all your sagas
github "lsunsi/ReduxSwiftSaga"
This project is licensed under the MIT License - see the LICENSE file for details