diff --git a/CHANGELOG.md b/CHANGELOG.md index 30d5b3d..3487e7d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,9 +4,20 @@ ## CHANGELOG +* Version **[0.9.3](#093)** * Version **[0.9.2](#092)** * Version **[0.9.1](#091)** + + +## Hydra 0.9.3 +--- +- **Release Date**: 2017-03-06 +- **Zipped Version**: [Download 0.9.3](https://github.com/malcommac/Hydra/releases/tag/0.9.3) + +- [#15](https://github.com/malcommac/Hydra/pull/15) Added conditional block to retry operator to determine whether retry is possible +- [#14](https://github.com/malcommac/Hydra/pull/14) Minor fixes for documentation (`zip` and `all` funcs) + ## Hydra 0.9.2 diff --git a/Hydra/Hydra.xcodeproj/project.pbxproj b/Hydra/Hydra.xcodeproj/project.pbxproj index e71291f..fa03812 100644 --- a/Hydra/Hydra.xcodeproj/project.pbxproj +++ b/Hydra/Hydra.xcodeproj/project.pbxproj @@ -449,7 +449,7 @@ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 3; DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; @@ -503,7 +503,7 @@ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 3; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; diff --git a/Hydra/Hydra.xcodeproj/project.xcworkspace/xcuserdata/danielemm.xcuserdatad/UserInterfaceState.xcuserstate b/Hydra/Hydra.xcodeproj/project.xcworkspace/xcuserdata/danielemm.xcuserdatad/UserInterfaceState.xcuserstate index afdc086..cf3e555 100644 Binary files a/Hydra/Hydra.xcodeproj/project.xcworkspace/xcuserdata/danielemm.xcuserdatad/UserInterfaceState.xcuserstate and b/Hydra/Hydra.xcodeproj/project.xcworkspace/xcuserdata/danielemm.xcuserdatad/UserInterfaceState.xcuserstate differ diff --git a/HydraAsync.podspec b/HydraAsync.podspec index 2d50f0b..9833626 100644 --- a/HydraAsync.podspec +++ b/HydraAsync.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |spec| spec.name = 'HydraAsync' - spec.version = '0.9.2' + spec.version = '0.9.3' spec.summary = 'Promises & Await: Write better async in Swift' spec.homepage = 'https://github.com/malcommac/Hydra' spec.license = { :type => 'MIT', :file => 'LICENSE' } diff --git a/README.md b/README.md index 270eb2c..dedc93e 100644 --- a/README.md +++ b/README.md @@ -300,7 +300,7 @@ Execution of all promises is done in parallel. ```swift let promises = usernameList.map { return getAvatar(username: $0) } -Promise.all(promises).then { usersAvatars in +all(promises).then { usersAvatars in // you will get an array of UIImage with the avatars of input // usernames, all in the same order of the input. // Download of the avatar is done in parallel in background! @@ -375,7 +375,7 @@ Map is used to transform a list of items into promises and resolve them in paral `zip` allows you to join different promises (2,3 or 4) and return a tuple with the result of them. Promises are resolved in parallel. ```swift -join(getUserProfile(user), getUserAvatar(user), getUserFriends(user)) +Promise.zip(a: getUserProfile(user), b: getUserAvatar(user), c: getUserFriends(user)) .then { profile, avatar, friends in // ... let's do something }.catch { @@ -406,6 +406,20 @@ myAsyncFunc(param).retry(3).then { value in } ``` +Conditional retry allows you to control retryable if it ends with a rejection. + +```swift +// If myAsyncFunc() fails the operator execute the condition block to check retryable. +// If return false in condition block, promise state rejected with last catched error. +myAsyncFunc(param).retry(3) { (remainAttempts, error) -> Bool in + return error.isRetryable +}.then { value in + print("Value \(value) got at attempt #\(currentAttempt)") +}.catch { err in + print("Failed to get a value after \(currentAttempt) attempts with error: \(err)") +} +``` + ## Installation diff --git a/Sources/Hydra/Promise+Always.swift b/Sources/Hydra/Promise+Always.swift index f9b7d00..591746a 100644 --- a/Sources/Hydra/Promise+Always.swift +++ b/Sources/Hydra/Promise+Always.swift @@ -64,7 +64,7 @@ public extension Promise { } }) - self.add(observers: onResolve,onReject) + self.add(observers: onResolve, onReject) } nextPromise.runBody() self.runBody() diff --git a/Sources/Hydra/Promise+Catch.swift b/Sources/Hydra/Promise+Catch.swift index cefefe0..5d4a492 100644 --- a/Sources/Hydra/Promise+Catch.swift +++ b/Sources/Hydra/Promise+Catch.swift @@ -56,7 +56,7 @@ public extension Promise { } resolve(()) }) - self.add(observers: onResolve,onReject) + self.add(observers: onResolve, onReject) } nextPromise.runBody() self.runBody() diff --git a/Sources/Hydra/Promise+Retry.swift b/Sources/Hydra/Promise+Retry.swift index 8bfc974..3a859cc 100644 --- a/Sources/Hydra/Promise+Retry.swift +++ b/Sources/Hydra/Promise+Retry.swift @@ -38,9 +38,11 @@ public extension Promise { /// If reached the attempts the promise still rejected chained promise is also rejected along with /// the same source error. /// - /// - Parameter attempts: number of retry attempts for source promise (must be a number > 1, otherwise promise is rejected with `PromiseError.invalidInput` error. + /// - Parameters: + /// - attempts: number of retry attempts for source promise (must be a number > 1, otherwise promise is rejected with `PromiseError.invalidInput` error. + /// - condition: code block to check retryable source promise /// - Returns: a promise which resolves when the first attempt to resolve source promise is succeded, rejects if none of the attempts ends with a success. - public func retry(_ attempts: Int = 3) -> Promise { + public func retry(_ attempts: Int = 3, _ condition: @escaping ((Int, Error) throws -> Bool) = { _ in true }) -> Promise { guard attempts >= 1 else { // Must be a valid attempts number return Promise(rejected: PromiseError.invalidInput) @@ -61,6 +63,17 @@ public extension Promise { reject(error) return } + // If promise is rejected we will check condition that is retryable + do { + guard try condition(remainingAttempts, error) else { + reject(error) + return + } + } catch(_) { + // reject soruce promise error + reject(error) + return + } // Reset the state of the promise // (okay it's true, a Promise cannot change state as you know...this // is a bit trick which will remain absolutely internal to the library itself) @@ -69,7 +82,7 @@ public extension Promise { self.runBody() }) // Observe changes from source promise - self.add(observers: onResolve,onReject) + self.add(observers: onResolve, onReject) self.runBody() } nextPromise.runBody() diff --git a/Sources/Hydra/Promise+Then.swift b/Sources/Hydra/Promise+Then.swift index 0a96e79..411c6de 100644 --- a/Sources/Hydra/Promise+Then.swift +++ b/Sources/Hydra/Promise+Then.swift @@ -86,7 +86,7 @@ public extension Promise { // execute the promise's body and get the result of it let pResolve = Observer.onResolve(ctx, resolve) let pReject = Observer.onReject(ctx, reject) - chainedPromise.add(observers: pResolve,pReject) + chainedPromise.add(observers: pResolve, pReject) chainedPromise.runBody() } catch let error { reject(error) @@ -96,7 +96,7 @@ public extension Promise { // Observe the reject of the self promise let onReject = Observer.onReject(ctx, reject) - self.add(observers: onResolve,onReject) + self.add(observers: onResolve, onReject) }) nextPromise.runBody() self.runBody() diff --git a/Tests/HydraTests/HydraTests.swift b/Tests/HydraTests/HydraTests.swift index 4347600..4325311 100644 --- a/Tests/HydraTests/HydraTests.swift +++ b/Tests/HydraTests/HydraTests.swift @@ -602,6 +602,42 @@ class HydraTestThen: XCTestCase { waitForExpectations(timeout: expTimeout, handler: nil) } + func test_retry_condition() { + let exp = expectation(description: "test_retry_condition") + + let retryAttempts = 5 + let successOnAttempt = 5 + let retryableRemainAttempt = 2 + var currentAttempt = 0 + Promise { (resolve, reject) in + currentAttempt += 1 + if currentAttempt < successOnAttempt { + print("attempt is \(currentAttempt)... reject") + reject(TestErrors.anotherError) + } else { + print("attempt is \(currentAttempt)... resolve") + resolve(5) + } + }.retry(retryAttempts) { (remainAttempts, error) -> Bool in + if remainAttempts > retryableRemainAttempt { + print("retry remainAttempts is \(remainAttempts)... true") + return true + } else { + print("retry remainAttempts is \(remainAttempts)... false") + return false + } + }.then { value in + print("value \(value) at attempt \(currentAttempt)") + XCTFail() + }.catch { err in + print("failed \(err) at attempt \(currentAttempt)") + XCTAssertEqual(currentAttempt, 3) + exp.fulfill() + } + + waitForExpectations(timeout: expTimeout, handler: nil) + } + //MARK: Helper func intFailedPromise(_ error: Error) -> Promise {