GCD pattern for chaining async operations while pi

2019-04-09 00:04发布

Coming from JavaScript world using async javascript promises, and I believe the same is done using GCD async queues in Swift.

Can you point me to an example where 2 to 3 async functions are specified in a queue, with one async operation feeding the result to the second, and second feeding the result to the third (commonly known as piping the results) and then finally a result and error handler.

All functions restrict to a single argument by design.

If any error let's say during function 2, function 3 is skipped and error is passed straight down to the error handler.

Looking for a native solution instead of any 3rd party Promise or Monad library.

Appreciate swift 3.0 code.

Edit. Also learned from the example that steps are more like a manual linear path in GCD where developer is the one feeding result to the next and checking error each time, Any Functional Programming possible using Function Compositions?

I want to avoid Pyramid of Doom and looking for a linear async programming.

2条回答
冷血范
2楼-- · 2019-04-09 00:48

There is no support for that in the standard library and there probably won't be for a while. There are great third-party libraries, but if you don't want those, you could define a minimum yourself (I'm using Swift 3 here):

enum Result<R> {
    case Success(R)
    case Failure(ErrorProtocol)
}

typealias Async<A, B> = (a: A, handler: (Result<B>) -> Void) -> Void

infix operator • {
    associativity right
    precedence 190
}

func •<A, B, C>(f: Async<A, B>, g: Async<B, C>) -> Async<A, C> {
    return { a, handler in
        f(a: a, handler: { result in
            switch result {
            case .Success(let b): g(a: b, handler: handler)
            case .Failure(let e): handler(.Failure(e))
            }
        })
    }
}

Usage example:

func f(n: Int, h: (Result<String>) -> ()) {
    h(.Success(n.description))
}

func g(s: String, h: (Result<Int>) -> ()) {
    h(.Success(s.characters.count))
}

let chained = f • g

chained(a: 10) { result in
    switch result {
    case .Success(let r): print("Success: \(r)")
    case .Failure(let e): print("Error: \(e)")
    }
}

Advanced usage example:

enum Error : ErrorProtocol {
    case NoResult
    case StringDecoding
}

extension URLSession {
    func getData(with url: URL, completionHandler: (Result<(Data, URLResponse)>) -> Void) {
        let task = dataTask(with: url) { (data, response, error) in
            if let error = error {
                completionHandler(.Failure(error))
            } else if let data = data, response = response {
                completionHandler(.Success((data, response)))
            } else {
                completionHandler(.Failure(Error.NoResult))
            }
        }

        task.resume()
    }
}

func decode(d: (Data, URLResponse), handler: (Result<String>) -> Void) {
    DispatchQueue(label: "async").async{
        if let string = String(data: d.0, encoding: .utf8) {
            handler(.Success(string))
        } else {
            handler(.Failure(Error.StringDecoding))
        }
    }
}

let getString = URLSession.shared().getData • decode


getString(a: URL(string: "https://www.reddit.com")!) { result in
    switch result {
    case .Success(let string): print(string)
    case .Failure(let e): print(e)
    }
}
查看更多
虎瘦雄心在
3楼-- · 2019-04-09 00:55

I've reached out to Apple with a support request and they referred me to this WWDC15 video Advance NSOperations.

I didn't get to see how to pipe results from one sub operation to the other, now based on this video if someone can write some comprehensive example code where one routine after executing passing results to the next, I'd accept the answer.

查看更多
登录 后发表回答