Skip to content

Instantly share code, notes, and snippets.

@mansbernhardt
Last active May 4, 2018 08:29
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mansbernhardt/a410b3de02e618cb5c6a76cbb069be56 to your computer and use it in GitHub Desktop.
Save mansbernhardt/a410b3de02e618cb5c6a76cbb069be56 to your computer and use it in GitHub Desktop.
import Foundation
import PlaygroundSupport
enum Result<Value> {
case success(Value)
case failure(Error)
}
func data(at url: URL, completion: @escaping (Result<Data>) -> Void) {
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
print("done")
completion(.success(Data()))
}
}
struct Jar {
let json: Data
init(json: Data) throws {
self.json = json
}
}
extension Result {
init(getValue: () throws -> Value) {
do {
self = .success(try getValue())
} catch {
self = .failure(error)
}
}
func getValue() throws -> Value {
switch self {
case .success(let value): return value
case .failure(let error): throw error
}
}
}
extension Result {
func map<O>(_ transform: (Value) throws -> O) -> Result<O> {
return Result<O> { try transform(getValue()) }
}
}
func json(at url: URL, completion: @escaping (Result<Jar>) -> Void) {
data(at: url) { result in
completion(result.map { try Jar(json: $0) })
}
}
#if false // First implentation attempt
struct Future<Value> {
let onResult: (@escaping (Result<Value>) -> Void) -> Void
}
#elseif false // Second implentation attempt
final class Future<Value> {
var completions: [(Result<Value>) -> Void] = []
init(onResult: @escaping (@escaping (Result<Value>) -> Void) -> Void) {
onResult { result in
self.completions.forEach { $0(result) }
}
}
func onResult(_ completion: @escaping (Result<Value>) -> Void) {
completions.append(completion)
}
}
Future<Int> { c in c(.success(1)) }.onResult { result in
// Will never be called as `c` was called before `onResult` was called.
}
#else // Third an final implentation attempt
final class Future<Value> {
enum State {
case completed(Result<Value>)
case pending([(Result<Value>) -> Void])
}
var state: State
init(onResult: @escaping (@escaping (Result<Value>) -> Void) -> Void) {
state = .pending([])
onResult { result in
guard case .pending(let completions) = self.state else { return }
completions.forEach { $0(result) }
self.state = .completed(result)
}
}
func onResult(_ completion: @escaping (Result<Value>) -> Void) {
switch self.state {
case .completed(let result):
completion(result)
case .pending(var completions):
completions.append(completion)
self.state = .pending(completions)
}
}
}
#endif
func data(at url: URL) -> Future<Data> {
return Future { completion in
data(at: url, completion: completion)
}
}
extension Future {
func map<O>(_ transform: @escaping (Value) throws -> O) -> Future<O> {
return Future<O> { completion in
self.onResult { result in
completion(result.map(transform))
}
}
}
}
func json(at url: URL) -> Future<Jar> {
return data(at: url).map { try Jar(json: $0) }
}
extension Future {
func flatMap<O>(_ transform: @escaping (Value) throws -> Future<O>) -> Future<O> {
return Future<O> { completion in
self.onResult { result in
do {
try transform(result.getValue()).onResult(completion)
} catch {
completion(.failure(error))
}
}
}
}
}
extension Future {
func onValue(_ callback: @escaping (Value) throws -> Void) -> Future {
return map { value in
try callback(value)
return value
}
}
}
print("Start request")
let url = URL(string: "https://www.izettle.com")!
json(at: url).flatMap { jar in
json(at: url)
}.map { jar in
jar
}.onValue { friends in
print("End Request")
}.onResult { _ in
// onResult needed by basic impl.
}
PlaygroundPage.current.needsIndefiniteExecution = true
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment