Classes

The following classes are available globally.

  • 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"))
          }
          return AnyDisposable() // Or a way to cancel the ongoing task
        }
      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))
    
    See more

    Declaration

    Swift

    public final class EffectMiddleware<InputActionType, OutputActionType, StateType, Dependencies> : MiddlewareProtocol
    extension EffectMiddleware: Semigroup
    extension EffectMiddleware: Monoid where Dependencies == Void