ReactKit / SwiftTask

Promise + progress + pause + cancel + retry for Swift.

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

implement zip function

DanielAsher opened this issue · comments

would be great to have a zip function that takes two tasks with different progress, value and error types and returns a task that returns a tuple of values.

I've implemented the function with the signature

public class func zip<P1, V1, R1, P2, V2, R2>(task1: Task<P1, V1, R1>, _ task2: Task<P2, V2, R2>) -> Task<(P1, P2), (V1, V2), (R1?, R2?)>

but unfortunately I'm unable to call it without the error:

Cannot invoke 'zip' with an argument list of type '(Task<Void, String, String>, Task<Void, String, String>)'

I posted a question documenting my problem and solution. I'd be glad to submit a pull request if it was deemed useful, though I have not tested (or thought too deeply) about the solution.

Hi @DanielAsher, thanks for your proposal on new zip feature.

At a first glance, I found your method interface is a bit too complicated
just because SwiftTask has to deal with 3 generic types <P, V, E> for each task.

To alleviate this issue, I have implemented zip-like method called Task.all(),
which can be used to combine any number of tasks by using array as Task.Value type
rather than using tuple.
Though not strictly typed, this method will hopefully solve some scenarios.

There are, however, some problems with this approach too, that it has to use the same <P, V, E> type for all tasks.
That means, there needs some sort of type conversion to zip different types of tasks into one,
e.g. Task<P, [Any], E>.

I will look into this issue more carefully...

Hi @inamiy ,

thank you once again for a wonderful framework. I figured out the problem with my implementation. please follow the link above to see the answer. As a result I implemented zip as a member function. This also simplifies the solution significantly. An example call site is:

listener?.zip(praise).success { (recognition, utterance) in
    self.machine?.handleEvent(.Complete)
}

Once again I have not fully implemented and tested the solution, but it is working within my project. Enabling progress handling needs to be thought about too. But I'd be happy to issue a pull request if you feel it is appropriate. The current code is below.

best wishes,

Daniel

    func zip<Progress2, Value2, Error2>(task2: Task<Progress2, Value2, Error2>) -> Task<(Progress, Progress2), (Value, Value2), (Error?, Error2?)> {

        return Task<(Progress, Progress2), (Value, Value2), (Error?, Error2?)> { progress, fulfill, reject, configure in

            var completedCount = 0
            var rejectedCount = 0
            let totalCount = 2

            let cancelAll : Void -> Void = {
                self.cancel()
                task2.cancel() 
            }

            let pauseAll : Void -> Void = {
                self.pause()
                task2.pause() 
            }

            let resumeAll : Void -> Void = {
                self.resume()
                task2.resume() 
            }

            self.success { (value: Value) -> Void in

                synchronized(self) 
                {
                    completedCount++

                    if completedCount == totalCount 
                    {
                        fulfill( (self.value!, task2.value!) )
                    }
                }
            }.failure { (error, isCancelled) -> Void in

                synchronized(self) 
                {
                    reject( error, nil )
                    cancelAll() 
                }
            }

            task2.success { (value: Value2) -> Void in

                synchronized(self) 
                {
                    completedCount++

                    if completedCount == totalCount 
                    {
                        fulfill( (self.value!, task2.value!) )
                    }
                }
            }.failure { (error, isCancelled) -> Void in

                synchronized(self) 
                {
                    reject( nil, error )
                    cancelAll() 
                }
            }

            configure.pause = { pauseAll() }
            configure.resume = { resumeAll() }
            configure.cancel = { cancelAll() }

        }.name("\(self.name) zipped with \(task2.name)")
    }
}

I roughly sketched zip() function in SwiftTask.swift#L669-L699 (feature/zip branch) using Task.all() method, also handling progress values properly.

I think this works pretty well, but there also needs some refactoring in Task.all() because current BulkProgress is not useful for zipping.
Let me take some more time to improve this.

Thanks for this. I also need a little time to figure this one out. Conversion to Task<Any, Any, Any>.all([t1, t2]) certainly changes the syntax. I'd be interested to understand if it changes the semantics or performance. I'll pull and check out the new Task.zip!

Thanks for this!!!