Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Using await/async in middleware #131

Open
armoona opened this issue May 1, 2023 · 4 comments
Open

Using await/async in middleware #131

armoona opened this issue May 1, 2023 · 4 comments

Comments

@armoona
Copy link

armoona commented May 1, 2023

I am trying to use await/async in middleware. For example, rewriting the sample from the docs, is this correct?

public final class FavoritesAPIMiddleware: MiddlewareProtocol {
    public typealias InputActionType = FavoritesAction  // It wants to receive only actions related to Favorites
    public typealias OutputActionType = FavoritesAction // It wants to also dispatch actions related to Favorites
    public typealias StateType = FavoritesModel         // It wants to read the app state that manages favorites

    private let api: API

    public init(api: API) {
        self.api = api
    }

    public func handle(action: InputActionType, from dispatcher: ActionSource, state: @escaping GetState<StateType>) -> IO<OutputActionType> {
        guard case let .toggleFavorite(movieId) = action else { return .pure() }
        let favoritesList = state() // state before reducer
        let makeFavorite = !favoritesList.contains(where: { $0.id == movieId })

        return IO { [weak self] output in
            guard let self = self else { return }

            ///////// START ASYNC CODE
            Task.init {
                do {
                    let result = try await self.api.changeFavorite(id: movieId, makeFavorite: makeFavorite)
                    switch result {
                    case let .success(value):
                        output.dispatch(.changedFavorite(movieId, isFavorite: makeFavorite), info: "API.changeFavorite callback")
                    case let .failure(error):
                        output.dispatch(.changedFavoriteHasFailed(movieId, isFavorite: !makeFavorite, error: error), info: "api.changeFavorite callback")
                    }
                } catch {
                    // .. handle error
                }
            }
            ///////// END ASYNC CODE
        }
    }
}
@luizmb
Copy link
Member

luizmb commented May 9, 2023

Hi @armoona,

First of all, I'm sorry for the late reply! Been busy lately and couldn't monitor Github as much as I wish in the past days.

I believe your example code is mostly correct, I am planning to convert IO into a cancellable async context so you could skip the Task block yourself, similar to what is done in the EffectMiddleware where Effect holds a publisher and a cancellation token. This is important in case you want to cancel your task, or in case you want to use either Task (one shot) or AsyncStream (continuous side-effect).

That said, if you want to implement ad-hoc the same solution, I believe your code should work, and you may consider storing the task in the middleware for future cancellation. You can also consider making the API changeFavorite either returning async Result, or async throws, but not both (so you don't have to catch twice, in the switch result and in the do/catch). I'm thinking if the Task.detached should be better but I believe it doesn't make any difference because this is not be part of any parent context.

Please let me know if this works well, I am still rewriting the Middleware base class to accommodate all the changes I wish to do (such as better SPM dependency chain) and the async/await Middleware is not yet in development so I don't know all the challenges in implementing it yet.

If you wanna contribute with the AsyncMiddleware, a good place to start is understanding the EffectMiddleware, the solution should be very similar but the Effect would receive the closure (Context) -> async DispatchedAction<OutputAction> instead of (Context) -> Publisher<DispatchedAction<OutputAction>, Never> (and the same for AsyncStream).

@wangpeiyan
Copy link

Any progress about this?

@luizmb
Copy link
Member

luizmb commented Jul 7, 2023

Hi @wangpeiyan,

I'm currently traveling and with no access to my Macbook. I'll return working on these topics somewhere in August, but before that I believe my comment should already point you to the right direction.

If you open a PR with this new middleware, I'll be glad to approve it and release a version. It shouldn't be too hard, it only needs a good amount of unit tests.

@wangpeiyan
Copy link

@luizmb thank you for your reply, i'm new of the redux concept and still leaning it。But i can clearly understand your comment, so i will try to implement a local version

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants