EffectMiddleware
@available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *)
public final class EffectMiddleware<InputActionType, OutputActionType, StateType, Dependencies> : MiddlewareProtocol
extension EffectMiddleware: Semigroup
extension EffectMiddleware: Monoid where Dependencies == Void
Easiest way to implement a Middleware
, with a single function that gives you all you need, and from which you can return an Effect
.
A MiddlewareEffect
is a function providing an incoming Action
, State
and Context
(dispatcher source, dependencies, cancellation closure)
and expecting as result one or multiple effects that will eventually result in outgoing actions.
An Effect
is a publisher or observable type according to your reactive framework. It can be a one-shot effect, such as an HTTP request,
an observation that gives back multiple values over time, such as CoreLocation or NotificationCenter, a Timer, a pure value already known
or even an empty effect using the .doNothing
constructor, if there’s no need for side-effects.
Effect
cannot fail, and its element/output/value type is the OutputAction
generic of this middleware. The purpose is running tasks,
creating actions as they respond and returning these actions over time back to the Store. When the Effect completes, it should send a
completion event so the middleware will cleanup the resources.
Cancellation: effects can optionally provide a cancellation token, which can be any hashable value. If another effect arrives in the same
middleware instance having the very same cancellation token, the previous effect will be cancelled and replaced by the new one. This is
useful in case you want to keep only the last request of certain kind running, but cancel any previous ongoing request when a new is
dispatched. You can also explicitly cancel one or many effects at any point. For that, you will be given a toCancel
closure during every
action arrival within the Context
(third parameter). Feel free to call cancellation at that point or even later, if you hold this toCancel
closure.
Examples
Using Promises
let someMiddleware = EffectMiddleware<ApiRequestAction, ApiResponseAction, SomeState, Void>.onAction { action, state, context in
switch action {
case .users:
return .promise { completion in
DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
completion(ApiResponseAction.someResponse("42"))
}
}
case .somethingIDontCare:
return .doNothing
}
}
From Publisher
typealias ApiFetchMiddlewareDependencies = (session: @escaping () -> URLSession, decoder: @escaping () -> JSONDecoder)
let apiFetchMiddleware =
EffectMiddleware<ApiRequestAction, ApiResponseAction, SomeState, ApiFetchMiddlewareDependencies>.onAction { action, state, context in
switch action {
case .users:
return context.dependencies.urlSession
.dataTaskPublisher(for: fetchAllUsersURL())
.map { data, _ in data }
.decode(type: [User].self, decoder: context.dependencies)
.map { users in ApiResponseAction.gotUserList(users) }
.replaceError(with: ApiResponseAction.errorRetrivingUserList)
.asEffect
case .user(id: UUID):
// ..
case .somethingIDontCare:
return .doNothing
}
}.inject((session: { URLSession.shared }, decoder: JSONDecoder.init))
Cancellation
typealias ApiFetchMiddlewareDependencies = (session: @escaping () -> URLSession, decoder: @escaping () -> JSONDecoder)
let apiFetchMiddleware =
EffectMiddleware<ApiRequestAction, ApiResponseAction, SomeState, ApiFetchMiddlewareDependencies>.onAction { action, state, context in
switch action {
case let .userPicture(userId):
return context.dependencies.urlSession
.dataTaskPublisher(for: fetchPicture())
.map { data, _ in ApiResponseAction.gotUserPicture(id: userId, data: data) }
.replaceError(with: ApiResponseAction.errorRetrivingUserPicture(id: userId))
.asEffect(cancellationToken: "image-for-user-\(userId)") // this will automatically cancel any pending download for the same image
// using the URL would also be possible
case let .cancelImageDownload(userId):
return context.toCancel("image-for-user-\(userId)") // alternatively you can explicitly cancel tasks by token
}
}.inject((session: { URLSession.shared }, decoder: JSONDecoder.init))
-
Declaration
Swift
public func handle(action: InputActionType, from dispatcher: ActionSource, state: @escaping GetState<StateType>) -> IO<OutputActionType>
-
Undocumented
Declaration
Swift
public func lift<GlobalInputActionType, GlobalOutputActionType, GlobalStateType>( inputAction inputActionMap: @escaping (GlobalInputActionType) -> InputActionType?, outputAction outputActionMap: @escaping (OutputActionType) -> GlobalOutputActionType, state stateMap: @escaping (GlobalStateType) -> StateType ) -> EffectMiddleware<GlobalInputActionType, GlobalOutputActionType, GlobalStateType, Dependencies>
-
Undocumented
Declaration
Swift
public func lift<GlobalInputActionType, GlobalOutputActionType>( inputAction inputActionMap: @escaping (GlobalInputActionType) -> InputActionType?, outputAction outputActionMap: @escaping (OutputActionType) -> GlobalOutputActionType ) -> EffectMiddleware<GlobalInputActionType, GlobalOutputActionType, StateType, Dependencies>
-
Undocumented
Declaration
Swift
public func lift<GlobalInputActionType, GlobalStateType>( inputAction inputActionMap: @escaping (GlobalInputActionType) -> InputActionType?, state stateMap: @escaping (GlobalStateType) -> StateType ) -> EffectMiddleware<GlobalInputActionType, OutputActionType, GlobalStateType, Dependencies>
-
Undocumented
Declaration
Swift
public func lift<GlobalOutputActionType, GlobalStateType>( outputAction outputActionMap: @escaping (OutputActionType) -> GlobalOutputActionType, state stateMap: @escaping (GlobalStateType) -> StateType ) -> EffectMiddleware<InputActionType, GlobalOutputActionType, GlobalStateType, Dependencies>
-
Undocumented
Declaration
Swift
public func lift<GlobalInputActionType>( inputAction inputActionMap: @escaping (GlobalInputActionType) -> InputActionType? ) -> EffectMiddleware<GlobalInputActionType, OutputActionType, StateType, Dependencies>
-
Undocumented
Declaration
Swift
public func lift<GlobalOutputActionType>( outputAction outputActionMap: @escaping (OutputActionType) -> GlobalOutputActionType ) -> EffectMiddleware<InputActionType, GlobalOutputActionType, StateType, Dependencies>
-
Undocumented
Declaration
Swift
public func lift<GlobalStateType>( state stateMap: @escaping (GlobalStateType) -> StateType ) -> EffectMiddleware<InputActionType, OutputActionType, GlobalStateType, Dependencies>
-
Undocumented
Declaration
Swift
public static func onAction( do onAction: @escaping (InputActionType, ActionSource, @escaping GetState<StateType>) -> Effect<Dependencies, OutputActionType> ) -> MiddlewareReader<Dependencies, EffectMiddleware>
-
Declaration
Swift
public static func <> (lhs: EffectMiddleware, rhs: EffectMiddleware) -> EffectMiddleware
-
Undocumented
Declaration
Swift
public func lift<GlobalActionType, GlobalStateType>( action actionMap: WritableKeyPath<GlobalActionType, InputActionType?>, state stateMap: KeyPath<GlobalStateType, StateType> ) -> EffectMiddleware<GlobalActionType, GlobalActionType, GlobalStateType, Dependencies>
-
Undocumented
Declaration
Swift
public func liftToCollection<GlobalAction, GlobalState, CollectionState: MutableCollection>( inputAction actionMap: @escaping (GlobalAction) -> ElementIDAction<StateType.ID, InputActionType>?, outputAction outputMap: @escaping (ElementIDAction<StateType.ID, OutputActionType>) -> GlobalAction, stateCollection: @escaping (GlobalState) -> CollectionState ) -> EffectMiddleware<GlobalAction, GlobalAction, GlobalState, Dependencies> where CollectionState.Element == StateType
-
Undocumented
Declaration
Swift
public func liftToCollection<GlobalAction, GlobalState, CollectionState: MutableCollection>( action actionMap: WritableKeyPath<GlobalAction, ElementIDAction<StateType.ID, InputActionType>?>, stateCollection: KeyPath<GlobalState, CollectionState> ) -> EffectMiddleware<GlobalAction, GlobalAction, GlobalState, Dependencies> where CollectionState.Element == StateType
-
Undocumented
Declaration
Swift
public static func onAction( do onAction: @escaping (InputActionType, ActionSource, @escaping GetState<StateType>) -> Effect<Dependencies, OutputActionType> ) -> EffectMiddleware<InputActionType, OutputActionType, StateType, Dependencies>
-
Declaration
Swift
public static var identity: EffectMiddleware<InputActionType, OutputActionType, StateType, Dependencies> { get }