From bfb3ea190b57972a8fc76f83b50d7f784f6ca81f Mon Sep 17 00:00:00 2001 From: Vadym Markov Date: Sat, 27 May 2017 16:43:13 +0200 Subject: [PATCH 1/2] Implement promise recover --- Sources/When/Promise.swift | 88 +++++++++++++++++------- WhenTests/Shared/Helpers/SpecError.swift | 1 + WhenTests/Shared/PromiseSpec.swift | 86 +++++++++++++++++++++++ 3 files changed, 152 insertions(+), 23 deletions(-) diff --git a/Sources/When/Promise.swift b/Sources/When/Promise.swift index f27a92a..1d7e12f 100644 --- a/Sources/When/Promise.swift +++ b/Sources/When/Promise.swift @@ -123,7 +123,7 @@ open class Promise { // MARK: - Helpers - private func update(state: State?) { + fileprivate func update(state: State?) { dispatch(queue) { guard let state = state, let result = state.result else { return @@ -156,25 +156,6 @@ open class Promise { observer = nil } - fileprivate func addObserver(on queue: DispatchQueue, promise: Promise, _ body: @escaping (T) throws -> U?) { - observer = Observer(queue: queue) { result in - switch result { - case let .success(value): - do { - if let result = try body(value) { - promise.resolve(result) - } - } catch { - promise.reject(error) - } - case let .failure(error): - promise.reject(error) - } - } - - update(state: state) - } - private func dispatch(_ queue: DispatchQueue, closure: @escaping () -> Void) { if queue === instantQueue { closure() @@ -190,13 +171,11 @@ extension Promise { public func then(on queue: DispatchQueue = mainQueue, _ body: @escaping (T) throws -> U) -> Promise { let promise = Promise(queue: queue) addObserver(on: queue, promise: promise, body) - return promise } public func then(on queue: DispatchQueue = mainQueue, _ body: @escaping (T) throws -> Promise) -> Promise { let promise = Promise(queue: queue) - addObserver(on: queue, promise: promise) { value -> U? in let nextPromise = try body(value) nextPromise.addObserver(on: queue, promise: promise) { value -> U? in @@ -205,7 +184,6 @@ extension Promise { return nil } - return promise } @@ -217,7 +195,71 @@ extension Promise { return then(on: backgroundQueue, body) } + fileprivate func addObserver(on queue: DispatchQueue, promise: Promise, _ body: @escaping (T) throws -> U?) { + observer = Observer(queue: queue) { result in + switch result { + case let .success(value): + do { + if let result = try body(value) { + promise.resolve(result) + } + } catch { + promise.reject(error) + } + case let .failure(error): + promise.reject(error) + } + } + + update(state: state) + } + func asVoid() -> Promise { return then(on: instantQueue) { _ in return } } } + +// MARK: - Recover + +extension Promise { + public func recover(on queue: DispatchQueue = mainQueue, + _ body: @escaping (Error) throws -> T) -> Promise { + let promise = Promise(queue: queue) + addRecoverObserver(on: queue, promise: promise, body) + return promise + } + + public func recover(on queue: DispatchQueue = mainQueue, + _ body: @escaping (Error) throws -> Promise) -> Promise { + let promise = Promise(queue: queue) + addRecoverObserver(on: queue, promise: promise) { error -> T? in + let nextPromise = try body(error) + nextPromise.addObserver(on: queue, promise: promise) { value -> T? in + return value + } + + return nil + } + return promise + } + + private func addRecoverObserver(on queue: DispatchQueue, promise: Promise, + _ body: @escaping (Error) throws -> T?) { + observer = Observer(queue: queue) { result in + switch result { + case let .success(value): + promise.resolve(value) + case let .failure(error): + do { + if let result = try body(error) { + promise.resolve(result) + } + } catch { + promise.reject(error) + } + } + } + + update(state: state) + } +} diff --git a/WhenTests/Shared/Helpers/SpecError.swift b/WhenTests/Shared/Helpers/SpecError.swift index 84901a8..53df8b6 100644 --- a/WhenTests/Shared/Helpers/SpecError.swift +++ b/WhenTests/Shared/Helpers/SpecError.swift @@ -2,4 +2,5 @@ import Foundation enum SpecError: Error { case notFound + case rejected } diff --git a/WhenTests/Shared/PromiseSpec.swift b/WhenTests/Shared/PromiseSpec.swift index 346b17f..107e8b9 100644 --- a/WhenTests/Shared/PromiseSpec.swift +++ b/WhenTests/Shared/PromiseSpec.swift @@ -366,6 +366,92 @@ class PromiseSpec: QuickSpec { } } } + + describe("#recover") { + beforeEach { + promise = Promise() + } + + context("with a body that transforms result") { + context("with a body throws an error") { + it("rejects the promise") { + let failExpectation = self.expectation(description: "Then fail expectation") + + promise + .recover({ error -> String in + throw SpecError.notFound + }) + .fail({ error in + expect(error is SpecError).to(beTrue()) + failExpectation.fulfill() + }) + + promise.reject(SpecError.rejected) + self.waitForExpectations(timeout: 2.0, handler:nil) + } + } + + context("with a body that returns a value") { + it("resolves the promise") { + let doneExpectation = self.expectation(description: "Then done expectation") + + promise + .recover({ error in + return "Recovered" + }) + .done({ value in + expect(value).to(equal("Recovered")) + doneExpectation.fulfill() + }) + + promise.reject(SpecError.rejected) + self.waitForExpectations(timeout: 2.0, handler:nil) + } + } + } + + context("with a body that returns a new promise") { + context("with a rejected promise") { + it("rejects the promise") { + let failExpectation = self.expectation(description: "Then fail expectation") + + promise + .recover({ error in + return Promise({ + throw SpecError.notFound + }) + }) + .fail({ error in + expect(error is SpecError).to(beTrue()) + failExpectation.fulfill() + }) + + promise.reject(SpecError.rejected) + self.waitForExpectations(timeout: 2.0, handler:nil) + } + } + + context("with a body that returns a value") { + it("with a resolved promise") { + let doneExpectation = self.expectation(description: "Then done expectation") + + promise + .recover({ error -> Promise in + return Promise({ + return "Recovered" + }) + }) + .done({ value in + expect(value).to(equal("Recovered")) + doneExpectation.fulfill() + }) + + promise.reject(SpecError.rejected) + self.waitForExpectations(timeout: 2.0, handler:nil) + } + } + } + } } } } From d03c35f4e1f5995bff4bcddf1f6c496f9879329b Mon Sep 17 00:00:00 2001 From: Vadym Markov Date: Sun, 28 May 2017 16:03:13 +0200 Subject: [PATCH 2/2] Update readme --- README.md | 21 +++++++++++++++++++++ Sources/When/Promise.swift | 9 +++++++++ 2 files changed, 30 insertions(+) diff --git a/README.md b/README.md index e257266..a2e4a03 100644 --- a/README.md +++ b/README.md @@ -28,6 +28,7 @@ asynchronous code up to the next level. * [Fail](#fail) * [Always](#always) * [Then](#then) + * [Recover](#recover) * [When](#when) * [Installation](#installation) * [Author](#author) @@ -229,6 +230,26 @@ promise2.thenInBackground({ data -> Promise in }) ``` +### Recover +Returns a new ***promise*** that can be used to continue the chain when an +error was thrown. + +```swift +let promise = Promise() +// Recover the chain +promise + .recover({ error -> Promise in + return Promise({ + return "Recovered" + }) + }) + .done({ string in + print(string) // Recovered + }) +// Reject the promise +promise.reject(Error.notFound) +``` + ### When Provides a way to execute callback functions based on one or more ***promises***. The ***when*** method returns a new "master" ***promise*** that diff --git a/Sources/When/Promise.swift b/Sources/When/Promise.swift index 1d7e12f..8c5171c 100644 --- a/Sources/When/Promise.swift +++ b/Sources/When/Promise.swift @@ -222,6 +222,9 @@ extension Promise { // MARK: - Recover extension Promise { + /** + Helps to recover from certain errors. Continues the chain if a given closure does not throw. + */ public func recover(on queue: DispatchQueue = mainQueue, _ body: @escaping (Error) throws -> T) -> Promise { let promise = Promise(queue: queue) @@ -229,6 +232,9 @@ extension Promise { return promise } + /** + Helps to recover from certain errors. Continues the chain if a given closure does not throw. + */ public func recover(on queue: DispatchQueue = mainQueue, _ body: @escaping (Error) throws -> Promise) -> Promise { let promise = Promise(queue: queue) @@ -243,6 +249,9 @@ extension Promise { return promise } + /** + Adds a recover observer. + */ private func addRecoverObserver(on queue: DispatchQueue, promise: Promise, _ body: @escaping (Error) throws -> T?) { observer = Observer(queue: queue) { result in