Created
January 6, 2019 15:30
-
-
Save felginep/039ca3b21e4f0cabb1c06126d9164680 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import UIKit | |
import Foundation | |
func test(named: String, | |
_ work: (_ assert: @escaping (Bool) -> Void, _ done: @escaping () -> Void) -> Void) { | |
var testPass = true | |
var assertCount = 0 | |
let assert: (Bool) -> Void = { value in | |
assertCount = assertCount + 1 | |
testPass = testPass && value | |
} | |
let done: () -> Void = { | |
if testPass { | |
print("• Test \(named) passed (\(assertCount) assertions)") | |
} else { | |
print("× Test \(named) failed (\(assertCount) assertions)") | |
} | |
} | |
work(assert, done) | |
} | |
func after(_ timeInterval: TimeInterval = 0.1, work: @escaping () -> Void) { | |
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + timeInterval, execute: work) | |
} | |
//: ## Promise | |
//func asyncWork(completion: (String) -> Void) { | |
// // ... | |
// completion("test") | |
//} | |
// | |
//asyncWork { value in | |
// print(value) | |
//} | |
// | |
//let asyncPromise = Promise<String> { resolve in | |
// // ... | |
// resolve("test") | |
//} | |
// | |
//asyncPromise.then { value in | |
// print(value) | |
//} | |
class Promise<Value> { | |
enum State<T> { | |
case pending | |
case resolved(T) | |
} | |
private var state: State<Value> = .pending | |
private var callbacks: [(Value) -> Void] = [] | |
init(executor: (_ resolve: @escaping (Value) -> Void) -> Void) { | |
executor(resolve) | |
} | |
// observe | |
public func then(_ onResolved: @escaping (Value) -> Void) { | |
callbacks.append(onResolved) | |
triggerCallbacksIfResolved() | |
} | |
// flatMap | |
public func then<NewValue>(_ onResolved: @escaping (Value) -> Promise<NewValue>) -> Promise<NewValue> { | |
return Promise<NewValue> { resolve in | |
then { value in | |
onResolved(value).then(resolve) | |
} | |
} | |
} | |
// map | |
public func then<NewValue>(_ onResolved: @escaping (Value) -> NewValue) -> Promise<NewValue> { | |
return then { value in | |
return Promise<NewValue> { resolve in | |
resolve(onResolved(value)) | |
} | |
} | |
} | |
private func resolve(value: Value) { | |
updateState(to: .resolved(value)) | |
} | |
private func updateState(to newState: State<Value>) { | |
guard case .pending = state else { return } | |
state = newState | |
triggerCallbacksIfResolved() | |
} | |
private func triggerCallbacksIfResolved() { | |
guard case let .resolved(value) = state else { return } | |
callbacks.forEach { callback in | |
callback(value) | |
} | |
callbacks.removeAll() | |
} | |
} | |
//: ## Tests | |
//test(named: "0. Executor function is called immediately") { assert, done in | |
// var string: String = "" | |
// _ = Promise { string = "foo" } | |
// assert(string == "foo") | |
// done() | |
//} | |
test(named: "1.1 Resolution handler is called when promise is resolved sync") { assert, done in | |
let string: String = "foo" | |
let promise = Promise<String> { resolve in | |
resolve(string) | |
} | |
promise.then { (value: String) in | |
assert(string == value) | |
done() | |
} | |
} | |
test(named: "1.2 Resolution handler is called when promise is resolved async") { assert, done in | |
let string: String = "foo" | |
let promise = Promise<String> { resolve in | |
after(0.1) { | |
resolve(string) | |
} | |
} | |
promise.then { (value: String) in | |
assert(string == value) | |
done() | |
} | |
} | |
test(named: "2.1 Promise supports many resolution handlers sync") { assert, done in | |
let string: String = "foo" | |
let promise = Promise<String> { resolve in | |
resolve(string) | |
} | |
promise.then { value in | |
assert(string == value) | |
} | |
promise.then { value in | |
assert(string == value) | |
done() | |
} | |
} | |
test(named: "2.2 Promise supports many resolution handlers async") { assert, done in | |
let string: String = "foo" | |
let promise = Promise<String> { resolve in | |
after(0.1) { | |
resolve(string) | |
} | |
} | |
promise.then { value in | |
assert(string == value) | |
} | |
promise.then { value in | |
assert(string == value) | |
done() | |
} | |
} | |
test(named: "3. Resolution handlers can be chained") { assert, done in | |
let string: String = "foo" | |
let promise = Promise<String> { resolve in | |
after(0.1) { | |
resolve(string) | |
} | |
} | |
promise | |
.then { value in | |
return Promise<String> { resolve in | |
after(0.1) { | |
resolve(value + value) | |
} | |
} | |
} | |
.then { value in | |
assert(string + string == value) | |
done() | |
} | |
} | |
test(named: "4. Chaining works with non promise return values") { assert, done in | |
let string: String = "foo" | |
let promise = Promise<String> { resolve in | |
after(0.1) { | |
resolve(string) | |
} | |
} | |
promise | |
.then { value -> String in | |
return value + value | |
} | |
.then { value in | |
assert(string + string == value) | |
done() | |
} | |
} | |
//: ## Example | |
struct User { | |
let id: Int | |
let name: String | |
} | |
func fetchIds() -> Promise<[Int]> { | |
return Promise { resolve in | |
resolve([0]) | |
} | |
} | |
func fetchUser(id: Int) -> Promise<User> { | |
return Promise { resolve in | |
after(0.1) { | |
resolve(User(id: id, name: "Bob")) | |
} | |
} | |
} | |
fetchIds() | |
.then { ids in // flatMap | |
return fetchUser(id: ids[0]) | |
} | |
.then { user in // map | |
return user.name | |
} | |
.then { name in // observe | |
print(name) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment