Skip to content

Commit

Permalink
Merge branch 'release/2.0.4'
Browse files Browse the repository at this point in the history
  • Loading branch information
malcommac committed Sep 15, 2020
2 parents fe3e826 + 4e7f08b commit b8cabb2
Show file tree
Hide file tree
Showing 19 changed files with 192 additions and 85 deletions.
Binary file not shown.
Binary file not shown.
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Bucket
uuid = "8543AB78-D45C-4EF9-B6DC-39F8F011741C"
type = "1"
version = "2.0">
</Bucket>
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>SchemeUserState</key>
<dict>
<key>DemoApp.xcscheme_^#shared#^_</key>
<dict>
<key>orderHint</key>
<integer>4</integer>
</dict>
<key>Hydra-iOS.xcscheme_^#shared#^_</key>
<dict>
<key>orderHint</key>
<integer>0</integer>
</dict>
<key>Hydra-macOS.xcscheme_^#shared#^_</key>
<dict>
<key>orderHint</key>
<integer>1</integer>
</dict>
<key>Hydra-tvOS.xcscheme_^#shared#^_</key>
<dict>
<key>orderHint</key>
<integer>2</integer>
</dict>
<key>Hydra-watchOS.xcscheme_^#shared#^_</key>
<dict>
<key>orderHint</key>
<integer>3</integer>
</dict>
</dict>
</dict>
</plist>
4 changes: 2 additions & 2 deletions HydraAsync.podspec
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
Pod::Spec.new do |spec|
spec.name = 'HydraAsync'
spec.version = '2.0.2'
spec.version = '2.0.4 '
spec.summary = 'Promises & Await: Write better async in Swift'
spec.homepage = 'https://github.com/malcommac/Hydra'
spec.license = { :type => 'MIT', :file => 'LICENSE' }
Expand All @@ -15,5 +15,5 @@ Pod::Spec.new do |spec|
spec.requires_arc = true
spec.module_name = 'Hydra'
spec.frameworks = "Foundation"
spec.swift_version = "5.0"
spec.swift_versions = ['4.0', '4.1', '4.2', '5.0', '5.1', '5.3']
end
5 changes: 2 additions & 3 deletions Package.swift
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// swift-tools-version:4.0
// swift-tools-version:5.1
import PackageDescription

let package = Package(
Expand All @@ -9,6 +9,5 @@ let package = Package(
targets: [
.target(name: "Hydra", dependencies: []),
.testTarget(name: "HydraTests", dependencies: ["Hydra"])
],
swiftLanguageVersions: [4]
]
)
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -345,7 +345,7 @@ invalidator.invalidate()
Await can be also used in conjuction with zip to resolve all promises from a list:

```swift
let (resultA,resultB) = await(Promise<Void>.zip(promiseA,promiseB))
let (resultA,resultB) = await(zip(promiseA,promiseB))
print(resultA)
print(resultB)
```
Expand Down Expand Up @@ -512,7 +512,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
Promise<Void>.zip(a: getUserProfile(user), b: getUserAvatar(user), c: getUserFriends(user))
zip(a: getUserProfile(user), b: getUserAvatar(user), c: getUserFriends(user))
.then { profile, avatar, friends in
// ... let's do something
}.catch {
Expand Down
4 changes: 2 additions & 2 deletions Sources/Hydra/Commons.swift
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ public enum PromiseError: Error {


/// Invalidatable protocol is used to control the execution of a promise from the outside
/// You should pass an object conforms to this type at the init of your Promsie instance.
/// You should pass an object conforms to this type at the init of your Promise instance.
/// To invalidate a Promise just return the `.isCancelled` property to `true`.
///
/// From the inside of your Promise's body you should check if the `operation.isCancelled` is `true`.
Expand All @@ -67,7 +67,7 @@ public protocol InvalidatableProtocol {


/// This is a simple implementation of the `InvalidatableProtocol` protocol.
/// You can use or extend this class in order to provide your own bussiness logic.
/// You can use or extend this class in order to provide your own business logic.
open class InvalidationToken: InvalidatableProtocol {

/// Current status of the promise
Expand Down
2 changes: 1 addition & 1 deletion Sources/Hydra/DispatchTimerWrapper.swift
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ internal class DispatchTimerWrapper {
timer.setEventHandler(handler: handler)
}

func scheduleOneshot(deadline: DispatchTime, leeway: DispatchTimeInterval = .nanoseconds(0)) {
func scheduleOneShot(deadline: DispatchTime, leeway: DispatchTimeInterval = .nanoseconds(0)) {
timer.schedule(deadline: deadline, leeway: leeway)
}

Expand Down
4 changes: 2 additions & 2 deletions Sources/Hydra/Promise+All.swift
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ public func all<L, S: Sequence>(_ promises: S, concurrency: UInt = UInt.max) ->
}

// We want to create a Promise which groups all input Promises and return only
// when all input promises fullfill or one of them reject.
// when all input promises fulfill or one of them reject.
// Promises are resolved in parallel but the array with the results of all promises is reported
// in the same order of the input promises.
let allPromise = Promise<[L]> { (resolve, reject, operation) in
Expand All @@ -66,7 +66,7 @@ public func all<L, S: Sequence>(_ promises: S, concurrency: UInt = UInt.max) ->
// decrement remaining promise to fulfill
countRemaining -= 1
if countRemaining == 0 {
// if all promises are fullfilled we can resolve our chain Promise
// if all promises are fulfilled we can resolve our chain Promise
// with an array of all values results of our input promises (in the same order)
let allResults = promises.map({ return $0.state.value! })
resolve(allResults)
Expand Down
36 changes: 21 additions & 15 deletions Sources/Hydra/Promise+Await.swift
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ public prefix func ..!<T> (_ promise: Promise<T>) -> T? {
/// - Parameters:
/// - context: context in which you want to execute the operation. If not specified default concurrent `awaitContext` is used instead.
/// - promise: target promise
/// - Returns: fufilled value of the promise
/// - Returns: fulfilled value of the promise
/// - Throws: throws an exception if promise fails due to an error
@discardableResult
public func await<T>(in context: Context? = nil, _ promise: Promise<T>) throws -> T {
Expand Down Expand Up @@ -92,17 +92,23 @@ public extension Context {
/// Awaits that the given promise fulfilled with its value or throws an error if the promise fails.
///
/// - Parameter promise: target promise
/// - Returns: return the value of the promise
/// - Throws: throw if promise fails
@discardableResult
internal func await<T>(_ promise: Promise<T>) throws -> T {
guard self.queue != DispatchQueue.main else {
// execute a promise on main context does not make sense
// dispatch_semaphore_wait should NOT be called on the main thread.
// more here: https://medium.com/@valentinkalchev/how-to-pause-and-resume-a-sequence-of-mutating-swift-structs-using-dispatch-semaphore-fc98eca55c0#.ipbujy4k2
throw PromiseError.invalidContext
}

/// - Returns: return the value of the promise
/// - Throws: throw if promise fails
@discardableResult
internal func await<T>(_ promise: Promise<T>) throws -> T {
#if os(Linux)
let isNotMainQueue = self.queue.label != DispatchQueue.main.label
#else
let isNotMainQueue = self.queue != DispatchQueue.main
#endif

guard isNotMainQueue else {
// execute a promise on main context does not make sense
// dispatch_semaphore_wait should NOT be called on the main thread.
// more here: https://medium.com/@valentinkalchev/how-to-pause-and-resume-a-sequence-of-mutating-swift-structs-using-dispatch-semaphore-fc98eca55c0#.ipbujy4k2
throw PromiseError.invalidContext
}

var result: T?
var error: Error?

Expand All @@ -111,16 +117,16 @@ public extension Context {
let semaphore = DispatchSemaphore(value: 0)

promise.then(in: self) { value -> Void in
// promise is fulfilled, fillup error and resume code execution
// promise is fulfilled, fill-up error and resume code execution
result = value
semaphore.signal()
}.catch(in: self) { err in
// promise is rejected, fillup error and resume code execution
// promise is rejected, fill-up error and resume code execution
error = err
semaphore.signal()
}

// Wait and block code execution until promise is fullfilled or rejected
// Wait and block code execution until promise is full-filled or rejected
_ = semaphore.wait(timeout: .distantFuture)

guard let promiseValue = result else {
Expand Down
4 changes: 2 additions & 2 deletions Sources/Hydra/Promise+Cancel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -37,11 +37,11 @@ public extension Promise {
/// Catch a cancelled promise.
///
/// - Parameters:
/// - context: context in which the body will be eecuted. If not specified `.main` is used.
/// - context: context in which the body will be executed. If not specified `.main` is used.
/// - body: body to execute
/// - Returns: a new void promise
@discardableResult
func cancelled(in context: Context? = nil, _ body: @escaping (() -> (()))) -> Promise<Void> {
func cancelled(in context: Context? = nil, _ body: @escaping (() -> (Void))) -> Promise<Void> {
let ctx = context ?? .main
let nextPromise = Promise<Void>(in: ctx, token: self.invalidationToken) { resolve, reject, operation in
let onResolve = Observer.onResolve(ctx, { _ in
Expand Down
6 changes: 5 additions & 1 deletion Sources/Hydra/Promise+Defer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -35,13 +35,17 @@ import Foundation

public extension Promise {

/// Delay the executon of a Promise chain by some number of seconds from current time
/// Delay the execution of a Promise chain by some number of seconds from current time
///
/// - Parameters:
/// - context: context in which the body is executed (if not specified `background` is used)
/// - seconds: delay time in seconds; execution time is `.now()+seconds`
/// - Returns: the Promise to resolve to after the delay
func `defer`(in context: Context? = nil, _ seconds: TimeInterval) -> Promise<Value> {
guard seconds > 0 else {
return self
}

let ctx = context ?? .background
return self.then(in: ctx, { value in
return Promise<Value> { resolve, _, _ in
Expand Down
5 changes: 2 additions & 3 deletions Sources/Hydra/Promise+Recover.swift
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@
import Foundation

public extension Promise {

/// Allows you to recover a failed promise by returning another promise with the same output
///
/// - Parameters:
Expand All @@ -43,8 +42,8 @@ public extension Promise {
/// - Returns: a promise
func recover(in context: Context? = nil, _ body: @escaping (Error) throws -> Promise<Value>) -> Promise<Value> {
let ctx = context ?? .background
return Promise<Value>(in: ctx, token: self.invalidationToken, { resolve, reject, operation in
return self.then(in: ctx, {
return Promise<Value>(in: ctx, token: self.invalidationToken, { [weak self] resolve, reject, operation in
self?.then(in: ctx, {
// If promise resolve we don't need to do anything.
resolve($0)
}).catch(in: ctx, { error in
Expand Down
14 changes: 11 additions & 3 deletions Sources/Hydra/Promise+Retry.swift
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,17 @@
import Foundation

public extension Promise {

func retry(_ attempts: Int = 3, _ condition: @escaping ((Int, Error) throws -> Bool) = { _,_ in true }) -> Promise<Value> {
return retryWhen(attempts) { (remainingAttempts, error) -> Promise<Bool> in

/// Retry the operation of the promise.
///
/// - Parameters:
/// - attempts: number of attempts.
/// - delay: delay between each attempts (starting when failed the first time).
/// - condition: condition for delay.
/// - Returns: Promise<Value>
func retry(_ attempts: Int = 3, delay: TimeInterval = 0,
_ condition: @escaping ((Int, Error) throws -> Bool) = { _,_ in true }) -> Promise<Value> {
return retryWhen(attempts, delay: delay) { (remainingAttempts, error) -> Promise<Bool> in
do {
return Promise<Bool>(resolved: try condition(remainingAttempts, error))
} catch (_) {
Expand Down
7 changes: 4 additions & 3 deletions Sources/Hydra/Promise+RetryWhen.swift
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,8 @@ import Foundation

public extension Promise {

func retryWhen(_ attempts: Int = 3, _ condition: @escaping ((Int, Error) -> Promise<Bool>) = { _,_ in Promise<Bool>(resolved: true) }) -> Promise<Value> {
func retryWhen(_ attempts: Int = 3, delay: TimeInterval = 0,
_ condition: @escaping ((Int, Error) -> Promise<Bool>) = { _,_ in Promise<Bool>(resolved: true) }) -> Promise<Value> {
guard attempts >= 1 else {
// Must be a valid attempts number
return Promise<Value>(rejected: PromiseError.invalidInput)
Expand All @@ -44,7 +45,7 @@ public extension Promise {
// We'll create a next promise which will be resolved when attempts to resolve self (source promise)
// is reached (with a fulfill or a rejection).
let nextPromise = Promise<Value>(in: self.context, token: self.invalidationToken) { (resolve, reject, operation) in
innerPromise = self.recover(in: self.context) { [unowned self] (error) -> (Promise<Value>) in
innerPromise = self.defer(delay).recover(in: self.context) { [unowned self] (error) -> (Promise<Value>) in
// If promise is rejected we'll decrement the attempts counter
remainingAttempts -= 1
guard remainingAttempts >= 1 else {
Expand All @@ -64,7 +65,7 @@ public extension Promise {
self.resetState()
// Re-execute the body of the source promise to re-execute the async operation
self.runBody()
self.retryWhen(remainingAttempts, condition).then(in: self.context) { (result) in
self.retryWhen(remainingAttempts, delay: delay, condition).then(in: self.context) { (result) in
resolve(result)
}.catch { (retriedError) in
reject(retriedError)
Expand Down
2 changes: 1 addition & 1 deletion Sources/Hydra/Promise+Timeout.swift
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ public extension Promise {
let errorToPass = (error ?? PromiseError.timeout)
reject(errorToPass)
}
timer.scheduleOneshot(deadline: .now() + timeout)
timer.scheduleOneShot(deadline: .now() + timeout)
timer.resume()

// Observe resolve
Expand Down
2 changes: 1 addition & 1 deletion Sources/Hydra/Promise.swift
Original file line number Diff line number Diff line change
Expand Up @@ -261,7 +261,7 @@ public class Promise<Value> {
/// ```
/// let op_1: Promise<User> = asyncGetCurrentUserProfile()
/// let op_2: Promise<UIImage> = asyncGetCurrentUserAvatar()
/// let op_3: Promise<[User]> = asyncGetCUrrentUserFriends()
/// let op_3: Promise<[User]> = asyncGetCurrentUserFriends()
/// all(op_1.void,op_2.void,op_3.void).then { _ in
/// let userProfile = op_1.result
/// let avatar = op_2.result
Expand Down
Loading

0 comments on commit b8cabb2

Please sign in to comment.