diff --git a/Documents/Installation.md b/Documents/Installation.md index 8f51add0b..a8c4dc8e1 100644 --- a/Documents/Installation.md +++ b/Documents/Installation.md @@ -27,6 +27,8 @@ pod "PromiseKit/Foundation", "~> 7.0.0-rc1" pod "PromiseKit/MapKit", "~> 7.0.0-rc1" ``` +Considering 7.0 is still a release candidate, you may prefer to use [v6](https://github.com/mxcl/PromiseKit/blob/v6/README.md). + ## Carthage We will support [Carthage] if you can PR an automated solution for generating diff --git a/Sources/PromiseKit/Cancellation/CancellableThenable.swift b/Sources/PromiseKit/Cancellation/CancellableThenable.swift index 5ca4ee68d..7be688805 100644 --- a/Sources/PromiseKit/Cancellation/CancellableThenable.swift +++ b/Sources/PromiseKit/Cancellation/CancellableThenable.swift @@ -295,13 +295,6 @@ public extension CancellableThenable { } public extension CancellableThenable { - /** - - Returns: The error with which this cancellable promise was rejected; `nil` if this promise is not rejected. - */ - var error: Error? { - return thenable.error - } - /** - Returns: `true` if the cancellable promise has not yet resolved. */ @@ -324,10 +317,10 @@ public extension CancellableThenable { } /** - - Returns: `true` if the cancellable promise was rejected. - */ - var isRejected: Bool { - return thenable.isRejected + - Returns: The result with which this cancellable promise was resolved or `nil` if this cancellable promise is pending. + */ + var result: U.R? { + return thenable.result } /** @@ -338,6 +331,22 @@ public extension CancellableThenable { } } +public extension CancellableThenable where U.R == Result { + /** + - Returns: The error with which this cancellable promise was rejected; `nil` if this promise is not rejected. + */ + var error: Error? { + return thenable.error + } + + /** + - Returns: `true` if the cancellable promise was rejected. + */ + var isRejected: Bool { + return thenable.isRejected + } +} + public extension CancellableThenable where U.T: Sequence { /** `CancellablePromise<[U.T]>` => `U.T` -> `V` => `CancellablePromise<[V]>` @@ -518,3 +527,7 @@ public extension CancellableThenable where U.T: Sequence, U.T.Iterator.Element: return map(on: on) { $0.sorted() } } } + +func asThenables(_ cancellableThenables: [CT]) -> [CT.U] { + cancellableThenables.map { $0.thenable } +} diff --git a/Sources/PromiseKit/Guarantee.swift b/Sources/PromiseKit/Guarantee.swift index 668eb0252..f4b902c9a 100644 --- a/Sources/PromiseKit/Guarantee.swift +++ b/Sources/PromiseKit/Guarantee.swift @@ -51,12 +51,12 @@ public final class Guarantee: Thenable { } /// - See: `Thenable.result` - public var result: Result? { + public var result: T? { switch box.inspect() { case .pending: return nil case .resolved(let value): - return .success(value) + return value } } @@ -163,7 +163,6 @@ public extension Guarantee { any part of your chain may use. Like the main thread for example. */ func wait() -> T { - if Thread.isMainThread { conf.logHandler(.waitOnMainThread) } diff --git a/Sources/PromiseKit/Result+Utils.swift b/Sources/PromiseKit/Result+Utils.swift new file mode 100644 index 000000000..29f58cfa0 --- /dev/null +++ b/Sources/PromiseKit/Result+Utils.swift @@ -0,0 +1,13 @@ +import Foundation + +extension Result { + + var error: Failure? { + switch self { + case let .failure(error): + return error + case .success: + return nil + } + } +} diff --git a/Sources/PromiseKit/Thenable.swift b/Sources/PromiseKit/Thenable.swift index 4d4d09f1f..2e115143f 100644 --- a/Sources/PromiseKit/Thenable.swift +++ b/Sources/PromiseKit/Thenable.swift @@ -4,15 +4,21 @@ import Dispatch public protocol Thenable: AnyObject { /// The type of the wrapped value associatedtype T + /// The type of the result + associatedtype R /// `pipe` is immediately executed when this `Thenable` is resolved func pipe(to: @escaping(Result) -> Void) /// The resolved result or nil if pending. - var result: Result? { get } + var result: R? { get } + + /// The resolved result's value or nil if pending. + var value: T? { get } } public extension Thenable { + /** The provided closure executes when this promise is fulfilled. @@ -218,17 +224,10 @@ public extension Thenable { public extension Thenable { /** - - Returns: The error with which this promise was rejected; `nil` if this promise is not rejected. + - Returns: `true` if the promise was fulfilled. */ - var error: Error? { - switch result { - case .none: - return nil - case .some(.success): - return nil - case .some(.failure(let error)): - return error - } + var isFulfilled: Bool { + return value != nil } /** @@ -244,33 +243,37 @@ public extension Thenable { var isResolved: Bool { return !isPending } +} +public extension Thenable where R == T { /** - - Returns: `true` if the promise was fulfilled. + - Returns: The value with which this promise was fulfilled or `nil` if this promise is pending or rejected. */ - var isFulfilled: Bool { - return value != nil + var value: T? { + return result } +} +public extension Thenable where R == Result { + /** + - Returns: The value with which this promise was fulfilled or `nil` if this promise is pending or rejected. + */ + var value: T? { + return try? result?.get() + } + /** - - Returns: `true` if the promise was rejected. + - Returns: `true` if this promise was rejected. */ var isRejected: Bool { return error != nil } - + /** - - Returns: The value with which this promise was fulfilled or `nil` if this promise is pending or rejected. + - Returns: The error with which this promise was rejected; `nil` if this promise is not rejected. */ - var value: T? { - switch result { - case .none: - return nil - case .some(.success(let value)): - return value - case .some(.failure): - return nil - } + var error: Error? { + return result?.error } } diff --git a/Sources/PromiseKit/when.swift b/Sources/PromiseKit/when.swift index eb548babf..b467671f8 100644 --- a/Sources/PromiseKit/when.swift +++ b/Sources/PromiseKit/when.swift @@ -1,12 +1,12 @@ import Foundation import Dispatch -private func _when(_ thenables: [U]) -> Promise { - var countdown = thenables.count - guard countdown > 0 else { +// MARK: - when(fulfilled:) array + +private func _when(_ thenables: [TH]) -> Promise { + guard !thenables.isEmpty else { return .value(Void()) } - let rp = Promise(.pending) #if PMKDisableProgress || os(Linux) || os(Android) @@ -18,23 +18,22 @@ private func _when(_ thenables: [U]) -> Promise { #endif let barrier = DispatchQueue(label: "org.promisekit.barrier.when", attributes: .concurrent) + var countdown = thenables.count for promise in thenables { promise.pipe { result in barrier.sync(flags: .barrier) { switch result { case .failure(let error): - if rp.isPending { - progress.completedUnitCount = progress.totalUnitCount - rp.box.seal(.failure(error)) - } + guard rp.isPending else { return } + progress.completedUnitCount = progress.totalUnitCount + rp.box.seal(.failure(error)) case .success: guard rp.isPending else { return } progress.completedUnitCount += 1 countdown -= 1 - if countdown == 0 { - rp.box.seal(.success(())) - } + guard countdown == 0 else { return } + rp.box.seal(.success(())) } } } @@ -43,9 +42,8 @@ private func _when(_ thenables: [U]) -> Promise { return rp } -/** - Wait for all promises in a set to fulfill. - +/** Wait for all provided thenables to fulfill. You can use either an array of `Promise`'s or an array of `Guarantee`'s. + For example: when(fulfilled: promise1, promise2).then { results in @@ -59,54 +57,91 @@ private func _when(_ thenables: [U]) -> Promise { } } - - Note: If *any* of the provided promises reject, the returned promise is immediately rejected with that error. - - Warning: In the event of rejection the other promises will continue to resolve and, as per any other promise, will either fulfill or reject. This is the right pattern for `getter` style asynchronous tasks, but often for `setter` tasks (eg. storing data on a server), you most likely will need to wait on all tasks and then act based on which have succeeded and which have failed, in such situations use `when(resolved:)`. - - Parameter promises: The promises upon which to wait before the returned promise resolves. - - Returns: A new promise that resolves when all the provided promises fulfill or one of the provided promises rejects. - - Note: `when` provides `NSProgress`. + - Note: If *any* of the provided thenables rejects, the returned promise is immediately rejected with that error. + - Warning: In the event of rejection the other thenables will continue to resolve. `Guarantee`'s will resolve and `Promise`'s, will either fulfill or reject. This is the right pattern for `getter` style asynchronous tasks, but often for `setter` tasks (eg. storing data on a server), you most likely will need to wait on all tasks and then act based on which have succeeded and which have failed, in such situations use `when(resolved:)`. + - Parameter fulfilled: The thenables upon which to wait before the returned promise resolves. + - Returns: A new promise that resolves when all the provided thenables fulfill or one of the thenables rejects. + - Note: `when(fulfilled:)` provides `NSProgress`. + - Note: Provided thenables must be of the same concrete type and have the same associated type `T` as type of their wrapped values. Otherwise use tuple overloads. - SeeAlso: `when(resolved:)` */ -public func when(fulfilled thenables: [U]) -> Promise<[U.T]> { +public func when(fulfilled thenables: [TH]) -> Promise<[TH.T]> { return _when(thenables).map(on: nil) { thenables.map{ $0.value! } } } -/// Wait for all promises in a set to fulfill. -public func when(fulfilled promises: U...) -> Promise where U.T == Void { - return _when(promises) +/** Wait for all provided `Void` thenables to fulfill. You can use either an array of `Promise`'s or an array of `Guarantee`'s. + + Convenience function that returns `Promise` for `Void` thenables. + - SeeAlso: when(fulfilled:) + - SeeAlso: when(resolved:) +*/ +public func when(fulfilled thenables: [TH]) -> Promise where TH.T == Void { + return _when(thenables) } -/// Wait for all promises in a set to fulfill. -public func when(fulfilled promises: [U]) -> Promise where U.T == Void { - return _when(promises) +// THIS MAKES UNAMBIGUOUS USAGES DUE TO TUPLE ARITY FUNCTIONS +//public func when(fulfilled thenables: TH...) -> Promise<[TH.T]> { +// return _when(thenables) +//} + +// MARK: - when(fulfilled:) tuples arity + +/** Wait for all provided thenables to fulfill. You can mix `Promise`'s and `Guarantee`'s. +*/ +public func when(fulfilled th1: TH1, _ th2: TH2) -> Promise<(TH1.T, TH2.T)> { + return _when([th1.asVoid(), th2.asVoid()]).map(on: nil) { (th1.value!, th2.value!) } } -/// Wait for all promises in a set to fulfill. -public func when(fulfilled pu: U, _ pv: V) -> Promise<(U.T, V.T)> { - return _when([pu.asVoid(), pv.asVoid()]).map(on: nil) { (pu.value!, pv.value!) } +/** Wait for all provided thenables to fulfill. You can mix `Promise`'s and `Guarantee`'s. + */ +public func when(fulfilled th1: TH1, _ th2: TH2, _ th3: TH3) -> Promise<(TH1.T, TH2.T, TH3.T)> { + return _when([th1.asVoid(), th2.asVoid(), th3.asVoid()]).map(on: nil) { (th1.value!, th2.value!, th3.value!) } } -/// Wait for all promises in a set to fulfill. -public func when(fulfilled pu: U, _ pv: V, _ pw: W) -> Promise<(U.T, V.T, W.T)> { - return _when([pu.asVoid(), pv.asVoid(), pw.asVoid()]).map(on: nil) { (pu.value!, pv.value!, pw.value!) } +/** Wait for all provided thenables to fulfill. You can mix `Promise`'s and `Guarantee`'s. + */ +public func when(fulfilled th1: TH1, _ th2: TH2, _ th3: TH3, _ th4: TH4) -> Promise<(TH1.T, TH2.T, TH3.T, TH4.T)> { + return _when([th1.asVoid(), th2.asVoid(), th3.asVoid(), th4.asVoid()]).map(on: nil) { (th1.value!, th2.value!, th3.value!, th4.value!) } } -/// Wait for all promises in a set to fulfill. -public func when(fulfilled pu: U, _ pv: V, _ pw: W, _ px: X) -> Promise<(U.T, V.T, W.T, X.T)> { - return _when([pu.asVoid(), pv.asVoid(), pw.asVoid(), px.asVoid()]).map(on: nil) { (pu.value!, pv.value!, pw.value!, px.value!) } +/** Wait for all provided thenables to fulfill. You can mix `Promise`'s and `Guarantee`'s. + */ +public func when(fulfilled th1: TH1, _ th2: TH2, _ th3: TH3, _ th4: TH4, _ th5: TH5) -> Promise<(TH1.T, TH2.T, TH3.T, TH4.T, TH5.T)> { + return _when([th1.asVoid(), th2.asVoid(), th3.asVoid(), th4.asVoid(), th5.asVoid()]).map(on: nil) { (th1.value!, th2.value!, th3.value!, th4.value!, th5.value!) } } -/// Wait for all promises in a set to fulfill. -public func when(fulfilled pu: U, _ pv: V, _ pw: W, _ px: X, _ py: Y) -> Promise<(U.T, V.T, W.T, X.T, Y.T)> { - return _when([pu.asVoid(), pv.asVoid(), pw.asVoid(), px.asVoid(), py.asVoid()]).map(on: nil) { (pu.value!, pv.value!, pw.value!, px.value!, py.value!) } +/** Wait for all provided thenables to fulfill. You can mix `Promise`'s and `Guarantee`'s. + */ +public func when(fulfilled th1: TH1, _ th2: TH2, _ th3: TH3, _ th4: TH4, _ th5: TH5, _ th6: TH6) -> Promise<(TH1.T, TH2.T, TH3.T, TH4.T, TH5.T, TH6.T)> { + return _when([th1.asVoid(), th2.asVoid(), th3.asVoid(), th4.asVoid(), th5.asVoid(), th6.asVoid()]).map(on: nil) { (th1.value!, th2.value!, th3.value!, th4.value!, th5.value!, th6.value!) } } -/** - Generate promises at a limited rate and wait for all to fulfill. +/** Wait for all provided thenables to fulfill. You can mix `Promise`'s and `Guarantee`'s. + */ +public func when(fulfilled th1: TH1, _ th2: TH2, _ th3: TH3, _ th4: TH4, _ th5: TH5, _ th6: TH6, _ th7: TH7) -> Promise<(TH1.T, TH2.T, TH3.T, TH4.T, TH5.T, TH6.T, TH7.T)> { + return _when([th1.asVoid(), th2.asVoid(), th3.asVoid(), th4.asVoid(), th5.asVoid(), th6.asVoid(), th7.asVoid()]).map(on: nil) { (th1.value!, th2.value!, th3.value!, th4.value!, th5.value!, th6.value!, th7.value!) } +} + +/** Wait for all provided thenables to fulfill. You can mix `Promise`'s and `Guarantee`'s. + */ +public func when(fulfilled th1: TH1, _ th2: TH2, _ th3: TH3, _ th4: TH4, _ th5: TH5, _ th6: TH6, _ th7: TH7, _ th8: TH8) -> Promise<(TH1.T, TH2.T, TH3.T, TH4.T, TH5.T, TH6.T, TH7.T, TH8.T)> { + return _when([th1.asVoid(), th2.asVoid(), th3.asVoid(), th4.asVoid(), th5.asVoid(), th6.asVoid(), th7.asVoid(), th8.asVoid()]).map(on: nil) { (th1.value!, th2.value!, th3.value!, th4.value!, th5.value!, th6.value!, th7.value!, th8.value!) } +} + +/** Wait for all provided thenables to fulfill. You can mix `Promise`'s and `Guarantee`'s. + */ +public func when(fulfilled th1: TH1, _ th2: TH2, _ th3: TH3, _ th4: TH4, _ th5: TH5, _ th6: TH6, _ th7: TH7, _ th8: TH8, _ th9: TH9) -> Promise<(TH1.T, TH2.T, TH3.T, TH4.T, TH5.T, TH6.T, TH7.T, TH8.T, TH9.T)> { + return _when([th1.asVoid(), th2.asVoid(), th3.asVoid(), th4.asVoid(), th5.asVoid(), th6.asVoid(), th7.asVoid(), th8.asVoid(), th9.asVoid()]).map(on: nil) { (th1.value!, th2.value!, th3.value!, th4.value!, th5.value!, th6.value!, th7.value!, th8.value!, th9.value!) } +} + +// MARK: - when(fulfilled:) iterator + +/** Generate thenables at a limited rate and wait for all to fulfill. For example: func downloadFile(url: URL) -> Promise { - // … + // ... } let urls: [URL] = /*…*/ @@ -120,42 +155,36 @@ public func when(fulfilled promiseIterator: It, concurrently: Int) -> Promise<[It.Element.T]> where It.Element: Thenable { - +public func when(fulfilled iterator: It, concurrently: Int) -> Promise<[It.Element.T]> where It.Element: Thenable { guard concurrently > 0 else { return Promise(error: PMKError.badInput) } - var generator = promiseIterator + var generator = iterator let root = Promise<[It.Element.T]>.pending() var pendingPromises = 0 var promises: [It.Element] = [] - let barrier = DispatchQueue(label: "org.promisekit.barrier.when", attributes: [.concurrent]) func dequeue() { - guard root.promise.isPending else { return } // don’t continue dequeueing if root has been rejected - + guard root.promise.isPending else { return } // don’t continue dequeueing if root has been rejected var shouldDequeue = false barrier.sync { shouldDequeue = pendingPromises < concurrently } guard shouldDequeue else { return } - var promise: It.Element! - barrier.sync(flags: .barrier) { guard let next = generator.next() else { return } promise = next @@ -165,21 +194,18 @@ public func when(fulfilled promiseIterator: It, concurrent func testDone() { barrier.sync { - if pendingPromises == 0 { - root.resolver.fulfill(promises.compactMap{ $0.value }) - } + guard pendingPromises == 0 else { return } + root.resolver.fulfill(promises.compactMap{ $0.value }) } } guard promise != nil else { return testDone() } - promise.pipe { resolution in barrier.sync(flags: .barrier) { pendingPromises -= 1 } - switch resolution { case .success: dequeue() @@ -188,181 +214,203 @@ public func when(fulfilled promiseIterator: It, concurrent root.resolver.reject(error) } } - dequeue() } - dequeue() - return root.promise } -/** - Waits on all provided promises. +// MARK: - when(resolved:) array - `when(fulfilled:)` rejects as soon as one of the provided promises rejects. `when(resolved:)` waits on all provided promises whatever their result, and then provides an array of `Result` so you can individually inspect the results. As a consequence this function returns a `Guarantee`, ie. errors are lifted from the individual promises into the results array of the returned `Guarantee`. +private func _when(resolved thenables: [TH]) -> Guarantee { + guard !thenables.isEmpty else { + return .value(Void()) + } + var countdown = thenables.count + let barrier = DispatchQueue(label: "org.promisekit.barrier.join", attributes: .concurrent) + + let rg = Guarantee(.pending) + for thenable in thenables { + thenable.pipe { result in + barrier.sync(flags: .barrier) { + countdown -= 1 + } + barrier.sync { + guard countdown == 0 else { return } + rg.box.seal(Void()) + } + } + } + return rg +} + +/** Wait for all provided thenables to resolve. You can use either an array of `Promise`'s or an array of `Guarantee`'s. + `when(fulfilled:)` rejects as soon as one of the provided thenables rejects. `when(resolved:)` waits on all provided thenables whatever their result, and then provides an array of corresponding results so you can inspect each one individually. As a consequence this function returns a `Guarantee`, ie. errors are lifted from the individual thenables into the results array of the returned `Guarantee`. + + For example: + when(resolved: promise1, promise2, promise3).then { results in - for result in results where case .success(let value) { + for result in results where case .fulfilled(let value) { //… } }.catch { error in // invalid! Never rejects } - - - Returns: A new promise that resolves once all the provided promises resolve. The array is ordered the same as the input, ie. the result order is *not* resolution order. - - Note: we do not provide tuple variants for `when(resolved:)` but will accept a pull-request - - Remark: Doesn't take Thenable due to protocol `associatedtype` paradox + + - Note: Provided thenables must be of the same concrete type and have the same associated type `R` as type of their results. Otherwise use tuple overloads. + - Returns: A new guarantee that resolves once all the provided thenables resolve. The array is ordered the same as the input, ie. the result order is *not* resolution order. */ -public func when(resolved promises: Promise...) -> Guarantee<[Result]> { - return when(resolved: promises) +public func when(resolved thenables: [TH]) -> Guarantee<[TH.R]> { + return _when(resolved: thenables).map(on: nil) { thenables.map{ $0.result! } } } -/// - See: `when(resolved: Promise...)` -public func when(resolved promises: [Promise]) -> Guarantee<[Result]> { - guard !promises.isEmpty else { - return .value([]) - } +/** Wait for all provided `Void` thenables to resolve. You can use either an array of `Promise`'s or an array of `Guarantee`'s. + + Convenience function that returns `Promise` for `Void` thenables. + - SeeAlso: when(fulfilled:) + - SeeAlso: when(resolved:) + */ +public func when(resolved thenables: [TH]) -> Guarantee where TH.T == Void { + return _when(resolved: thenables) +} - var countdown = promises.count - let barrier = DispatchQueue(label: "org.promisekit.barrier.join", attributes: .concurrent) +// THIS MAKES UNAMBIGUOUS USAGES DUE TO TUPLE ARITY FUNCTIONS +//public func when(resolved thenables: TH...) -> Guarantee<[TH.R]> { +// return _when(resolved: thenables) +//} - let rg = Guarantee<[Result]>(.pending) - for promise in promises { - promise.pipe { result in - barrier.sync(flags: .barrier) { - countdown -= 1 - } - barrier.sync { - if countdown == 0 { - rg.box.seal(promises.map{ $0.result! }) - } - } - } - } - return rg +// MARK: - when(resolved:) tuples arity + +/** Wait for all provided thenables to resolve. You can mix `Promise`'s and `Guarantee`'s. + */ +public func when(resolved th1: TH1, _ th2: TH2) -> Guarantee<(TH1.R, TH2.R)> { + return _when(resolved: [th1.asVoid(), th2.asVoid()]).map(on: nil) { (th1.result!, th2.result!) } } -/** -Generate promises at a limited rate and wait for all to resolve. +/** Wait for all provided thenables to resolve. You can mix `Promise`'s and `Guarantee`'s. + */ +public func when(resolved th1: TH1, _ th2: TH2, _ th3: TH3) -> Guarantee<(TH1.R, TH2.R, TH3.R)> { + return _when(resolved: [th1.asVoid(), th2.asVoid(), th3.asVoid()]).map(on: nil) { (th1.result!, th2.result!, th3.result!) } +} + +/** Wait for all provided thenables to resolve. You can mix `Promise`'s and `Guarantee`'s. + */ +public func when(resolved th1: TH1, _ th2: TH2, _ th3: TH3, _ th4: TH4) -> Guarantee<(TH1.R, TH2.R, TH3.R, TH4.R)> { + return _when(resolved: [th1.asVoid(), th2.asVoid(), th3.asVoid(), th4.asVoid()]).map(on: nil) { (th1.result!, th2.result!, th3.result!, th4.result!) } +} -For example: +/** Wait for all provided thenables to resolve. You can mix `Promise`'s and `Guarantee`'s. + */ +public func when(resolved th1: TH1, _ th2: TH2, _ th3: TH3, _ th4: TH4, _ th5: TH5) -> Guarantee<(TH1.R, TH2.R, TH3.R, TH4.R, TH5.R)> { + return _when(resolved: [th1.asVoid(), th2.asVoid(), th3.asVoid(), th4.asVoid(), th5.asVoid()]).map(on: nil) { (th1.result!, th2.result!, th3.result!, th4.result!, th5.result!) } +} +/** Wait for all provided thenables to resolve. You can mix `Promise`'s and `Guarantee`'s. + */ +public func when(resolved th1: TH1, _ th2: TH2, _ th3: TH3, _ th4: TH4, _ th5: TH5, _ th6: TH6) -> Guarantee<(TH1.R, TH2.R, TH3.R, TH4.R, TH5.R, TH6.R)> { + return _when(resolved: [th1.asVoid(), th2.asVoid(), th3.asVoid(), th4.asVoid(), th5.asVoid(), th6.asVoid()]).map(on: nil) { (th1.result!, th2.result!, th3.result!, th4.result!, th5.result!, th6.result!) } +} + +/** Wait for all provided thenables to resolve. You can mix `Promise`'s and `Guarantee`'s. + */ +public func when(resolved th1: TH1, _ th2: TH2, _ th3: TH3, _ th4: TH4, _ th5: TH5, _ th6: TH6, _ th7: TH7) -> Guarantee<(TH1.R, TH2.R, TH3.R, TH4.R, TH5.R, TH6.R, TH7.R)> { + return _when(resolved: [th1.asVoid(), th2.asVoid(), th3.asVoid(), th4.asVoid(), th5.asVoid(), th6.asVoid(), th7.asVoid()]).map(on: nil) { (th1.result!, th2.result!, th3.result!, th4.result!, th5.result!, th6.result!, th7.result!) } +} + +/** Wait for all provided thenables to resolve. You can mix `Promise`'s and `Guarantee`'s. + */ +public func when(resolved th1: TH1, _ th2: TH2, _ th3: TH3, _ th4: TH4, _ th5: TH5, _ th6: TH6, _ th7: TH7, _ th8: TH8) -> Guarantee<(TH1.R, TH2.R, TH3.R, TH4.R, TH5.R, TH6.R, TH7.R, TH8.R)> { + return _when(resolved: [th1.asVoid(), th2.asVoid(), th3.asVoid(), th4.asVoid(), th5.asVoid(), th6.asVoid(), th7.asVoid(), th8.asVoid()]).map(on: nil) { (th1.result!, th2.result!, th3.result!, th4.result!, th5.result!, th6.result!, th7.result!, th8.result!) } +} + +/** Wait for all provided thenables to resolve. You can mix `Promise`'s and `Guarantee`'s. + */ +public func when(resolved th1: TH1, _ th2: TH2, _ th3: TH3, _ th4: TH4, _ th5: TH5, _ th6: TH6, _ th7: TH7, _ th8: TH8, _ th9: TH9) -> Guarantee<(TH1.R, TH2.R, TH3.R, TH4.R, TH5.R, TH6.R, TH7.R, TH8.R, TH9.R)> { + return _when(resolved: [th1.asVoid(), th2.asVoid(), th3.asVoid(), th4.asVoid(), th5.asVoid(), th6.asVoid(), th7.asVoid(), th8.asVoid(), th9.asVoid()]).map(on: nil) { (th1.result!, th2.result!, th3.result!, th4.result!, th5.result!, th6.result!, th7.result!, th8.result!, th9.result!) } +} + +// MARK: - when(resolved:) iterator + +/** Generate thenables at a limited rate and wait for all to resolve. + + For example: + func downloadFile(url: URL) -> Promise { // ... } - let urls: [URL] = /*…*/ let urlGenerator = urls.makeIterator() - let generator = AnyIterator> { guard url = urlGenerator.next() else { return nil } return downloadFile(url) } - when(resolved: generator, concurrently: 3).done { results in // ... } - + No more than three downloads will occur simultaneously. Downloads will continue if one of them fails - + - Note: The generator is called *serially* on a *background* queue. - Warning: Refer to the warnings on `when(resolved:)` -- Parameter promiseGenerator: Generator of promises. -- Returns: A new promise that resolves once all the provided promises resolve. The array is ordered the same as the input, ie. the result order is *not* resolution order. +- Parameter resolved: Generator of thenables. +- Returns: A new promise that resolves once all the provided thenables resolve. The array is ordered the same as the input, ie. the result order is *not* resolution order. - SeeAlso: `when(resolved:)` */ -public func when(resolved promiseIterator: It, concurrently: Int) - -> Guarantee<[Result]> where It.Element: Thenable { - guard concurrently > 0 else { - return Guarantee.value([Result.failure(PMKError.badInput)]) - } - - var generator = promiseIterator - let root = Guarantee<[Result]>.pending() +public func when(resolved iterator: It, concurrently: Int) -> Guarantee<[It.Element.R]> where It.Element: Thenable { + let concurrently = max(concurrently, 1) + var generator = iterator + let root = Guarantee<[It.Element.R]>.pending() var pendingPromises = 0 var promises: [It.Element] = [] - let barrier = DispatchQueue(label: "org.promisekit.barrier.when", attributes: [.concurrent]) func dequeue() { - guard root.guarantee.isPending else { - return - } // don’t continue dequeueing if root has been rejected - + guard root.guarantee.isPending else { return } // don’t continue dequeueing if root has been rejected var shouldDequeue = false barrier.sync { shouldDequeue = pendingPromises < concurrently } - guard shouldDequeue else { - return - } - + guard shouldDequeue else { return } var promise: It.Element! - barrier.sync(flags: .barrier) { - guard let next = generator.next() else { - return - } - + guard let next = generator.next() else { return } promise = next - pendingPromises += 1 promises.append(next) } func testDone() { barrier.sync { - if pendingPromises == 0 { - #if !swift(>=3.3) || (swift(>=4) && !swift(>=4.1)) - root.resolve(promises.flatMap { $0.result }) - #else - root.resolve(promises.compactMap { $0.result }) - #endif - } + guard pendingPromises == 0 else { return } + root.resolve(promises.compactMap { $0.result }) } } guard promise != nil else { return testDone() } - promise.pipe { _ in barrier.sync(flags: .barrier) { pendingPromises -= 1 } - dequeue() testDone() } - dequeue() } - dequeue() - return root.guarantee } -/// Waits on all provided Guarantees. -public func when(_ guarantees: Guarantee...) -> Guarantee { - return when(guarantees: guarantees) -} - -// Waits on all provided Guarantees. -public func when(guarantees: [Guarantee]) -> Guarantee { - return when(fulfilled: guarantees).recover{ _ in }.asVoid() -} - -//////////////////////////////////////////////////////////// Cancellation - -/** - Wait for all cancellable promises in a set to fulfill. +// MARK: - when(fulfilled:) array - cancellable +/** Wait for all cancellable thenables to fulfill. + For example: - + let p = when(fulfilled: promise1, promise2).then { results in //… }.catch { error in @@ -375,17 +423,17 @@ public func when(guarantees: [Guarantee]) -> Guarantee { } //… - p.cancel() - - - Note: If *any* of the provided promises reject, the returned promise is immediately rejected with that error. - - Warning: In the event of rejection the other promises will continue to resolve and, as per any other promise, will either fulfill or reject. This is the right pattern for `getter` style asynchronous tasks, but often for `setter` tasks (eg. storing data on a server), you most likely will need to wait on all tasks and then act based on which have succeeded and which have failed, in such situations use `when(resolved:)`. - - Parameter promises: The promises upon which to wait before the returned promise resolves. - - Returns: A new promise that resolves when all the provided promises fulfill or one of the provided promises rejects. - - Note: `when` provides `NSProgress`. + + - Note: If *any* of the provided cancellable thenables rejects, the returned cancellable promise is immediately rejected with that error. + - Warning: In the event of rejection the other thenables will continue to resolve. `Guarantee`'s will resolve and `Promise`'s, will either fulfill or reject. This is the right pattern for `getter` style asynchronous tasks, but often for `setter` tasks (eg. storing data on a server), you most likely will need to wait on all tasks and then act based on which have succeeded and which have failed, in such situations use `when(resolved:)`. + - Parameter fulfilled: The cancellable thenables upon which to wait before the returned cancellable promise resolves. + - Returns: A new cancellable promise that resolves when all the provided cancellable thenables fulfill or one of the provided cancellable thenables rejects. + - Note: `when(fulfilled:)` provides `NSProgress`. + - Note: Provided cancellable thenables must be of the same concrete type and have the same associated type `T` as type of their wrapped thenable values. Otherwise use tuple overloads. - SeeAlso: `when(resolved:)` */ -public func when(fulfilled thenables: V...) -> CancellablePromise<[V.U.T]> { +public func when(fulfilled thenables: [CT]) -> CancellablePromise<[CT.U.T]> { let rp = CancellablePromise(when(fulfilled: asThenables(thenables))) for t in thenables { rp.appendCancelContext(from: t) @@ -393,7 +441,13 @@ public func when(fulfilled thenables: V...) -> Cancellab return rp } -public func when(fulfilled thenables: [V]) -> CancellablePromise<[V.U.T]> { +/** Wait for all provided `Void` cancellable thenables to fulfill. + + Convenience function that returns `CancellablePromise` for `Void` cancellable thenables. + - SeeAlso: when(fulfilled:) + - SeeAlso: when(resolved:) +*/ +public func when(fulfilled thenables: [CT]) -> CancellablePromise where CT.U.T == Void { let rp = CancellablePromise(when(fulfilled: asThenables(thenables))) for t in thenables { rp.appendCancelContext(from: t) @@ -401,72 +455,69 @@ public func when(fulfilled thenables: [V]) -> Cancellabl return rp } -/// Wait for all cancellable promises in a set to fulfill. -public func when(fulfilled promises: V...) -> CancellablePromise where V.U.T == Void { - let rp = CancellablePromise(when(fulfilled: asThenables(promises))) - for p in promises { - rp.appendCancelContext(from: p) - } - return rp +// THIS MAKES UNAMBIGUOUS USAGES DUE TO TUPLE ARITY FUNCTIONS +//public func when(fulfilled thenables: CT...) -> CancellablePromise<[CT.U.T]> { +// let rp = CancellablePromise(when(fulfilled: asThenables(thenables))) +// for t in thenables { +// rp.appendCancelContext(from: t) +// } +// return rp +//} + +// MARK: - when(fulfilled:) tuples arity - cancellable + +/** Wait for all provided cancellable thenables to fulfill. +*/ +public func when(fulfilled ct1: CT1, _ ct2: CT2) -> CancellablePromise<(CT1.U.T, CT2.U.T)> { + return when(fulfilled: [ct1.asVoid(), ct2.asVoid()]).map(on: nil) { (ct1.value!, ct2.value!) } } -/// Wait for all cancellable promises in a set to fulfill. -public func when(fulfilled promises: [V]) -> CancellablePromise where V.U.T == Void { - let rp = CancellablePromise(when(fulfilled: asThenables(promises))) - for p in promises { - rp.appendCancelContext(from: p) - } - return rp +/** Wait for all provided cancellable thenables to fulfill. + */ +public func when(fulfilled ct1: CT1, _ ct2: CT2, _ ct3: CT3) -> CancellablePromise<(CT1.U.T, CT2.U.T, CT3.U.T)> { + return when(fulfilled: [ct1.asVoid(), ct2.asVoid(), ct3.asVoid()]).map(on: nil) { (ct1.value!, ct2.value!, ct3.value!) } } -/** - Wait for all cancellable promises in a set to fulfill. +/** Wait for all provided cancellable thenables to fulfill. + */ +public func when(fulfilled ct1: CT1, _ ct2: CT2, _ ct3: CT3, _ ct4: CT4) -> CancellablePromise<(CT1.U.T, CT2.U.T, CT3.U.T, CT4.U.T)> { + return when(fulfilled: [ct1.asVoid(), ct2.asVoid(), ct3.asVoid(), ct4.asVoid()]).map(on: nil) { (ct1.value!, ct2.value!, ct3.value!, ct4.value!) } +} - - Note: by convention the cancellable 'when' functions should not have a 'cancellable' prefix, however the prefix is necessary due to a compiler bug exemplified by the following: - - ```` - This works fine: - 1 func hi(_: String...) { } - 2 func hi(_: String, _: String) { } - 3 hi("hi", "there") +/** Wait for all provided cancellable thenables to fulfill. + */ +public func when(fulfilled ct1: CT1, _ ct2: CT2, _ ct3: CT3, _ ct4: CT4, _ ct5: CT5) -> CancellablePromise<(CT1.U.T, CT2.U.T, CT3.U.T, CT4.U.T, CT5.U.T)> { + return when(fulfilled: [ct1.asVoid(), ct2.asVoid(), ct3.asVoid(), ct4.asVoid(), ct5.asVoid()]).map(on: nil) { (ct1.value!, ct2.value!, ct3.value!, ct4.value!, ct5.value!) } +} - This does not compile: - 1 func hi(_: String...) { } - 2 func hi(_: String, _: String) { } - 3 func hi(_: Int...) { } - 4 func hi(_: Int, _: Int) { } - 5 - 6 hi("hi", "there") // Ambiguous use of 'hi' (lines 1 & 2 are candidates) - 7 hi(1, 2) // Ambiguous use of 'hi' (lines 3 & 4 are candidates) - ```` - - - SeeAlso: `when(fulfilled:,_:)` -*/ -public func cancellableWhen(fulfilled pu: U, _ pv: V) -> CancellablePromise<(U.U.T, V.U.T)> { - return when(fulfilled: [pu.asVoid(), pv.asVoid()]).map(on: nil) { (pu.value!, pv.value!) } +/** Wait for all provided cancellable thenables to fulfill. + */ +public func when(fulfilled ct1: CT1, _ ct2: CT2, _ ct3: CT3, _ ct4: CT4, _ ct5: CT5, _ ct6: CT6) -> CancellablePromise<(CT1.U.T, CT2.U.T, CT3.U.T, CT4.U.T, CT5.U.T, CT6.U.T)> { + return when(fulfilled: [ct1.asVoid(), ct2.asVoid(), ct3.asVoid(), ct4.asVoid(), ct5.asVoid(), ct6.asVoid()]).map(on: nil) { (ct1.value!, ct2.value!, ct3.value!, ct4.value!, ct5.value!, ct6.value!) } } -/// Wait for all cancellable promises in a set to fulfill. -/// - SeeAlso: `when(fulfilled:,_:)` -public func cancellableWhen(fulfilled pu: U, _ pv: V, _ pw: W) -> CancellablePromise<(U.U.T, V.U.T, W.U.T)> { - return when(fulfilled: [pu.asVoid(), pv.asVoid(), pw.asVoid()]).map(on: nil) { (pu.value!, pv.value!, pw.value!) } +/** Wait for all provided cancellable thenables to fulfill. + */ +public func when(fulfilled ct1: CT1, _ ct2: CT2, _ ct3: CT3, _ ct4: CT4, _ ct5: CT5, _ ct6: CT6, _ ct7: CT7) -> CancellablePromise<(CT1.U.T, CT2.U.T, CT3.U.T, CT4.U.T, CT5.U.T, CT6.U.T, CT7.U.T)> { + return when(fulfilled: [ct1.asVoid(), ct2.asVoid(), ct3.asVoid(), ct4.asVoid(), ct5.asVoid(), ct6.asVoid(), ct7.asVoid()]).map(on: nil) { (ct1.value!, ct2.value!, ct3.value!, ct4.value!, ct5.value!, ct6.value!, ct7.value!) } } -/// Wait for all cancellable promises in a set to fulfill. -/// - SeeAlso: `when(fulfilled:,_:)` -public func cancellableWhen(fulfilled pu: U, _ pv: V, _ pw: W, _ px: X) -> CancellablePromise<(U.U.T, V.U.T, W.U.T, X.U.T)> { - return when(fulfilled: [pu.asVoid(), pv.asVoid(), pw.asVoid(), px.asVoid()]).map(on: nil) { (pu.value!, pv.value!, pw.value!, px.value!) } +/** Wait for all provided cancellable thenables to fulfill. + */ +public func when(fulfilled ct1: CT1, _ ct2: CT2, _ ct3: CT3, _ ct4: CT4, _ ct5: CT5, _ ct6: CT6, _ ct7: CT7, _ ct8: CT8) -> CancellablePromise<(CT1.U.T, CT2.U.T, CT3.U.T, CT4.U.T, CT5.U.T, CT6.U.T, CT7.U.T, CT8.U.T)> { + return when(fulfilled: [ct1.asVoid(), ct2.asVoid(), ct3.asVoid(), ct4.asVoid(), ct5.asVoid(), ct6.asVoid(), ct7.asVoid(), ct8.asVoid()]).map(on: nil) { (ct1.value!, ct2.value!, ct3.value!, ct4.value!, ct5.value!, ct6.value!, ct7.value!, ct8.value!) } } -/// Wait for all cancellable promises in a set to fulfill. -/// - SeeAlso: `when(fulfilled:,_:)` -public func cancellableWhen(fulfilled pu: U, _ pv: V, _ pw: W, _ px: X, _ py: Y) -> CancellablePromise<(U.U.T, V.U.T, W.U.T, X.U.T, Y.U.T)> { - return when(fulfilled: [pu.asVoid(), pv.asVoid(), pw.asVoid(), px.asVoid(), py.asVoid()]).map(on: nil) { (pu.value!, pv.value!, pw.value!, px.value!, py.value!) } +/** Wait for all provided cancellable thenables to fulfill. + */ +public func when(fulfilled ct1: CT1, _ ct2: CT2, _ ct3: CT3, _ ct4: CT4, _ ct5: CT5, _ ct6: CT6, _ ct7: CT7, _ ct8: CT8, _ ct9: CT9) -> CancellablePromise<(CT1.U.T, CT2.U.T, CT3.U.T, CT4.U.T, CT5.U.T, CT6.U.T, CT7.U.T, CT8.U.T, CT9.U.T)> { + return when(fulfilled: [ct1.asVoid(), ct2.asVoid(), ct3.asVoid(), ct4.asVoid(), ct5.asVoid(), ct6.asVoid(), ct7.asVoid(), ct8.asVoid(), ct9.asVoid()]).map(on: nil) { (ct1.value!, ct2.value!, ct3.value!, ct4.value!, ct5.value!, ct6.value!, ct7.value!, ct8.value!, ct9.value!) } } -/** - Generate cancellable promises at a limited rate and wait for all to fulfill. Call `cancel` on the returned promise to cancel all currently pending promises. +// MARK: - when(fulfilled:) iterator - cancellable +/** Generate cancellable thenables at a limited rate and wait for all to fulfill. Call `cancel` on the returned cancellable promise to cancel all currently pending cancellable thenables. + For example: func downloadFile(url: URL) -> CancellablePromise { @@ -475,14 +526,12 @@ public func cancellableWhen> { guard url = urlGenerator.next() else { return nil } return downloadFile(url) } - let promise = when(generator, concurrently: 3).done { datas in // … } @@ -490,36 +539,33 @@ public func cancellableWhen(fulfilled promiseIterator: It, concurrently: Int) -> CancellablePromise<[It.Element.U.T]> where It.Element: CancellableThenable { +public func when(fulfilled iterator: It, concurrently: Int) -> CancellablePromise<[It.Element.U.T]> where It.Element: CancellableThenable { guard concurrently > 0 else { return CancellablePromise(error: PMKError.badInput) } - - var pi = promiseIterator - var generatedPromises: [CancellablePromise] = [] + var pi = iterator + var generatedPromises: [It.Element] = [] var rootPromise: CancellablePromise<[It.Element.U.T]>! - let generator = AnyIterator> { - guard let promise = pi.next() as? CancellablePromise else { + let generator = AnyIterator { + guard let cancellableThenable = pi.next() else { return nil } if let root = rootPromise { - root.appendCancelContext(from: promise) + root.appendCancelContext(from: cancellableThenable) } else { - generatedPromises.append(promise) + generatedPromises.append(cancellableThenable) } - return promise.promise + return cancellableThenable.thenable } rootPromise = CancellablePromise(when(fulfilled: generator, concurrently: concurrently)) @@ -529,12 +575,14 @@ public func when(fulfilled promiseIterator: It, concurrent return rootPromise } -/** - Waits on all provided cancellable promises. - - `when(fulfilled:)` rejects as soon as one of the provided promises rejects. `when(resolved:)` waits on all provided promises and *never* rejects. When cancelled, all promises will attempt to be cancelled and those that are successfully cancelled will have a result of - PMKError.cancelled. +// MARK: - when(resolved:) array - cancellable +/** Wait for all provided cancellable thenables to resolve. + + `when(fulfilled:)` rejects as soon as one of the provided cancellable thenables rejects. `when(resolved:)` waits on all provided cancellable thenables and *never* rejects. When cancelled, all thenables will attempt to be cancelled and those that are successfully cancelled will have a result of `PMKError.cancelled`. + + For example: + let p = when(resolved: promise1, promise2, promise3, cancel: context).then { results in for result in results where case .fulfilled(let value) { //… @@ -544,39 +592,147 @@ public func when(fulfilled promiseIterator: It, concurrent } //… - p.cancel() - - Returns: A new promise that resolves once all the provided promises resolve. The array is ordered the same as the input, ie. the result order is *not* resolution order. - - Note: Any promises that error are implicitly consumed. - - Remark: Doesn't take CancellableThenable due to protocol associatedtype paradox + - Returns: A new cancellable promise that resolves once all the provided cancellable thenables resolve. The array is ordered the same as the input, ie. the result order is *not* resolution order. + - Note: Any cancellable thenables that error out are implicitly consumed. + - Note: Provided cancellable thenables must be of the same concrete type and have the same associated type `R` as type of their wrapped thenable results. Otherwise use tuple overloads. + - SeeAlso: `when(resolved:)` */ -public func when(resolved promises: CancellablePromise...) -> CancellablePromise<[Result]> { - return when(resolved: promises) +public func when(resolved cancellableThenables: [CT]) -> CancellablePromise<[CT.U.R]> { + let rp = CancellablePromise(when(resolved: asThenables(cancellableThenables))) + for thenable in cancellableThenables { + rp.appendCancelContext(from: thenable) + } + return rp } -/// Waits on all provided cancellable promises. -/// - SeeAlso: `when(resolved:)` -public func when(resolved promises: [CancellablePromise]) -> CancellablePromise<[Result]> { - let rp = CancellablePromise(when(resolved: asPromises(promises))) - for p in promises { - rp.appendCancelContext(from: p) +/** Wait for all provided `Void` cancellable thenables to resolve. + + Convenience function that returns `CancellablePromise` for `Void` cancellable thenables. + - SeeAlso: when(fulfilled:) + - SeeAlso: when(resolved:) +*/ +public func when(resolved cancellableThenables: [CT]) -> CancellablePromise where CT.U.T == Void { + let rp = CancellablePromise(when(resolved: asThenables(cancellableThenables))) + for thenable in cancellableThenables { + rp.appendCancelContext(from: thenable) } return rp } -func asThenables(_ cancellableThenables: [V]) -> [V.U] { - var thenables: [V.U] = [] - for ct in cancellableThenables { - thenables.append(ct.thenable) - } - return thenables +// THIS MAKES UNAMBIGUOUS USAGES DUE TO TUPLE ARITY FUNCTIONS +//public func when(resolved cancellableThenables: CT...) -> CancellablePromise<[CT.U.R]> { +// return when(resolved: cancellableThenables) +//} + +// MARK: - when(resolved:) tuples arity - cancellable + +/** Wait for all provided cancellable thenables to resolve. +*/ +public func when(resolved ct1: CT1, _ ct2: CT2) -> CancellablePromise<(CT1.U.R, CT2.U.R)> { + return when(resolved: [ct1.asVoid(), ct2.asVoid()]).map(on: nil) { (ct1.result!, ct2.result!) } +} + +/** Wait for all provided cancellable thenables to resolve. + */ +public func when(resolved ct1: CT1, _ ct2: CT2, _ ct3: CT3) -> CancellablePromise<(CT1.U.R, CT2.U.R, CT3.U.R)> { + return when(resolved: [ct1.asVoid(), ct2.asVoid(), ct3.asVoid()]).map(on: nil) { (ct1.result!, ct2.result!, ct3.result!) } } -func asPromises(_ cancellablePromises: [CancellablePromise]) -> [Promise] { - var promises = [Promise]() - for cp in cancellablePromises { - promises.append(cp.promise) +/** Wait for all provided cancellable thenables to resolve. + */ +public func when(resolved ct1: CT1, _ ct2: CT2, _ ct3: CT3, _ ct4: CT4) -> CancellablePromise<(CT1.U.R, CT2.U.R, CT3.U.R, CT4.U.R)> { + return when(resolved: [ct1.asVoid(), ct2.asVoid(), ct3.asVoid(), ct4.asVoid()]).map(on: nil) { (ct1.result!, ct2.result!, ct3.result!, ct4.result!) } +} + +/** Wait for all provided cancellable thenables to resolve. + */ +public func when(resolved ct1: CT1, _ ct2: CT2, _ ct3: CT3, _ ct4: CT4, _ ct5: CT5) -> CancellablePromise<(CT1.U.R, CT2.U.R, CT3.U.R, CT4.U.R, CT5.U.R)> { + return when(resolved: [ct1.asVoid(), ct2.asVoid(), ct3.asVoid(), ct4.asVoid(), ct5.asVoid()]).map(on: nil) { (ct1.result!, ct2.result!, ct3.result!, ct4.result!, ct5.result!) } +} + +/** Wait for all provided cancellable thenables to resolve. + */ +public func when(resolved ct1: CT1, _ ct2: CT2, _ ct3: CT3, _ ct4: CT4, _ ct5: CT5, _ ct6: CT6) -> CancellablePromise<(CT1.U.R, CT2.U.R, CT3.U.R, CT4.U.R, CT5.U.R, CT6.U.R)> { + return when(resolved: [ct1.asVoid(), ct2.asVoid(), ct3.asVoid(), ct4.asVoid(), ct5.asVoid(), ct6.asVoid()]).map(on: nil) { (ct1.result!, ct2.result!, ct3.result!, ct4.result!, ct5.result!, ct6.result!) } +} + +/** Wait for all provided cancellable thenables to resolve. + */ +public func when(resolved ct1: CT1, _ ct2: CT2, _ ct3: CT3, _ ct4: CT4, _ ct5: CT5, _ ct6: CT6, _ ct7: CT7) -> CancellablePromise<(CT1.U.R, CT2.U.R, CT3.U.R, CT4.U.R, CT5.U.R, CT6.U.R, CT7.U.R)> { + return when(resolved: [ct1.asVoid(), ct2.asVoid(), ct3.asVoid(), ct4.asVoid(), ct5.asVoid(), ct6.asVoid(), ct7.asVoid()]).map(on: nil) { (ct1.result!, ct2.result!, ct3.result!, ct4.result!, ct5.result!, ct6.result!, ct7.result!) } +} + +/** Wait for all provided cancellable thenables to resolve. + */ +public func when(resolved ct1: CT1, _ ct2: CT2, _ ct3: CT3, _ ct4: CT4, _ ct5: CT5, _ ct6: CT6, _ ct7: CT7, _ ct8: CT8) -> CancellablePromise<(CT1.U.R, CT2.U.R, CT3.U.R, CT4.U.R, CT5.U.R, CT6.U.R, CT7.U.R, CT8.U.R)> { + return when(resolved: [ct1.asVoid(), ct2.asVoid(), ct3.asVoid(), ct4.asVoid(), ct5.asVoid(), ct6.asVoid(), ct7.asVoid(), ct8.asVoid()]).map(on: nil) { (ct1.result!, ct2.result!, ct3.result!, ct4.result!, ct5.result!, ct6.result!, ct7.result!, ct8.result!) } +} + +/** Wait for all provided cancellable thenables to resolve. + */ +public func when(resolved ct1: CT1, _ ct2: CT2, _ ct3: CT3, _ ct4: CT4, _ ct5: CT5, _ ct6: CT6, _ ct7: CT7, _ ct8: CT8, _ ct9: CT9) -> CancellablePromise<(CT1.U.R, CT2.U.R, CT3.U.R, CT4.U.R, CT5.U.R, CT6.U.R, CT7.U.R, CT8.U.R, CT9.U.R)> { + return when(resolved: [ct1.asVoid(), ct2.asVoid(), ct3.asVoid(), ct4.asVoid(), ct5.asVoid(), ct6.asVoid(), ct7.asVoid(), ct8.asVoid(), ct9.asVoid()]).map(on: nil) { (ct1.result!, ct2.result!, ct3.result!, ct4.result!, ct5.result!, ct6.result!, ct7.result!, ct8.result!, ct9.result!) } +} + +// MARK: - when(resolved:) iterator - cancellable + +/** Generate cancellable thenables at a limited rate and wait for all to resolve. Call `cancel` on the returned cancellable promise to cancel all currently pending cancellable thenables. + + For example: + + func downloadFile(url: URL) -> CancellablePromise { + // … + } + + let urls: [URL] = /*…*/ + let urlGenerator = urls.makeIterator() + let generator = AnyIterator> { + guard url = urlGenerator.next() else { + return nil + } + return downloadFile(url) + } + let promise = when(generator, concurrently: 3).done { datas in + // … + } + + // … + + promise.cancel() + + No more than three downloads will occur simultaneously. + + - Note: The generator is called *serially* on a *background* queue. + - Warning: Refer to the warnings on `when(fulfilled:)` + - Parameter fulfilled: Generator of cancellable thenables. + - Returns: A new cancellable promise that resolves when all the provided cancellable thenables fulfill or one of the provided cancellable thenables rejects. + - SeeAlso: `when(fulfilled:)` + */ +public func when(resolved iterator: It, concurrently: Int) -> CancellablePromise<[It.Element.U.R]> where It.Element: CancellableThenable { + guard concurrently > 0 else { + return CancellablePromise(error: PMKError.badInput) } - return promises + var pi = iterator + var generatedPromises: [It.Element] = [] + var rootPromise: CancellablePromise<[It.Element.U.R]>! + + let generator = AnyIterator { + guard let cancellableThenable = pi.next() else { + return nil + } + if let root = rootPromise { + root.appendCancelContext(from: cancellableThenable) + } else { + generatedPromises.append(cancellableThenable) + } + return cancellableThenable.thenable + } + + rootPromise = CancellablePromise(when(resolved: generator, concurrently: concurrently)) + for p in generatedPromises { + rootPromise.appendCancelContext(from: p) + } + return rootPromise } diff --git a/Tests/Cancel/WhenResolvedTests.swift b/Tests/Cancel/WhenResolvedTests.swift index 6be9f6295..52a577b4b 100644 --- a/Tests/Cancel/WhenResolvedTests.swift +++ b/Tests/Cancel/WhenResolvedTests.swift @@ -9,7 +9,7 @@ class JoinTests: XCTestCase { let successPromise = CancellablePromise() var joinFinished = false - when(resolved: successPromise).done(on: nil) { _ in joinFinished = true }.cancel() + when(resolved: [successPromise]).done(on: nil) { joinFinished = true }.cancel() XCTAssert(joinFinished, "Join immediately finishes on fulfilled promise") let promise2 = Promise.value(2) diff --git a/Tests/Cancel/WhenTests.swift b/Tests/Cancel/WhenTests.swift index ecf512e4c..dd705bfb0 100644 --- a/Tests/Cancel/WhenTests.swift +++ b/Tests/Cancel/WhenTests.swift @@ -1,31 +1,32 @@ -import PromiseKit import Dispatch +import PromiseKit import XCTest class WhenTests: XCTestCase { func testEmpty() { - let e1 = expectation(description: "") + let ex = expectation(description: "") + ex.expectedFulfillmentCount = 2 let promises: [CancellablePromise] = [] + when(fulfilled: promises).done { _ in XCTFail() }.catch(policy: .allErrors) { - $0.isCancelled ? e1.fulfill() : XCTFail() + $0.isCancelled ? ex.fulfill() : XCTFail() }.cancel() - let e2 = expectation(description: "") when(resolved: promises).done { _ in XCTFail() - e2.fulfill() }.catch(policy: .allErrors) { - $0.isCancelled ? e2.fulfill() : XCTFail() + $0.isCancelled ? ex.fulfill() : XCTFail() }.cancel() - wait(for: [e1, e2], timeout: 5) + waitForExpectations() } func testInt() { - let e1 = expectation(description: "") + let ex = expectation(description: "") + ex.expectedFulfillmentCount = 2 let p1 = Promise.value(1).cancellize() let p2 = Promise.value(2).cancellize() let p3 = Promise.value(3).cancellize() @@ -34,121 +35,497 @@ class WhenTests: XCTestCase { when(fulfilled: [p1, p2, p3, p4]).done { _ in XCTFail() }.catch(policy: .allErrors) { - $0.isCancelled ? e1.fulfill() : XCTFail() + $0.isCancelled ? ex.fulfill() : XCTFail() }.cancel() - waitForExpectations(timeout: 5, handler: nil) + + when(resolved: [p1, p2, p3, p4]).done { _ in + XCTFail() + }.catch(policy: .allErrors) { + $0.isCancelled ? ex.fulfill() : XCTFail() + }.cancel() + + waitForExpectations() } - func testIntAlt() { - let e1 = expectation(description: "") + func testBinaryTuple() { + let ex = expectation(description: "") + ex.expectedFulfillmentCount = 2 let p1 = Promise.value(1).cancellize() - let p2 = Promise.value(2).cancellize() - let p3 = Promise.value(3).cancellize() - let p4 = Promise.value(4).cancellize() + let p2 = Promise.value("abc").cancellize() - when(fulfilled: p1, p2, p3, p4).done { _ in + when(fulfilled: p1, p2).done{ v1, v2 in + XCTAssertEqual(v1, 1) + XCTAssertEqual(v2, "abc") + ex.fulfill() + }.silenceWarning() + + when(resolved: p1, p2).done{ r1, r2 in + XCTAssertEqual(try r1.get(), 1) + XCTAssertEqual(try r2.get(), "abc") + ex.fulfill() + }.silenceWarning() + + waitForExpectations() + } + + func testBinaryTupleCancel() { + let ex = expectation(description: "") + ex.expectedFulfillmentCount = 2 + let p1 = Promise.value(1).cancellize() + let p2 = Promise.value("abc").cancellize() + + when(fulfilled: p1, p2).done{ _, _ in + XCTFail() + }.catch(policy: .allErrors) { + $0.isCancelled ? ex.fulfill() : XCTFail() + }.cancel() + + when(resolved: p1, p2).done{ _, _ in XCTFail() }.catch(policy: .allErrors) { - $0.isCancelled ? e1.fulfill() : XCTFail() + $0.isCancelled ? ex.fulfill() : XCTFail() }.cancel() - waitForExpectations(timeout: 5, handler: nil) + + waitForExpectations() } + + func testTernaryTuple() { + let ex = expectation(description: "") + ex.expectedFulfillmentCount = 2 + let p1 = Promise.value(1).cancellize() + let p2 = Promise.value("abc").cancellize() + let p3 = Promise.value(1.0).cancellize() + + when(fulfilled: p1, p2, p3).done{ v1, v2, v3 in + XCTAssertEqual(v1, 1) + XCTAssertEqual(v2, "abc") + XCTAssertEqual(v3, 1.0) + ex.fulfill() + }.silenceWarning() - func testDoubleTupleSucceed() { - let e1 = expectation(description: "") + when(resolved: p1, p2, p3).done { r1, r2, r3 in + XCTAssertEqual(try r1.get(), 1) + XCTAssertEqual(try r2.get(), "abc") + XCTAssertEqual(try r3.get(), 1.0) + ex.fulfill() + }.silenceWarning() + + waitForExpectations() + } + + func testTernaryTupleCancel() { + let ex = expectation(description: "") + ex.expectedFulfillmentCount = 2 let p1 = Promise.value(1).cancellize() let p2 = Promise.value("abc").cancellize() - cancellableWhen(fulfilled: p1, p2).done{ x, y in - XCTAssertEqual(x, 1) - XCTAssertEqual(y, "abc") - e1.fulfill() + let p3 = Promise.value(1.0).cancellize() + + when(fulfilled: p1, p2, p3).done { _, _, _ in + XCTFail() + }.catch(policy: .allErrors) { + $0.isCancelled ? ex.fulfill() : XCTFail() + }.cancel() + + when(resolved: p1, p2, p3).done { _, _, _ in + XCTFail() + }.catch(policy: .allErrors) { + $0.isCancelled ? ex.fulfill() : XCTFail() + }.cancel() + + waitForExpectations() + } + + func testQuaternaryTuple() { + let ex = expectation(description: "") + ex.expectedFulfillmentCount = 2 + let p1 = Promise.value(1).cancellize() + let p2 = Promise.value("abc").cancellize() + let p3 = Promise.value(1.0).cancellize() + let p4 = Promise.value(true).cancellize() + + when(fulfilled: p1, p2, p3, p4).done{ v1, v2, v3, v4 in + XCTAssertEqual(v1, 1) + XCTAssertEqual(v2, "abc") + XCTAssertEqual(v3, 1.0) + XCTAssertEqual(v4, true) + ex.fulfill() }.silenceWarning() - waitForExpectations(timeout: 5, handler: nil) + + when(resolved: p1, p2, p3, p4).done { r1, r2, r3, r4 in + XCTAssertEqual(try r1.get(), 1) + XCTAssertEqual(try r2.get(), "abc") + XCTAssertEqual(try r3.get(), 1.0) + XCTAssertEqual(try r4.get(), true) + ex.fulfill() + }.silenceWarning() + + waitForExpectations() } - func testDoubleTupleCancel() { - let e1 = expectation(description: "") + func testQuaternaryTupleCancel() { + let ex = expectation(description: "") + ex.expectedFulfillmentCount = 2 let p1 = Promise.value(1).cancellize() let p2 = Promise.value("abc").cancellize() - cancellableWhen(fulfilled: p1, p2).done{ _, _ in + let p3 = Promise.value(1.0).cancellize() + let p4 = Promise.value(true).cancellize() + + when(fulfilled: p1, p2, p3, p4).done { _, _, _, _ in + XCTFail() + }.catch(policy: .allErrors) { + $0.isCancelled ? ex.fulfill() : XCTFail() + }.cancel() + + when(resolved: p1, p2, p3, p4).done { _, _, _, _ in + XCTFail() + }.catch(policy: .allErrors) { + $0.isCancelled ? ex.fulfill() : XCTFail() + }.cancel() + + waitForExpectations() + } + + func testQuinaryTuple() { + let ex = expectation(description: "") + ex.expectedFulfillmentCount = 2 + let p1 = Promise.value(1).cancellize() + let p2 = Promise.value("abc").cancellize() + let p3 = Promise.value(1.0).cancellize() + let p4 = Promise.value(true).cancellize() + let p5 = Promise.value("a" as Character).cancellize() + + when(fulfilled: p1, p2, p3, p4, p5).done{ v1, v2, v3, v4, v5 in + XCTAssertEqual(v1, 1) + XCTAssertEqual(v2, "abc") + XCTAssertEqual(v3, 1.0) + XCTAssertEqual(v4, true) + XCTAssertEqual(v5, "a" as Character) + ex.fulfill() + }.silenceWarning() + + when(resolved: p1, p2, p3, p4, p5).done { r1, r2, r3, r4, r5 in + XCTAssertEqual(try r1.get(), 1) + XCTAssertEqual(try r2.get(), "abc") + XCTAssertEqual(try r3.get(), 1.0) + XCTAssertEqual(try r4.get(), true) + XCTAssertEqual(try r5.get(), "a" as Character) + ex.fulfill() + }.silenceWarning() + + waitForExpectations() + } + + func testQuinaryTupleCancel() { + let ex = expectation(description: "") + ex.expectedFulfillmentCount = 2 + let p1 = Promise.value(1).cancellize() + let p2 = Promise.value("abc").cancellize() + let p3 = Promise.value(1.0).cancellize() + let p4 = Promise.value(true).cancellize() + let p5 = Promise.value("a" as Character).cancellize() + + when(fulfilled: p1, p2, p3, p4, p5).done { _, _, _, _, _ in + XCTFail() + }.catch(policy: .allErrors) { + $0.isCancelled ? ex.fulfill() : XCTFail() + }.cancel() + + when(resolved: p1, p2, p3, p4, p5).done { _, _, _, _, _ in + XCTFail() + }.catch(policy: .allErrors) { + $0.isCancelled ? ex.fulfill() : XCTFail() + }.cancel() + + waitForExpectations() + } + + func testSenaryTuple() { + let ex = expectation(description: "") + ex.expectedFulfillmentCount = 2 + let p1 = Promise.value(1).cancellize() + let p2 = Promise.value("abc").cancellize() + let p3 = Promise.value(1.0).cancellize() + let p4 = Promise.value(true).cancellize() + let p5 = Promise.value("a" as Character).cancellize() + let p6 = Promise.value(CGFloat(6)).cancellize() + + when(fulfilled: p1, p2, p3, p4, p5, p6).done { v1, v2, v3, v4, v5, v6 in + XCTAssertEqual(v1, 1) + XCTAssertEqual(v2, "abc") + XCTAssertEqual(v3, 1.0) + XCTAssertEqual(v4, true) + XCTAssertEqual(v5, "a" as Character) + XCTAssertEqual(v6, CGFloat(6)) + ex.fulfill() + }.silenceWarning() + + when(resolved: p1, p2, p3, p4, p5, p6).done { r1, r2, r3, r4, r5, r6 in + XCTAssertEqual(try r1.get(), 1) + XCTAssertEqual(try r2.get(), "abc") + XCTAssertEqual(try r3.get(), 1.0) + XCTAssertEqual(try r4.get(), true) + XCTAssertEqual(try r5.get(), "a" as Character) + XCTAssertEqual(try r6.get(), CGFloat(6)) + ex.fulfill() + }.silenceWarning() + + waitForExpectations() + } + + func testSenaryTupleCancel() { + let ex = expectation(description: "") + ex.expectedFulfillmentCount = 2 + let p1 = Promise.value(1).cancellize() + let p2 = Promise.value("abc").cancellize() + let p3 = Promise.value(1.0).cancellize() + let p4 = Promise.value(true).cancellize() + let p5 = Promise.value("a" as Character).cancellize() + let p6 = Promise.value(CGFloat(6)).cancellize() + + when(fulfilled: p1, p2, p3, p4, p5, p6).done { _, _, _, _, _, _ in + XCTFail() + }.catch(policy: .allErrors) { + $0.isCancelled ? ex.fulfill() : XCTFail() + }.cancel() + + when(resolved: p1, p2, p3, p4, p5, p6).done { _, _, _, _, _, _ in XCTFail() }.catch(policy: .allErrors) { - $0.isCancelled ? e1.fulfill() : XCTFail() + $0.isCancelled ? ex.fulfill() : XCTFail() }.cancel() - waitForExpectations(timeout: 5, handler: nil) + + waitForExpectations() } - func testTripleTuple() { - let e1 = expectation(description: "") + func testSeptenaryTuple() { + let ex = expectation(description: "") + ex.expectedFulfillmentCount = 2 let p1 = Promise.value(1).cancellize() let p2 = Promise.value("abc").cancellize() let p3 = Promise.value(1.0).cancellize() - cancellableWhen(fulfilled: p1, p2, p3).done { _, _, _ in + let p4 = Promise.value(true).cancellize() + let p5 = Promise.value("a" as Character).cancellize() + let p6 = Promise.value(CGFloat(6)).cancellize() + let p7 = Promise.value(1 as Int?).cancellize() + + when(fulfilled: p1, p2, p3, p4, p5, p6, p7).done { v1, v2, v3, v4, v5, v6, v7 in + XCTAssertEqual(v1, 1) + XCTAssertEqual(v2, "abc") + XCTAssertEqual(v3, 1.0) + XCTAssertEqual(v4, true) + XCTAssertEqual(v5, "a" as Character) + XCTAssertEqual(v6, CGFloat(6)) + XCTAssertEqual(v7, 1 as Int?) + ex.fulfill() + }.silenceWarning() + + when(resolved: p1, p2, p3, p4, p5, p6, p7).done { r1, r2, r3, r4, r5, r6, r7 in + XCTAssertEqual(try r1.get(), 1) + XCTAssertEqual(try r2.get(), "abc") + XCTAssertEqual(try r3.get(), 1.0) + XCTAssertEqual(try r4.get(), true) + XCTAssertEqual(try r5.get(), "a" as Character) + XCTAssertEqual(try r6.get(), CGFloat(6)) + XCTAssertEqual(try r7.get(), 1 as Int?) + ex.fulfill() + }.silenceWarning() + + waitForExpectations() + } + + func testSeptenaryTupleCancel() { + let ex = expectation(description: "") + ex.expectedFulfillmentCount = 2 + let p1 = Promise.value(1).cancellize() + let p2 = Promise.value("abc").cancellize() + let p3 = Promise.value(1.0).cancellize() + let p4 = Promise.value(true).cancellize() + let p5 = Promise.value("a" as Character).cancellize() + let p6 = Promise.value(CGFloat(6)).cancellize() + let p7 = Promise.value(1 as Int?).cancellize() + + when(fulfilled: p1, p2, p3, p4, p5, p6, p7).done { _, _, _, _, _, _, _ in XCTFail() }.catch(policy: .allErrors) { - $0.isCancelled ? e1.fulfill() : XCTFail() + $0.isCancelled ? ex.fulfill() : XCTFail() }.cancel() - waitForExpectations(timeout: 5, handler: nil) + + when(resolved: p1, p2, p3, p4, p5, p6, p7).done { _, _, _, _, _, _, _ in + XCTFail() + }.catch(policy: .allErrors) { + $0.isCancelled ? ex.fulfill() : XCTFail() + }.cancel() + + waitForExpectations() + } + + func testOctonaryTuple() { + let ex = expectation(description: "") + ex.expectedFulfillmentCount = 2 + let p1 = Promise.value(1).cancellize() + let p2 = Promise.value("abc").cancellize() + let p3 = Promise.value(1.0).cancellize() + let p4 = Promise.value(true).cancellize() + let p5 = Promise.value("a" as Character).cancellize() + let p6 = Promise.value(CGFloat(6)).cancellize() + let p7 = Promise.value(1 as Int?).cancellize() + let p8 = Promise.value("abc" as String?).cancellize() + + when(fulfilled: p1, p2, p3, p4, p5, p6, p7, p8).done { v1, v2, v3, v4, v5, v6, v7, v8 in + XCTAssertEqual(v1, 1) + XCTAssertEqual(v2, "abc") + XCTAssertEqual(v3, 1.0) + XCTAssertEqual(v4, true) + XCTAssertEqual(v5, "a" as Character) + XCTAssertEqual(v6, CGFloat(6)) + XCTAssertEqual(v7, 1 as Int?) + XCTAssertEqual(v8, "abc" as String?) + ex.fulfill() + }.silenceWarning() + + when(resolved: p1, p2, p3, p4, p5, p6, p7, p8).done { r1, r2, r3, r4, r5, r6, r7, r8 in + XCTAssertEqual(try r1.get(), 1) + XCTAssertEqual(try r2.get(), "abc") + XCTAssertEqual(try r3.get(), 1.0) + XCTAssertEqual(try r4.get(), true) + XCTAssertEqual(try r5.get(), "a" as Character) + XCTAssertEqual(try r6.get(), CGFloat(6)) + XCTAssertEqual(try r7.get(), 1 as Int?) + XCTAssertEqual(try r8.get(), "abc" as String?) + ex.fulfill() + }.silenceWarning() + + waitForExpectations() } - func testQuadrupleTuple() { - let e1 = expectation(description: "") + func testOctonaryTupleCancel() { + let ex = expectation(description: "") + ex.expectedFulfillmentCount = 2 let p1 = Promise.value(1).cancellize() let p2 = Promise.value("abc").cancellize() let p3 = Promise.value(1.0).cancellize() let p4 = Promise.value(true).cancellize() - cancellableWhen(fulfilled: p1, p2, p3, p4).done { _, _, _, _ in + let p5 = Promise.value("a" as Character).cancellize() + let p6 = Promise.value(CGFloat(6)).cancellize() + let p7 = Promise.value(1 as Int?).cancellize() + let p8 = Promise.value("abc" as String?).cancellize() + + when(fulfilled: p1, p2, p3, p4, p5, p6, p7, p8).done { _, _, _, _, _, _, _, _ in XCTFail() }.catch(policy: .allErrors) { - $0.isCancelled ? e1.fulfill() : XCTFail() + $0.isCancelled ? ex.fulfill() : XCTFail() }.cancel() - waitForExpectations(timeout: 5, handler: nil) + + when(resolved: p1, p2, p3, p4, p5, p6, p7, p8).done { _, _, _, _, _, _, _, _ in + XCTFail() + }.catch(policy: .allErrors) { + $0.isCancelled ? ex.fulfill() : XCTFail() + }.cancel() + + waitForExpectations() } - func testQuintupleTuple() { - let e1 = expectation(description: "") + func testNovenaryTuple() { + let ex = expectation(description: "") + ex.expectedFulfillmentCount = 2 let p1 = Promise.value(1).cancellize() let p2 = Promise.value("abc").cancellize() let p3 = Promise.value(1.0).cancellize() let p4 = Promise.value(true).cancellize() let p5 = Promise.value("a" as Character).cancellize() - cancellableWhen(fulfilled: p1, p2, p3, p4, p5).done { _, _, _, _, _ in + let p6 = Promise.value(CGFloat(6)).cancellize() + let p7 = Promise.value(1 as Int?).cancellize() + let p8 = Promise.value("abc" as String?).cancellize() + let p9 = Promise.value(nil as Double?).cancellize() + + when(fulfilled: p1, p2, p3, p4, p5, p6, p7, p8, p9).done { v1, v2, v3, v4, v5, v6, v7, v8, v9 in + XCTAssertEqual(v1, 1) + XCTAssertEqual(v2, "abc") + XCTAssertEqual(v3, 1.0) + XCTAssertEqual(v4, true) + XCTAssertEqual(v5, "a" as Character) + XCTAssertEqual(v6, CGFloat(6)) + XCTAssertEqual(v7, 1 as Int?) + XCTAssertEqual(v8, "abc" as String?) + XCTAssertEqual(v9, nil) + ex.fulfill() + }.silenceWarning() + + when(resolved: p1, p2, p3, p4, p5, p6, p7, p8, p9).done { r1, r2, r3, r4, r5, r6, r7, r8, r9 in + XCTAssertEqual(try r1.get(), 1) + XCTAssertEqual(try r2.get(), "abc") + XCTAssertEqual(try r3.get(), 1.0) + XCTAssertEqual(try r4.get(), true) + XCTAssertEqual(try r5.get(), "a" as Character) + XCTAssertEqual(try r6.get(), CGFloat(6)) + XCTAssertEqual(try r7.get(), 1 as Int?) + XCTAssertEqual(try r8.get(), "abc" as String?) + XCTAssertEqual(try r9.get(), nil) + ex.fulfill() + }.silenceWarning() + + waitForExpectations() + } + + func testNovenaryTupleCancel() { + let ex = expectation(description: "") + ex.expectedFulfillmentCount = 2 + let p1 = Promise.value(1).cancellize() + let p2 = Promise.value("abc").cancellize() + let p3 = Promise.value(1.0).cancellize() + let p4 = Promise.value(true).cancellize() + let p5 = Promise.value("a" as Character).cancellize() + let p6 = Promise.value(CGFloat(6)).cancellize() + let p7 = Promise.value(1 as Int?).cancellize() + let p8 = Promise.value("abc" as String?).cancellize() + let p9 = Promise.value(nil as Double?).cancellize() + + when(fulfilled: p1, p2, p3, p4, p5, p6, p7, p8, p9).done { _, _, _, _, _, _, _, _, _ in + XCTFail() + }.catch(policy: .allErrors) { + $0.isCancelled ? ex.fulfill() : XCTFail() + }.cancel() + + when(resolved: p1, p2, p3, p4, p5, p6, p7, p8, p9).done { _, _, _, _, _, _, _, _, _ in XCTFail() }.catch(policy: .allErrors) { - $0.isCancelled ? e1.fulfill() : XCTFail() + $0.isCancelled ? ex.fulfill() : XCTFail() }.cancel() - waitForExpectations(timeout: 5, handler: nil) + + waitForExpectations() } - func testVoid() { - let e1 = expectation(description: "") + func testVoidCancel() { + let ex = expectation(description: "") let p1 = Promise.value(1).cancellize().done { _ in } let p2 = Promise.value(2).cancellize().done { _ in } let p3 = Promise.value(3).cancellize().done { _ in } let p4 = Promise.value(4).cancellize().done { _ in } - when(fulfilled: p1, p2, p3, p4).done { + when(fulfilled: [p1, p2, p3, p4]).done { XCTFail() }.catch(policy: .allErrors) { - $0.isCancelled ? e1.fulfill() : XCTFail() + $0.isCancelled ? ex.fulfill() : XCTFail() }.cancel() - waitForExpectations(timeout: 5, handler: nil) + waitForExpectations() } func testRejected() { - enum Error: Swift.Error { case dummy } - - let e1 = expectation(description: "") + let ex = expectation(description: "") + ex.expectedFulfillmentCount = 2 let p1 = after(.milliseconds(100)).cancellize().map{ true } - let p2: CancellablePromise = after(.milliseconds(200)).cancellize().map{ throw Error.dummy } + let p2: CancellablePromise = after(.milliseconds(200)).cancellize().map{ throw TestError.dummy } let p3 = Promise.value(false).cancellize() - cancellableWhen(fulfilled: p1, p2, p3).catch(policy: .allErrors) { - $0.isCancelled ? e1.fulfill() : XCTFail() + when(fulfilled: p1, p2, p3).catch(policy: .allErrors) { + $0.isCancelled ? ex.fulfill() : XCTFail() + }.cancel() + when(resolved: p1, p2, p3).catch(policy: .allErrors) { + $0.isCancelled ? ex.fulfill() : XCTFail() }.cancel() - waitForExpectations(timeout: 5, handler: nil) + waitForExpectations() } func testProgress() { @@ -173,7 +550,7 @@ class WhenTests: XCTestCase { progress.resignCurrent() - waitForExpectations(timeout: 5, handler: nil) + waitForExpectations() } func testProgressDoesNotExceed100PercentSucceed() { @@ -214,7 +591,7 @@ class WhenTests: XCTestCase { p3.done(on: q, finally).silenceWarning() p4.done(on: q, finally).silenceWarning() - waitForExpectations(timeout: 5, handler: nil) + waitForExpectations() } func testProgressDoesNotExceed100PercentCancel() { @@ -267,82 +644,125 @@ class WhenTests: XCTestCase { p3.done(on: q, finally).catch(policy: .allErrors, catchall) p4.done(on: q, finally).catch(policy: .allErrors, catchall) - waitForExpectations(timeout: 5, handler: nil) + waitForExpectations() } func testUnhandledErrorHandlerDoesNotFire() { - enum Error: Swift.Error { - case test - } - let ex = expectation(description: "") - let p1 = CancellablePromise(error: Error.test) + let p1 = CancellablePromise(error: TestError.dummy) let p2 = after(.milliseconds(100)).cancellize() - cancellableWhen(fulfilled: p1, p2).done{ _ in XCTFail() }.catch(policy: .allErrors) { + when(fulfilled: p1, p2).done{ _ in XCTFail() }.catch(policy: .allErrors) { $0.isCancelled ? XCTFail() : ex.fulfill() }.cancel() - waitForExpectations(timeout: 5, handler: nil) + waitForExpectations() } func testUnhandledErrorHandlerDoesNotFireForStragglers() { - enum Error: Swift.Error { - case test - case straggler - } - - let ex1 = expectation(description: "") - let ex2 = expectation(description: "") - let ex3 = expectation(description: "") - - let p1 = CancellablePromise(error: Error.test) - let p2 = after(.milliseconds(100)).cancellize().done { throw Error.straggler } - let p3 = after(.milliseconds(200)).cancellize().done { throw Error.straggler } + let ex = expectation(description: "") + ex.expectedFulfillmentCount = 3 + let p1 = CancellablePromise(error: TestError.dummy) + let p2 = after(.milliseconds(100)).cancellize().done { throw TestError.straggler } + let p3 = after(.milliseconds(200)).cancellize().done { throw TestError.straggler } - cancellableWhen(fulfilled: p1, p2, p3).catch(policy: .allErrors) { - $0.isCancelled ? XCTFail() : ex1.fulfill() + when(fulfilled: p1, p2, p3).catch(policy: .allErrors) { + $0.isCancelled ? XCTFail() : ex.fulfill() }.cancel() - p2.ensure { after(.milliseconds(100)).done(ex2.fulfill) }.silenceWarning() - p3.ensure { after(.milliseconds(100)).done(ex3.fulfill) }.silenceWarning() + p2.ensure { after(.milliseconds(100)).done(ex.fulfill) }.silenceWarning() + p3.ensure { after(.milliseconds(100)).done(ex.fulfill) }.silenceWarning() - waitForExpectations(timeout: 5, handler: nil) + waitForExpectations() } func testAllSealedRejectedFirstOneRejects() { - enum Error: Swift.Error { - case test1 - case test2 - case test3 - } - let ex = expectation(description: "") - let p1 = CancellablePromise(error: Error.test1) - let p2 = CancellablePromise(error: Error.test2) - let p3 = CancellablePromise(error: Error.test3) + let p1 = CancellablePromise(error: TestError.dummy) + let p2 = CancellablePromise(error: TestError.straggler) + let p3 = CancellablePromise(error: TestError.stub) when(fulfilled: p1, p2, p3).catch(policy: .allErrors) { $0.isCancelled ? XCTFail() : ex.fulfill() }.cancel() - waitForExpectations(timeout: 5) + waitForExpectations() } func testGuaranteeWhen() { - let ex1 = expectation(description: "") + let ex = expectation(description: "") + ex.expectedFulfillmentCount = 2 + when(resolved: Guarantee().cancellize(), Guarantee().cancellize()).done { _ in XCTFail() }.catch(policy: .allErrors) { - $0.isCancelled ? ex1.fulfill() : XCTFail() + $0.isCancelled ? ex.fulfill() : XCTFail() }.cancel() - let ex2 = expectation(description: "") when(resolved: [Guarantee().cancellize(), Guarantee().cancellize()]).done { _ in XCTFail() }.catch(policy: .allErrors) { - $0.isCancelled ? ex2.fulfill() : XCTFail() + $0.isCancelled ? ex.fulfill() : XCTFail() + }.cancel() + + waitForExpectations() + } + + func testMixedThenables() { + let ex = expectation(description: "") + ex.expectedFulfillmentCount = 2 + let p1 = Promise.value(true).cancellize() + let p2 = Promise.value(2).cancellize() + let g1 = Guarantee.value("abc").cancellize() + + when(fulfilled: p1, p2, g1).done { v1, v2, v3 in + XCTAssertEqual(v1, true) + XCTAssertEqual(v2, 2) + XCTAssertEqual(v3, "abc") + ex.fulfill() + }.silenceWarning() + + when(resolved: p1, p2, g1).done { r1, r2, r3 in + XCTAssertEqual(try r1.get(), true) + XCTAssertEqual(try r2.get(), 2) + XCTAssertEqual(try r3.get(), "abc") + ex.fulfill() + }.silenceWarning() + + waitForExpectations() + } + + func testMixedThenablesCancel() { + let ex = expectation(description: "") + ex.expectedFulfillmentCount = 2 + let p1 = Promise.value(true).cancellize() + let p2 = Promise.value(2).cancellize() + let g1 = Guarantee.value("abc").cancellize() + + when(fulfilled: p1, p2, g1).done { _ in + XCTFail() + }.catch(policy: .allErrors) { + $0.isCancelled ? ex.fulfill() : XCTFail() + }.cancel() + + when(resolved: p1, p2, g1).done { _ in + XCTFail() + }.catch(policy: .allErrors) { + $0.isCancelled ? ex.fulfill() : XCTFail() }.cancel() + + waitForExpectations() + } +} - wait(for: [ex1, ex2], timeout: 5) +private enum TestError: Error { + case dummy + case straggler + case stub +} + +private extension XCTestCase { + + func waitForExpectations() { + waitForExpectations(timeout: 5) } } diff --git a/Tests/Core/DispatcherTypeTests.swift b/Tests/Core/DispatcherTypeTests.swift index d60e3bc7f..298431e6f 100644 --- a/Tests/Core/DispatcherTypeTests.swift +++ b/Tests/Core/DispatcherTypeTests.swift @@ -161,7 +161,9 @@ class DispatcherTypeTests: XCTestCase { printTestResults(deltaT, mostConcurrent, scenario) } } - + +#if false + // fails sporadically and is causing us woe as a result func testStrictRateLimitedDispatcher() { for scenario in scenarios { printScenarioDetails(scenario) @@ -181,6 +183,7 @@ class DispatcherTypeTests: XCTestCase { XCTAssert(dispatcher.startTimeHistory.count == 0, "Dispatcher did not clean up properly") } } +#endif func testConcurrencyLimitedDispatcher() { diff --git a/Tests/Core/GuaranteeTests.swift b/Tests/Core/GuaranteeTests.swift index 07e16160f..470028608 100644 --- a/Tests/Core/GuaranteeTests.swift +++ b/Tests/Core/GuaranteeTests.swift @@ -195,7 +195,7 @@ class GuaranteeTests: XCTestCase { let a = Guarantee.value let b = Guarantee.value(Void()) let c = Guarantee.value(()) - when(fulfilled: a, b, c).done { + when(fulfilled: [a, b, c]).done { ex.fulfill() }.cauterize() wait(for: [ex], timeout: 5) diff --git a/Tests/Core/PromiseTests.swift b/Tests/Core/PromiseTests.swift index 4a1e985a9..fb2c17c0c 100644 --- a/Tests/Core/PromiseTests.swift +++ b/Tests/Core/PromiseTests.swift @@ -212,7 +212,7 @@ class PromiseTests: XCTestCase { let a = Promise.value let b = Promise.value(Void()) let c = Promise.value(()) - when(fulfilled: a, b, c).done { + when(fulfilled: [a, b, c]).done { ex.fulfill() }.cauterize() wait(for: [ex], timeout: 5) diff --git a/Tests/Core/WhenResolvedTests.swift b/Tests/Core/WhenResolvedTests.swift index d65cf3728..5f001ee8f 100644 --- a/Tests/Core/WhenResolvedTests.swift +++ b/Tests/Core/WhenResolvedTests.swift @@ -1,7 +1,7 @@ // Created by Austin Feight on 3/19/16. // Copyright © 2016 Max Howell. All rights reserved. -import PromiseKit +@testable import PromiseKit import XCTest class JoinTests: XCTestCase { @@ -9,7 +9,7 @@ class JoinTests: XCTestCase { let successPromise = Promise() var joinFinished = false - when(resolved: successPromise).done(on: nil) { _ in joinFinished = true } + when(resolved: [successPromise]).done(on: nil) { joinFinished = true } XCTAssert(joinFinished, "Join immediately finishes on fulfilled promise") let promise2 = Promise.value(2) @@ -38,4 +38,68 @@ class JoinTests: XCTestCase { seal3.fulfill() XCTAssert(finished, "All promises have resolved") } + + func testErrorHandlerDoesFire() { + let ex = expectation(description: "") + let p1 = Promise(error: TestError.dummy) + let p2 = after(.milliseconds(100)) + when(resolved: p1, p2).done { _ in throw TestError.stub }.catch { error in + XCTAssertTrue(error as? TestError == TestError.stub) + ex.fulfill() + } + + waitForExpectations(timeout: 1, handler: nil) + } + + func testAllRejected() { + let ex = expectation(description: "") + let p1 = Promise(error: TestError.dummy) + let p2 = Promise(error: TestError.straggler) + let p3 = Promise(error: TestError.stub) + + when(resolved: p1, p2, p3).done { r1, r2, r3 in + XCTAssertTrue(r1.error! as! TestError == TestError.dummy) + XCTAssertTrue(r2.error! as! TestError == TestError.straggler) + XCTAssertTrue(r3.error! as! TestError == TestError.stub) + ex.fulfill() + } + + waitForExpectations(timeout: 1) + } + + func testMixedThenables() { + let ex = expectation(description: "") + let p1 = Promise.value(2) + let g1 = Guarantee.value(4) + + when(resolved: p1, g1).done { r1, r2 in + XCTAssertEqual(try r1.get(), 2) + XCTAssertEqual(r2, 4) + ex.fulfill() + } + + waitForExpectations(timeout: 1) + } + + func testMixedThenablesWithMixedResults() { + let ex = expectation(description: "") + let p1 = Promise(error: TestError.dummy) + let p2 = Promise.value(2) + let g1 = Guarantee.value("abc") + + when(resolved: p1, p2, g1).done { r1, r2, r3 in + XCTAssertTrue(r1.error! as! TestError == TestError.dummy) + XCTAssertEqual(try r2.get(), 2) + XCTAssertEqual(r3, "abc") + ex.fulfill() + } + + waitForExpectations(timeout: 1) + } +} + +private enum TestError: Error { + case dummy + case straggler + case stub } diff --git a/Tests/Core/WhenTests.swift b/Tests/Core/WhenTests.swift index d19e72f2c..aba050fb5 100644 --- a/Tests/Core/WhenTests.swift +++ b/Tests/Core/WhenTests.swift @@ -1,26 +1,27 @@ -import PromiseKit +@testable import PromiseKit import Dispatch import XCTest class WhenTests: XCTestCase { - func testEmpty() { - let e1 = expectation(description: "") let promises: [Promise] = [] + + let ex = expectation(description: "") when(fulfilled: promises).done { _ in - e1.fulfill() + ex.fulfill() }.silenceWarning() let e2 = expectation(description: "") when(resolved: promises).done { _ in e2.fulfill() - }.silenceWarning() + } - wait(for: [e1, e2], timeout: 5) + waitForExpectations() } func testInt() { - let e1 = expectation(description: "") + let ex = expectation(description: "") + let e2 = expectation(description: "") let p1 = Promise.value(1) let p2 = Promise.value(2) let p3 = Promise.value(3) @@ -32,96 +33,302 @@ class WhenTests: XCTestCase { XCTAssertEqual(x[2], 3) XCTAssertEqual(x[3], 4) XCTAssertEqual(x.count, 4) - e1.fulfill() + ex.fulfill() }.silenceWarning() - waitForExpectations(timeout: 5, handler: nil) + + when(resolved: [p1, p2, p3, p4]).done { x in + XCTAssertEqual(try x[0].get(), 1) + XCTAssertEqual(try x[1].get(), 2) + XCTAssertEqual(try x[2].get(), 3) + XCTAssertEqual(try x[3].get(), 4) + XCTAssertEqual(x.count, 4) + e2.fulfill() + } + + waitForExpectations() } - func testDoubleTuple() { - let e1 = expectation(description: "") + func testBinaryTuple() { + let ex = expectation(description: "") + ex.expectedFulfillmentCount = 2 let p1 = Promise.value(1) let p2 = Promise.value("abc") - when(fulfilled: p1, p2).done{ x, y in - XCTAssertEqual(x, 1) - XCTAssertEqual(y, "abc") - e1.fulfill() + + when(fulfilled: p1, p2).done{ v1, v2 in + XCTAssertEqual(v1, 1) + XCTAssertEqual(v2, "abc") + ex.fulfill() }.silenceWarning() - waitForExpectations(timeout: 5, handler: nil) + + when(resolved: p1, p2).done{ r1, r2 in + XCTAssertEqual(try r1.get(), 1) + XCTAssertEqual(try r2.get(), "abc") + ex.fulfill() + } + + waitForExpectations() } - func testTripleTuple() { - let e1 = expectation(description: "") + func testTernaryTuple() { + let ex = expectation(description: "") + ex.expectedFulfillmentCount = 2 let p1 = Promise.value(1) let p2 = Promise.value("abc") - let p3 = Promise.value( 1.0) - when(fulfilled: p1, p2, p3).done { u, v, w in - XCTAssertEqual(1, u) - XCTAssertEqual("abc", v) - XCTAssertEqual(1.0, w) - e1.fulfill() + let p3 = Promise.value(1.0) + + when(fulfilled: p1, p2, p3).done { v1, v2, v3 in + XCTAssertEqual(v1, 1) + XCTAssertEqual(v2, "abc") + XCTAssertEqual(v3, 1.0) + ex.fulfill() }.silenceWarning() - waitForExpectations(timeout: 5, handler: nil) + + when(resolved: p1, p2, p3).done { r1, r2, r3 in + XCTAssertEqual(try r1.get(), 1) + XCTAssertEqual(try r2.get(), "abc") + XCTAssertEqual(try r3.get(), 1.0) + ex.fulfill() + } + + waitForExpectations() } - func testQuadrupleTuple() { - let e1 = expectation(description: "") + func testQuaternaryTuple() { + let ex = expectation(description: "") + ex.expectedFulfillmentCount = 2 let p1 = Promise.value(1) let p2 = Promise.value("abc") let p3 = Promise.value(1.0) let p4 = Promise.value(true) - when(fulfilled: p1, p2, p3, p4).done { u, v, w, x in - XCTAssertEqual(1, u) - XCTAssertEqual("abc", v) - XCTAssertEqual(1.0, w) - XCTAssertEqual(true, x) - e1.fulfill() + + when(fulfilled: p1, p2, p3, p4).done { v1, v2, v3, v4 in + XCTAssertEqual(v1, 1) + XCTAssertEqual(v2, "abc") + XCTAssertEqual(v3, 1.0) + XCTAssertEqual(v4, true) + ex.fulfill() }.silenceWarning() - waitForExpectations(timeout: 5, handler: nil) + + when(resolved: p1, p2, p3, p4).done { r1, r2, r3, r4 in + XCTAssertEqual(try r1.get(), 1) + XCTAssertEqual(try r2.get(), "abc") + XCTAssertEqual(try r3.get(), 1.0) + XCTAssertEqual(try r4.get(), true) + ex.fulfill() + } + + waitForExpectations() } - func testQuintupleTuple() { - let e1 = expectation(description: "") + func testQuinaryTuple() { + let ex = expectation(description: "") + ex.expectedFulfillmentCount = 2 let p1 = Promise.value(1) let p2 = Promise.value("abc") let p3 = Promise.value(1.0) let p4 = Promise.value(true) let p5 = Promise.value("a" as Character) - when(fulfilled: p1, p2, p3, p4, p5).done { u, v, w, x, y in - XCTAssertEqual(1, u) - XCTAssertEqual("abc", v) - XCTAssertEqual(1.0, w) - XCTAssertEqual(true, x) - XCTAssertEqual("a" as Character, y) - e1.fulfill() + + when(fulfilled: p1, p2, p3, p4, p5).done { v1, v2, v3, v4, v5 in + XCTAssertEqual(v1, 1) + XCTAssertEqual(v2, "abc") + XCTAssertEqual(v3, 1.0) + XCTAssertEqual(v4, true) + XCTAssertEqual(v5, "a" as Character) + ex.fulfill() + }.silenceWarning() + + when(resolved: p1, p2, p3, p4, p5).done { r1, r2, r3, r4, r5 in + XCTAssertEqual(try r1.get(), 1) + XCTAssertEqual(try r2.get(), "abc") + XCTAssertEqual(try r3.get(), 1.0) + XCTAssertEqual(try r4.get(), true) + XCTAssertEqual(try r5.get(), "a" as Character) + ex.fulfill() + } + + waitForExpectations() + } + + func testSenaryTuple() { + let ex = expectation(description: "") + ex.expectedFulfillmentCount = 2 + let p1 = Promise.value(1) + let p2 = Promise.value("abc") + let p3 = Promise.value(1.0) + let p4 = Promise.value(true) + let p5 = Promise.value("a" as Character) + let p6 = Promise.value(CGFloat(6)) + + when(fulfilled: p1, p2, p3, p4, p5, p6).done { v1, v2, v3, v4, v5, v6 in + XCTAssertEqual(v1, 1) + XCTAssertEqual(v2, "abc") + XCTAssertEqual(v3, 1.0) + XCTAssertEqual(v4, true) + XCTAssertEqual(v5, "a" as Character) + XCTAssertEqual(v6, CGFloat(6)) + ex.fulfill() }.silenceWarning() - waitForExpectations(timeout: 5, handler: nil) + + when(resolved: p1, p2, p3, p4, p5, p6).done { r1, r2, r3, r4, r5, r6 in + XCTAssertEqual(try r1.get(), 1) + XCTAssertEqual(try r2.get(), "abc") + XCTAssertEqual(try r3.get(), 1.0) + XCTAssertEqual(try r4.get(), true) + XCTAssertEqual(try r5.get(), "a" as Character) + XCTAssertEqual(try r6.get(), CGFloat(6)) + ex.fulfill() + } + + waitForExpectations() } + func testSeptenaryTuple() { + let ex = expectation(description: "") + ex.expectedFulfillmentCount = 2 + let p1 = Promise.value(1) + let p2 = Promise.value("abc") + let p3 = Promise.value(1.0) + let p4 = Promise.value(true) + let p5 = Promise.value("a" as Character) + let p6 = Promise.value(CGFloat(6)) + let p7 = Promise.value(1 as Int?) + + when(fulfilled: p1, p2, p3, p4, p5, p6, p7).done { v1, v2, v3, v4, v5, v6, v7 in + XCTAssertEqual(v1, 1) + XCTAssertEqual(v2, "abc") + XCTAssertEqual(v3, 1.0) + XCTAssertEqual(v4, true) + XCTAssertEqual(v5, "a" as Character) + XCTAssertEqual(v6, CGFloat(6)) + XCTAssertEqual(v7, 1 as Int?) + ex.fulfill() + }.silenceWarning() + + when(resolved: p1, p2, p3, p4, p5, p6, p7).done { r1, r2, r3, r4, r5, r6, r7 in + XCTAssertEqual(try r1.get(), 1) + XCTAssertEqual(try r2.get(), "abc") + XCTAssertEqual(try r3.get(), 1.0) + XCTAssertEqual(try r4.get(), true) + XCTAssertEqual(try r5.get(), "a" as Character) + XCTAssertEqual(try r6.get(), CGFloat(6)) + XCTAssertEqual(try r7.get(), 1 as Int?) + ex.fulfill() + } + + waitForExpectations() + } + + func testOctonaryTuple() { + let ex = expectation(description: "") + ex.expectedFulfillmentCount = 2 + let p1 = Promise.value(1) + let p2 = Promise.value("abc") + let p3 = Promise.value(1.0) + let p4 = Promise.value(true) + let p5 = Promise.value("a" as Character) + let p6 = Promise.value(CGFloat(6)) + let p7 = Promise.value(1 as Int?) + let p8 = Promise.value("abc" as String?) + + when(fulfilled: p1, p2, p3, p4, p5, p6, p7, p8).done { v1, v2, v3, v4, v5, v6, v7, v8 in + XCTAssertEqual(v1, 1) + XCTAssertEqual(v2, "abc") + XCTAssertEqual(v3, 1.0) + XCTAssertEqual(v4, true) + XCTAssertEqual(v5, "a" as Character) + XCTAssertEqual(v6, CGFloat(6)) + XCTAssertEqual(v7, 1 as Int?) + XCTAssertEqual(v8, "abc" as String?) + ex.fulfill() + }.silenceWarning() + + when(resolved: p1, p2, p3, p4, p5, p6, p7, p8).done { r1, r2, r3, r4, r5, r6, r7, r8 in + XCTAssertEqual(try r1.get(), 1) + XCTAssertEqual(try r2.get(), "abc") + XCTAssertEqual(try r3.get(), 1.0) + XCTAssertEqual(try r4.get(), true) + XCTAssertEqual(try r5.get(), "a" as Character) + XCTAssertEqual(try r6.get(), CGFloat(6)) + XCTAssertEqual(try r7.get(), 1 as Int?) + XCTAssertEqual(try r8.get(), "abc" as String?) + ex.fulfill() + } + + waitForExpectations() + } + + func testNovenaryTuple() { + let ex = expectation(description: "") + ex.expectedFulfillmentCount = 2 + let p1 = Promise.value(1) + let p2 = Promise.value("abc") + let p3 = Promise.value(1.0) + let p4 = Promise.value(true) + let p5 = Promise.value("a" as Character) + let p6 = Promise.value(CGFloat(6)) + let p7 = Promise.value(1 as Int?) + let p8 = Promise.value("abc" as String?) + let p9 = Promise.value(nil as Double?) + + when(fulfilled: p1, p2, p3, p4, p5, p6, p7, p8, p9).done { v1, v2, v3, v4, v5, v6, v7, v8, v9 in + XCTAssertEqual(v1, 1) + XCTAssertEqual(v2, "abc") + XCTAssertEqual(v3, 1.0) + XCTAssertEqual(v4, true) + XCTAssertEqual(v5, "a" as Character) + XCTAssertEqual(v6, CGFloat(6)) + XCTAssertEqual(v7, 1 as Int?) + XCTAssertEqual(v8, "abc" as String?) + XCTAssertEqual(v9, nil) + ex.fulfill() + }.silenceWarning() + + when(resolved: p1, p2, p3, p4, p5, p6, p7, p8, p9).done { r1, r2, r3, r4, r5, r6, r7, r8, r9 in + XCTAssertEqual(try r1.get(), 1) + XCTAssertEqual(try r2.get(), "abc") + XCTAssertEqual(try r3.get(), 1.0) + XCTAssertEqual(try r4.get(), true) + XCTAssertEqual(try r5.get(), "a" as Character) + XCTAssertEqual(try r6.get(), CGFloat(6)) + XCTAssertEqual(try r7.get(), 1 as Int?) + XCTAssertEqual(try r8.get(), "abc" as String?) + XCTAssertEqual(try r9.get(), nil) + ex.fulfill() + } + + waitForExpectations() + } + func testVoid() { - let e1 = expectation(description: "") + let ex = expectation(description: "") + ex.expectedFulfillmentCount = 2 let p1 = Promise.value(1).done { _ in } let p2 = Promise.value(2).done { _ in } let p3 = Promise.value(3).done { _ in } let p4 = Promise.value(4).done { _ in } - when(fulfilled: p1, p2, p3, p4).done(e1.fulfill).silenceWarning() - - waitForExpectations(timeout: 5, handler: nil) + when(fulfilled: p1, p2, p3, p4).done { _ in ex.fulfill() }.silenceWarning() + when(resolved: p1, p2, p3, p4).done { _ in ex.fulfill() } + waitForExpectations() } func testRejected() { - enum Error: Swift.Error { case dummy } - - let e1 = expectation(description: "") + let ex = expectation(description: "") + ex.expectedFulfillmentCount = 2 let p1 = after(.milliseconds(100)).map{ true } - let p2: Promise = after(.milliseconds(200)).map{ throw Error.dummy } + let p2: Promise = after(.milliseconds(200)).map{ throw TestError.dummy } let p3 = Promise.value(false) when(fulfilled: p1, p2, p3).catch { _ in - e1.fulfill() + ex.fulfill() + } + when(resolved: p1, p2, p3).done { _, r2, _ in + XCTAssertEqual(r2.error! as! TestError, TestError.dummy) + ex.fulfill() } - waitForExpectations(timeout: 5, handler: nil) + waitForExpectations() } func testProgress() { @@ -144,7 +351,7 @@ class WhenTests: XCTestCase { progress.resignCurrent() - waitForExpectations(timeout: 5, handler: nil) + waitForExpectations() } func testProgressDoesNotExceed100Percent() { @@ -185,83 +392,104 @@ class WhenTests: XCTestCase { p3.done(on: q, finally) p4.done(on: q, finally) - waitForExpectations(timeout: 5, handler: nil) + waitForExpectations() } func testUnhandledErrorHandlerDoesNotFire() { - enum Error: Swift.Error { - case test - } - let ex = expectation(description: "") - let p1 = Promise(error: Error.test) + let p1 = Promise(error: TestError.dummy) let p2 = after(.milliseconds(100)) when(fulfilled: p1, p2).done{ _ in XCTFail() }.catch { error in - XCTAssertTrue(error as? Error == Error.test) + XCTAssertTrue(error as? TestError == TestError.dummy) ex.fulfill() } - waitForExpectations(timeout: 5, handler: nil) + waitForExpectations() } func testUnhandledErrorHandlerDoesNotFireForStragglers() { - enum Error: Swift.Error { - case test - case straggler - } - - let ex1 = expectation(description: "") - let ex2 = expectation(description: "") - let ex3 = expectation(description: "") + let ex = expectation(description: "") + ex.expectedFulfillmentCount = 3 - let p1 = Promise(error: Error.test) - let p2 = after(.milliseconds(100)).done { throw Error.straggler } - let p3 = after(.milliseconds(200)).done { throw Error.straggler } + let p1 = Promise(error: TestError.dummy) + let p2 = after(.milliseconds(100)).done { throw TestError.straggler } + let p3 = after(.milliseconds(200)).done { throw TestError.straggler } let whenFulfilledP1P2P3: Promise<(Void, Void, Void)> = when(fulfilled: p1, p2, p3) whenFulfilledP1P2P3.catch { error -> Void in - XCTAssertTrue(Error.test == error as? Error) - ex1.fulfill() + XCTAssertTrue(TestError.dummy == error as? TestError) + ex.fulfill() } - p2.ensure { after(.milliseconds(100)).done(ex2.fulfill) }.silenceWarning() - p3.ensure { after(.milliseconds(100)).done(ex3.fulfill) }.silenceWarning() + p2.ensure { after(.milliseconds(100)).done(ex.fulfill) }.silenceWarning() + p3.ensure { after(.milliseconds(100)).done(ex.fulfill) }.silenceWarning() - waitForExpectations(timeout: 5, handler: nil) + waitForExpectations() } func testAllSealedRejectedFirstOneRejects() { - enum Error: Swift.Error { - case test1 - case test2 - case test3 - } - let ex = expectation(description: "") - let p1 = Promise(error: Error.test1) - let p2 = Promise(error: Error.test2) - let p3 = Promise(error: Error.test3) + let p1 = Promise(error: TestError.dummy) + let p2 = Promise(error: TestError.straggler) + let p3 = Promise(error: TestError.stub) - let whenFulfilledP1P2P3: Promise = when(fulfilled: p1, p2, p3) - whenFulfilledP1P2P3.catch { error in - XCTAssertTrue(error as? Error == Error.test1) + when(fulfilled: p1, p2, p3).catch { error in + XCTAssertTrue(error as? TestError == TestError.dummy) ex.fulfill() } - waitForExpectations(timeout: 5) + waitForExpectations() } func testGuaranteeWhen() { - let ex1 = expectation(description: "") - when(Guarantee(), Guarantee()).done { - ex1.fulfill() + let ex = expectation(description: "") + ex.expectedFulfillmentCount = 2 + + when(resolved: Guarantee(), Guarantee()).done { _ in + ex.fulfill() } - let ex2 = expectation(description: "") - when(guarantees: [Guarantee(), Guarantee()]).done { - ex2.fulfill() + when(resolved: [Guarantee(), Guarantee()]).done { + ex.fulfill() } - wait(for: [ex1, ex2], timeout: 5) + waitForExpectations() + } + + func testMixedThenables() { + let ex = expectation(description: "") + ex.expectedFulfillmentCount = 2 + let p1 = Promise.value(true) + let p2 = Promise.value(2) + let g1 = Guarantee.value("abc") + + when(fulfilled: p1, p2, g1).done { v1, v2, v3 in + XCTAssertEqual(v1, true) + XCTAssertEqual(v2, 2) + XCTAssertEqual(v3, "abc") + ex.fulfill() + }.silenceWarning() + + when(resolved: p1, p2, g1).done { r1, r2, r3 in + XCTAssertEqual(try r1.get(), true) + XCTAssertEqual(try r2.get(), 2) + XCTAssertEqual(r3, "abc") + ex.fulfill() + }.silenceWarning() + + waitForExpectations() + } +} + +private enum TestError: Error { + case dummy + case straggler + case stub +} + +private extension XCTestCase { + + func waitForExpectations() { + waitForExpectations(timeout: 5) } }