Skip to content

Commit

Permalink
Merge pull request #18 from vadymmarkov/feature/recover
Browse files Browse the repository at this point in the history
Feature: promise recover
  • Loading branch information
vadymmarkov authored May 28, 2017
2 parents 37dfb81 + d03c35f commit 80b9da8
Show file tree
Hide file tree
Showing 4 changed files with 182 additions and 23 deletions.
21 changes: 21 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -229,6 +230,26 @@ promise2.thenInBackground({ data -> Promise<NSData> in
})
```

### Recover
Returns a new ***promise*** that can be used to continue the chain when an
error was thrown.

```swift
let promise = Promise<String>()
// Recover the chain
promise
.recover({ error -> Promise<String> 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
Expand Down
97 changes: 74 additions & 23 deletions Sources/When/Promise.swift
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ open class Promise<T> {

// MARK: - Helpers

private func update(state: State<T>?) {
fileprivate func update(state: State<T>?) {
dispatch(queue) {
guard let state = state, let result = state.result else {
return
Expand Down Expand Up @@ -156,25 +156,6 @@ open class Promise<T> {
observer = nil
}

fileprivate func addObserver<U>(on queue: DispatchQueue, promise: Promise<U>, _ 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()
Expand All @@ -190,13 +171,11 @@ extension Promise {
public func then<U>(on queue: DispatchQueue = mainQueue, _ body: @escaping (T) throws -> U) -> Promise<U> {
let promise = Promise<U>(queue: queue)
addObserver(on: queue, promise: promise, body)

return promise
}

public func then<U>(on queue: DispatchQueue = mainQueue, _ body: @escaping (T) throws -> Promise<U>) -> Promise<U> {
let promise = Promise<U>(queue: queue)

addObserver(on: queue, promise: promise) { value -> U? in
let nextPromise = try body(value)
nextPromise.addObserver(on: queue, promise: promise) { value -> U? in
Expand All @@ -205,7 +184,6 @@ extension Promise {

return nil
}

return promise
}

Expand All @@ -217,7 +195,80 @@ extension Promise {
return then(on: backgroundQueue, body)
}

fileprivate func addObserver<U>(on queue: DispatchQueue, promise: Promise<U>, _ 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<Void> {
return then(on: instantQueue) { _ in return }
}
}

// 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<T> {
let promise = Promise<T>(queue: queue)
addRecoverObserver(on: queue, promise: promise, body)
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<T>) -> Promise<T> {
let promise = Promise<T>(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
}

/**
Adds a recover observer.
*/
private func addRecoverObserver(on queue: DispatchQueue, promise: Promise<T>,
_ 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)
}
}
1 change: 1 addition & 0 deletions WhenTests/Shared/Helpers/SpecError.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@ import Foundation

enum SpecError: Error {
case notFound
case rejected
}
86 changes: 86 additions & 0 deletions WhenTests/Shared/PromiseSpec.swift
Original file line number Diff line number Diff line change
Expand Up @@ -366,6 +366,92 @@ class PromiseSpec: QuickSpec {
}
}
}

describe("#recover") {
beforeEach {
promise = Promise<String>()
}

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<String> 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)
}
}
}
}
}
}
}

0 comments on commit 80b9da8

Please sign in to comment.