diff --git a/Sources/Cancellation/CancellableCatchable.swift b/Sources/Cancellation/CancellableCatchable.swift index e03b1cf01..db76b608b 100644 --- a/Sources/Cancellation/CancellableCatchable.swift +++ b/Sources/Cancellation/CancellableCatchable.swift @@ -25,7 +25,7 @@ public extension CancellableCatchMixin { - SeeAlso: [Cancellation](https://github.com/mxcl/PromiseKit/blob/master/Documentation/CommonPatterns.md#cancellation) */ @discardableResult - func `catch`(on: Dispatcher = conf.D.return, policy: CatchPolicy = conf.catchPolicy, _ body: @escaping(Error) -> Void) -> CancellableFinalizer { + func `catch`(on: Dispatcher = conf.dd, policy: CatchPolicy = conf.catchPolicy, _ body: @escaping(Error) -> Void) -> CancellableFinalizer { return CancellableFinalizer(self.catchable.catch(on: on, policy: policy, body), cancel: self.cancelContext) } @@ -44,7 +44,7 @@ public extension CancellableCatchMixin { - Note: Since this method handles only specific errors, supplying a `CatchPolicy` is unsupported. You can instead specify e.g. your cancellable error. - SeeAlso: [Cancellation](https://github.com/mxcl/PromiseKit/blob/master/Documentation/CommonPatterns.md#cancellation) */ - func `catch`(only: E, on: Dispatcher = conf.D.return, _ body: @escaping(E) -> Void) -> CancellableCascadingFinalizer where E: Equatable { + func `catch`(only: E, on: Dispatcher = conf.dd, _ body: @escaping(E) -> Void) -> CancellableCascadingFinalizer where E: Equatable { return CancellableCascadingFinalizer(self.catchable.catch(only: only, on: on, body), cancel: self.cancelContext) } @@ -62,7 +62,7 @@ public extension CancellableCatchMixin { - Parameter execute: The handler to execute if this promise is rejected with the provided error type. - SeeAlso: [Cancellation](https://github.com/mxcl/PromiseKit/blob/master/Documentation/CommonPatterns.md#cancellation) */ - func `catch`(only: E.Type, on: Dispatcher = conf.D.return, policy: CatchPolicy = conf.catchPolicy, _ body: @escaping(E) -> Void) -> CancellableCascadingFinalizer { + func `catch`(only: E.Type, on: Dispatcher = conf.dd, policy: CatchPolicy = conf.catchPolicy, _ body: @escaping(E) -> Void) -> CancellableCascadingFinalizer { return CancellableCascadingFinalizer(self.catchable.catch(only: only, on: on, policy: policy, body), cancel: self.cancelContext) } } @@ -119,7 +119,7 @@ public class CancellableFinalizer: CancelContextFinalizer { /// `finally` is the same as `ensure`, but it is not chainable @discardableResult - public func finally(on: Dispatcher = conf.D.return, _ body: @escaping () -> Void) -> CancelContext { + public func finally(on: Dispatcher = conf.dd, _ body: @escaping () -> Void) -> CancelContext { pmkFinalizer.finally(on: on, body) return cancelContext } @@ -148,7 +148,7 @@ public class CancellableCascadingFinalizer: CancelContextFinalizer { - SeeAlso: [Cancellation](https://github.com/mxcl/PromiseKit/blob/master/Documentation/CommonPatterns.md#cancellation) */ @discardableResult - public func `catch`(on: Dispatcher = conf.D.return, policy: CatchPolicy = conf.catchPolicy, _ body: @escaping(Error) -> Void) -> CancellableFinalizer { + public func `catch`(on: Dispatcher = conf.dd, policy: CatchPolicy = conf.catchPolicy, _ body: @escaping(Error) -> Void) -> CancellableFinalizer { return CancellableFinalizer(pmkCascadingFinalizer.catch(on: on, policy: policy, body), cancel: cancelContext) } @@ -166,7 +166,7 @@ public class CancellableCascadingFinalizer: CancelContextFinalizer { - Note: Since this method handles only specific errors, supplying a `CatchPolicy` is unsupported. You can instead specify e.g. your cancellable error. - SeeAlso: [Cancellation](https://github.com/mxcl/PromiseKit/blob/master/Documentation/CommonPatterns.md#cancellation) */ - public func `catch`(only: E, on: Dispatcher = conf.D.return, _ body: @escaping(E) -> Void) -> CancellableCascadingFinalizer where E: Equatable { + public func `catch`(only: E, on: Dispatcher = conf.dd, _ body: @escaping(E) -> Void) -> CancellableCascadingFinalizer where E: Equatable { return CancellableCascadingFinalizer(pmkCascadingFinalizer.catch(only: only, on: on, body), cancel: cancelContext) } @@ -183,10 +183,20 @@ public class CancellableCascadingFinalizer: CancelContextFinalizer { - Parameter execute: The handler to execute if this promise is rejected with the provided error type. - SeeAlso: [Cancellation](https://github.com/mxcl/PromiseKit/blob/master/Documentation/CommonPatterns.md#cancellation) */ - public func `catch`(only: E.Type, on: Dispatcher = conf.D.return, policy: CatchPolicy = conf.catchPolicy, _ body: @escaping(E) -> Void) -> CancellableCascadingFinalizer { + public func `catch`(only: E.Type, on: Dispatcher = conf.dd, policy: CatchPolicy = conf.catchPolicy, _ body: @escaping(E) -> Void) -> CancellableCascadingFinalizer { return CancellableCascadingFinalizer(pmkCascadingFinalizer.catch(only: only, on: on, policy: policy, body), cancel: cancelContext) } + /// Set a default Dispatcher for the chain. Within the chain, this Dispatcher will remain the + /// default until you change it, even if you dispatch individual closures to other Dispatchers. + /// + /// - Parameters: + /// - on: The new default Dispatcher. Use `.default` to return to normal dispatching. + + public func dispatch(on: Dispatcher) -> CancellableCascadingFinalizer { + return CancellableCascadingFinalizer(pmkCascadingFinalizer.dispatch(on: on), cancel: cancelContext) + } + /** Consumes the Swift unused-result warning. - Note: You should `catch`, but in situations where you know you don’t need a `catch`, `cauterize` makes your intentions clear. @@ -221,7 +231,7 @@ public extension CancellableCatchMixin { - Parameter body: The handler to execute if this promise is rejected. - SeeAlso: [Cancellation](https://github.com/mxcl/PromiseKit/blob/master/Documentation/CommonPatterns.md#cancellation) */ - func recover(on: Dispatcher = conf.D.map, policy: CatchPolicy = conf.catchPolicy, _ body: @escaping(Error) throws -> V) -> CancellablePromise where V.U.T == C.T { + func recover(on: Dispatcher = conf.dd, policy: CatchPolicy = conf.catchPolicy, _ body: @escaping(Error) throws -> V) -> CancellablePromise where V.U.T == C.T { let cancelItemList = CancelItemList() let cancelBody = { (error: Error) throws -> V.U in @@ -263,7 +273,7 @@ public extension CancellableCatchMixin { - Parameter body: The handler to execute if this promise is rejected. - SeeAlso: [Cancellation](https://github.com/mxcl/PromiseKit/blob/master/Documentation/CommonPatterns.md#cancellation) */ - func recover(on: Dispatcher = conf.D.map, policy: CatchPolicy = conf.catchPolicy, _ body: @escaping(Error) throws -> V) -> CancellablePromise where V.T == C.T { + func recover(on: Dispatcher = conf.dd, policy: CatchPolicy = conf.catchPolicy, _ body: @escaping(Error) throws -> V) -> CancellablePromise where V.T == C.T { let cancelBody = { (error: Error) throws -> V in _ = self.cancelContext.removeItems(self.cancelItemList, clearList: true) let rval = try body(error) @@ -302,7 +312,7 @@ public extension CancellableCatchMixin { - Note: Since this method recovers only specific errors, supplying a `CatchPolicy` is unsupported. - SeeAlso: [Cancellation](https://github.com/mxcl/PromiseKit/blob/master/Documentation/CommonPatterns.md#cancellation) */ - func recover(only: E, on: Dispatcher = conf.D.map, _ body: @escaping(E) throws -> V) -> CancellablePromise where V.U.T == C.T, E: Equatable { + func recover(only: E, on: Dispatcher = conf.dd, _ body: @escaping(E) throws -> V) -> CancellablePromise where V.U.T == C.T, E: Equatable { let cancelItemList = CancelItemList() let cancelBody = { (error: E) throws -> V.U in @@ -340,7 +350,7 @@ public extension CancellableCatchMixin { - Note: Since this method recovers only specific errors, supplying a `CatchPolicy` is unsupported. You can instead specify e.g. your cancellable error. - SeeAlso: [Cancellation](https://github.com/mxcl/PromiseKit/blob/master/Documentation/CommonPatterns.md#cancellation) */ - func recover(only: E, on: Dispatcher = conf.D.map, _ body: @escaping(E) throws -> V) -> CancellablePromise where V.T == C.T, E: Equatable { + func recover(only: E, on: Dispatcher = conf.dd, _ body: @escaping(E) throws -> V) -> CancellablePromise where V.T == C.T, E: Equatable { let cancelBody = { (error: E) throws -> V in _ = self.cancelContext.removeItems(self.cancelItemList, clearList: true) let rval = try body(error) @@ -378,7 +388,7 @@ public extension CancellableCatchMixin { - Parameter body: The handler to execute if this promise is rejected with the provided error type. - SeeAlso: [Cancellation](https://github.com/mxcl/PromiseKit/blob/master/Documentation/CommonPatterns.md#cancellation) */ - func recover(only: E.Type, on: Dispatcher = conf.D.map, policy: CatchPolicy = conf.catchPolicy, _ body: @escaping(E) throws -> V) -> CancellablePromise where V.U.T == C.T { + func recover(only: E.Type, on: Dispatcher = conf.dd, policy: CatchPolicy = conf.catchPolicy, _ body: @escaping(E) throws -> V) -> CancellablePromise where V.U.T == C.T { let cancelItemList = CancelItemList() let cancelBody = { (error: E) throws -> V.U in @@ -417,7 +427,7 @@ public extension CancellableCatchMixin { - Parameter body: The handler to execute if this promise is rejected with the provided error type. - SeeAlso: [Cancellation](https://github.com/mxcl/PromiseKit/blob/master/Documentation/CommonPatterns.md#cancellation) */ - func recover(only: E.Type, on: Dispatcher = conf.D.map, policy: CatchPolicy = conf.catchPolicy, _ body: @escaping(E) throws -> V) -> CancellablePromise where V.T == C.T { + func recover(only: E.Type, on: Dispatcher = conf.dd, policy: CatchPolicy = conf.catchPolicy, _ body: @escaping(E) throws -> V) -> CancellablePromise where V.T == C.T { let cancelBody = { (error: E) throws -> V in _ = self.cancelContext.removeItems(self.cancelItemList, clearList: true) let rval = try body(error) @@ -460,11 +470,12 @@ public extension CancellableCatchMixin { - Parameter body: The closure that executes when this promise resolves. - Returns: A new promise, resolved with this promise’s resolution. */ - func ensure(on: Dispatcher = conf.D.return, _ body: @escaping () -> Void) -> CancellablePromise { + func ensure(on: Dispatcher = conf.dd, _ body: @escaping () -> Void) -> CancellablePromise { let rp = CancellablePromise.pending() rp.promise.cancelContext = self.cancelContext + rp.promise.dispatchState = self.catchable.dispatchState.nextState(givenDispatcher: on) self.catchable.pipe { result in - on.dispatch { + rp.promise.dispatch { body() switch result { case .success(let value): @@ -503,11 +514,12 @@ public extension CancellableCatchMixin { - Parameter body: The closure that executes when this promise resolves. - Returns: A new promise, resolved with this promise’s resolution. */ - func ensureThen(on: Dispatcher = conf.D.return, _ body: @escaping () -> CancellablePromise) -> CancellablePromise { + func ensureThen(on: Dispatcher = conf.dd, _ body: @escaping () -> CancellablePromise) -> CancellablePromise { let rp = CancellablePromise.pending() rp.promise.cancelContext = cancelContext + rp.promise.dispatchState = self.catchable.dispatchState.nextState(givenDispatcher: on) self.catchable.pipe { result in - on.dispatch { + rp.promise.dispatch { let rv = body() rp.promise.appendCancelContext(from: rv) @@ -552,7 +564,7 @@ public extension CancellableCatchMixin where C.T == Void { - Parameter body: The handler to execute if this promise is rejected. - SeeAlso: [Cancellation](https://github.com/mxcl/PromiseKit/blob/master/Documentation/CommonPatterns.md#cancellation) */ - func recover(on: Dispatcher = conf.D.map, policy: CatchPolicy = conf.catchPolicy, _ body: @escaping(Error) throws -> Void) -> CancellablePromise { + func recover(on: Dispatcher = conf.dd, policy: CatchPolicy = conf.catchPolicy, _ body: @escaping(Error) throws -> Void) -> CancellablePromise { let cancelBody = { (error: Error) throws -> Void in _ = self.cancelContext.removeItems(self.cancelItemList, clearList: true) try body(error) @@ -581,7 +593,7 @@ public extension CancellableCatchMixin where C.T == Void { - Note: Since this method recovers only specific errors, supplying a `CatchPolicy` is unsupported. You can instead specify e.g. your cancellable error. - SeeAlso: [Cancellation](https://github.com/mxcl/PromiseKit/blob/master/Documentation/CommonPatterns.md#cancellation) */ - func recover(only: E, on: Dispatcher = conf.D.map, _ body: @escaping(E) throws -> Void) + func recover(only: E, on: Dispatcher = conf.dd, _ body: @escaping(E) throws -> Void) -> CancellablePromise where E: Equatable { let cancelBody = { (error: E) throws -> Void in @@ -610,7 +622,7 @@ public extension CancellableCatchMixin where C.T == Void { - Parameter body: The handler to execute if this promise is rejected with the provided error type. - SeeAlso: [Cancellation](https://github.com/mxcl/PromiseKit/blob/master/Documentation/CommonPatterns.md#cancellation) */ - func recover(only: E.Type, on: Dispatcher = conf.D.map, policy: CatchPolicy = conf.catchPolicy, _ body: @escaping(E) throws -> Void) -> CancellablePromise { + func recover(only: E.Type, on: Dispatcher = conf.dd, policy: CatchPolicy = conf.catchPolicy, _ body: @escaping(E) throws -> Void) -> CancellablePromise { let cancelBody = { (error: E) throws -> Void in _ = self.cancelContext.removeItems(self.cancelItemList, clearList: true) try body(error) diff --git a/Sources/Cancellation/CancellablePromise.swift b/Sources/Cancellation/CancellablePromise.swift index 93e42002a..56f44eec0 100644 --- a/Sources/Cancellation/CancellablePromise.swift +++ b/Sources/Cancellation/CancellablePromise.swift @@ -8,9 +8,13 @@ import Dispatch - See: `CancellableThenable` */ -public class CancellablePromise: CancellableThenable, CancellableCatchMixin { +public class CancellablePromise: CancellableThenable, CancellableCatchMixin, HasDispatchState { /// Delegate `promise` for this CancellablePromise public let promise: Promise + var dispatchState: DispatchState { + get { return promise.dispatchState } + set { promise.dispatchState = newValue } + } /// Type of the delegate `thenable` public typealias U = Promise @@ -70,13 +74,11 @@ public class CancellablePromise: CancellableThenable, CancellableCatchMixin { if promise == nil { // Wrapper promise - promise = Promise { seal in - reject = seal.reject - bridge.done(on: CurrentThreadDispatcher()) { - seal.fulfill($0) - }.catch(on: CurrentThreadDispatcher(), policy: .allErrors) { - seal.reject($0) - } + let pending = Promise.pending() + (promise, reject) = (pending.promise, pending.resolver.reject) + promise.dispatchState = bridge.dispatchState + bridge.pipe { result in + pending.resolver.resolve(result) } } diff --git a/Sources/Cancellation/CancellableThenable.swift b/Sources/Cancellation/CancellableThenable.swift index 646ada924..6ef4ba593 100644 --- a/Sources/Cancellation/CancellableThenable.swift +++ b/Sources/Cancellation/CancellableThenable.swift @@ -79,7 +79,7 @@ public extension CancellableThenable { context.cancel() */ - func then(on: Dispatcher = conf.D.map, _ body: @escaping (U.T) throws -> V) -> CancellablePromise { + func then(on: Dispatcher = conf.dd, _ body: @escaping (U.T) throws -> V) -> CancellablePromise { let cancelItemList = CancelItemList() @@ -118,7 +118,7 @@ public extension CancellableThenable { context.cancel() */ - func then(on: Dispatcher = conf.D.map, _ body: @escaping (U.T) throws -> V) -> CancellablePromise { + func then(on: Dispatcher = conf.dd, _ body: @escaping (U.T) throws -> V) -> CancellablePromise { let cancelBody = { (value: U.T) throws -> V in if let error = self.cancelContext.removeItems(self.cancelItemList, clearList: true) { throw error @@ -152,7 +152,7 @@ public extension CancellableThenable { context.cancel() */ - func map(on: Dispatcher = conf.D.map, _ transform: @escaping (U.T) throws -> V) -> CancellablePromise { + func map(on: Dispatcher = conf.dd, _ transform: @escaping (U.T) throws -> V) -> CancellablePromise { let cancelTransform = { (value: U.T) throws -> V in if let error = self.cancelContext.removeItems(self.cancelItemList, clearList: true) { throw error @@ -184,7 +184,7 @@ public extension CancellableThenable { context.cancel() */ - func compactMap(on: Dispatcher = conf.D.map, _ transform: @escaping (U.T) throws -> V?) -> CancellablePromise { + func compactMap(on: Dispatcher = conf.dd, _ transform: @escaping (U.T) throws -> V?) -> CancellablePromise { let cancelTransform = { (value: U.T) throws -> V? in if let error = self.cancelContext.removeItems(self.cancelItemList, clearList: true) { throw error @@ -217,7 +217,7 @@ public extension CancellableThenable { context.cancel() */ - func done(on: Dispatcher = conf.D.return, _ body: @escaping (U.T) throws -> Void) -> CancellablePromise { + func done(on: Dispatcher = conf.dd, _ body: @escaping (U.T) throws -> Void) -> CancellablePromise { let cancelBody = { (value: U.T) throws -> Void in if let error = self.cancelContext.removeItems(self.cancelItemList, clearList: true) { throw error @@ -254,7 +254,7 @@ public extension CancellableThenable { context.cancel() */ - func get(on: Dispatcher = conf.D.return, _ body: @escaping (U.T) throws -> Void) -> CancellablePromise { + func get(on: Dispatcher = conf.dd, _ body: @escaping (U.T) throws -> Void) -> CancellablePromise { return map(on: on) { try body($0) return $0 @@ -272,11 +272,12 @@ public extension CancellableThenable { promise.tap{ print($0) }.then{ /*…*/ } */ - func tap(on: Dispatcher = conf.D.map, _ body: @escaping(Result) -> Void) -> CancellablePromise { + func tap(on: Dispatcher = conf.dd, _ body: @escaping(Result) -> Void) -> CancellablePromise { let rp = CancellablePromise.pending() rp.promise.cancelContext = self.cancelContext + rp.promise.dispatchState = self.thenable.dispatchState.nextState(givenDispatcher: on) self.thenable.pipe { result in - on.dispatch { + rp.promise.dispatch { if let error = self.cancelContext.removeItems(self.cancelItemList, clearList: true) { rp.resolver.reject(error) } else { @@ -287,7 +288,32 @@ public extension CancellableThenable { } return rp.promise } - + + /// Set a default Dispatcher for the chain. Within the chain, this Dispatcher will remain the + /// default until you change it, even if you dispatch individual closures to other Dispatchers. + /// + /// - Note: If you set a chain dispatcher within the body of a promise chain, you must + /// "confirm" the chain dispatcher when it gets to the tail to avoid a warning from + /// PromiseKit. To do this, just include `on: .chain` as an argument to the chain's + /// first `done`, `catch`, or `finally`. + /// + /// - Parameters: + /// - on: The new default Dispatcher. Use `.default` to return to normal dispatching. + + func dispatch(on: Dispatcher) -> CancellablePromise { + let rp = CancellablePromise.pending() + rp.promise.cancelContext = self.cancelContext + rp.promise.dispatchState = self.thenable.dispatchState.dispatch(on: on) + self.thenable.pipe { result in + if let error = self.cancelContext.removeItems(self.cancelItemList, clearList: true) { + rp.resolver.reject(error) + } else { + rp.resolver.resolve(result) + } + } + return rp.promise + } + /// - Returns: a new cancellable promise chained off this cancellable promise but with its value discarded. func asVoid() -> CancellablePromise { return map(on: nil) { _ in } @@ -350,7 +376,7 @@ public extension CancellableThenable where U.T: Sequence { // $0 => [2,4,6] } */ - func mapValues(on: Dispatcher = conf.D.map, _ transform: @escaping(U.T.Iterator.Element) throws -> V) -> CancellablePromise<[V]> { + func mapValues(on: Dispatcher = conf.dd, _ transform: @escaping(U.T.Iterator.Element) throws -> V) -> CancellablePromise<[V]> { return map(on: on) { try $0.map(transform) } } @@ -365,7 +391,7 @@ public extension CancellableThenable where U.T: Sequence { // $0 => [1,1,2,2,3,3] } */ - func flatMapValues(on: Dispatcher = conf.D.map, _ transform: @escaping(U.T.Iterator.Element) throws -> V) -> CancellablePromise<[V.Iterator.Element]> { + func flatMapValues(on: Dispatcher = conf.dd, _ transform: @escaping(U.T.Iterator.Element) throws -> V) -> CancellablePromise<[V.Iterator.Element]> { return map(on: on) { (foo: U.T) in try foo.flatMap { try transform($0) } } @@ -382,7 +408,7 @@ public extension CancellableThenable where U.T: Sequence { // $0 => [1,2,3] } */ - func compactMapValues(on: Dispatcher = conf.D.map, _ transform: @escaping(U.T.Iterator.Element) throws -> V?) -> CancellablePromise<[V]> { + func compactMapValues(on: Dispatcher = conf.dd, _ transform: @escaping(U.T.Iterator.Element) throws -> V?) -> CancellablePromise<[V]> { return map(on: on) { foo -> [V] in return try foo.compactMap(transform) } @@ -399,7 +425,7 @@ public extension CancellableThenable where U.T: Sequence { // $0 => [2,4,6] } */ - func thenMap(on: Dispatcher = conf.D.map, _ transform: @escaping(U.T.Iterator.Element) throws -> V) -> CancellablePromise<[V.U.T]> { + func thenMap(on: Dispatcher = conf.dd, _ transform: @escaping(U.T.Iterator.Element) throws -> V) -> CancellablePromise<[V.U.T]> { return then(on: on) { when(fulfilled: try $0.map(transform)) } @@ -416,7 +442,7 @@ public extension CancellableThenable where U.T: Sequence { // $0 => [2,4,6] } */ - func thenMap(on: Dispatcher = conf.D.map, _ transform: @escaping(U.T.Iterator.Element) throws -> V) -> CancellablePromise<[V.T]> { + func thenMap(on: Dispatcher = conf.dd, _ transform: @escaping(U.T.Iterator.Element) throws -> V) -> CancellablePromise<[V.T]> { return then(on: on) { when(fulfilled: try $0.map(transform)) } @@ -433,7 +459,7 @@ public extension CancellableThenable where U.T: Sequence { // $0 => [1,1,2,2,3,3] } */ - func thenFlatMap(on: Dispatcher = conf.D.map, _ transform: @escaping(U.T.Iterator.Element) throws -> V) -> CancellablePromise<[V.U.T.Iterator.Element]> where V.U.T: Sequence { + func thenFlatMap(on: Dispatcher = conf.dd, _ transform: @escaping(U.T.Iterator.Element) throws -> V) -> CancellablePromise<[V.U.T.Iterator.Element]> where V.U.T: Sequence { return then(on: on) { when(fulfilled: try $0.map(transform)) }.map(on: nil) { @@ -452,7 +478,7 @@ public extension CancellableThenable where U.T: Sequence { // $0 => [1,1,2,2,3,3] } */ - func thenFlatMap(on: Dispatcher = conf.D.map, _ transform: @escaping(U.T.Iterator.Element) throws -> V) -> CancellablePromise<[V.T.Iterator.Element]> where V.T: Sequence { + func thenFlatMap(on: Dispatcher = conf.dd, _ transform: @escaping(U.T.Iterator.Element) throws -> V) -> CancellablePromise<[V.T.Iterator.Element]> where V.T: Sequence { return then(on: on) { when(fulfilled: try $0.map(transform)) }.map(on: nil) { @@ -471,7 +497,7 @@ public extension CancellableThenable where U.T: Sequence { // $0 => [2,3] } */ - func filterValues(on: Dispatcher = conf.D.map, _ isIncluded: @escaping (U.T.Iterator.Element) -> Bool) -> CancellablePromise<[U.T.Iterator.Element]> { + func filterValues(on: Dispatcher = conf.dd, _ isIncluded: @escaping (U.T.Iterator.Element) -> Bool) -> CancellablePromise<[U.T.Iterator.Element]> { return map(on: on) { $0.filter(isIncluded) } @@ -490,7 +516,7 @@ public extension CancellableThenable where U.T: Collection { } } - func firstValue(on: Dispatcher = conf.D.map, where test: @escaping (U.T.Iterator.Element) -> Bool) -> CancellablePromise { + func firstValue(on: Dispatcher = conf.dd, where test: @escaping (U.T.Iterator.Element) -> Bool) -> CancellablePromise { return map(on: on) { for x in $0 where test(x) { return x @@ -514,7 +540,7 @@ public extension CancellableThenable where U.T: Collection { public extension CancellableThenable where U.T: Sequence, U.T.Iterator.Element: Comparable { /// - Returns: a cancellable promise fulfilled with the sorted values of this `Sequence`. - func sortedValues(on: Dispatcher = conf.D.map) -> CancellablePromise<[U.T.Iterator.Element]> { + func sortedValues(on: Dispatcher = conf.dd) -> CancellablePromise<[U.T.Iterator.Element]> { return map(on: on) { $0.sorted() } } } diff --git a/Sources/Catchable.swift b/Sources/Catchable.swift index e3c75e987..d3b88dadd 100644 --- a/Sources/Catchable.swift +++ b/Sources/Catchable.swift @@ -21,20 +21,21 @@ public extension CatchMixin { - SeeAlso: [Cancellation](https://github.com/mxcl/PromiseKit/blob/master/Documents/CommonPatterns.md#cancellation) */ @discardableResult - func `catch`(on: Dispatcher = conf.D.return, policy: CatchPolicy = conf.catchPolicy, _ body: @escaping(Error) -> Void) -> PMKFinalizer { + func `catch`(on: Dispatcher = conf.dd, policy: CatchPolicy = conf.catchPolicy, _ body: @escaping(Error) -> Void) -> PMKFinalizer { let finalizer = PMKFinalizer() + finalizer.dispatchState = dispatchState.nextState(givenDispatcher: on, isTailFunction: true) pipe { switch $0 { case .failure(let error): guard policy == .allErrors || !error.isCancelled else { fallthrough } - on.dispatch { + finalizer.dispatch { body(error) - finalizer.pending.resolve(()) + finalizer.fulfill() } case .success: - finalizer.pending.resolve(()) + finalizer.fulfill() } } return finalizer @@ -55,19 +56,20 @@ public extension CatchMixin { - Note: Since this method handles only specific errors, supplying a `CatchPolicy` is unsupported. - SeeAlso: [Cancellation](http://promisekit.org/docs/) */ - func `catch`(only: E, on: Dispatcher = conf.D.return, _ body: @escaping(E) -> Void) -> PMKCascadingFinalizer where E: Equatable { + func `catch`(only: E, on: Dispatcher = conf.dd, _ body: @escaping(E) -> Void) -> PMKCascadingFinalizer where E: Equatable { let finalizer = PMKCascadingFinalizer() + finalizer.dispatchState = dispatchState.nextState(givenDispatcher: on, isTailFunction: true) pipe { switch $0 { case .failure(let error as E) where error == only: - on.dispatch { + finalizer.dispatch { body(error) - finalizer.pending.resolver.fulfill(()) + finalizer.fulfill() } case .failure(let error): - finalizer.pending.resolver.reject(error) + finalizer.reject(error) case .success: - finalizer.pending.resolver.fulfill(()) + finalizer.fulfill() } } return finalizer @@ -89,41 +91,65 @@ public extension CatchMixin { - Returns: A promise finalizer that accepts additional `catch` clauses. - SeeAlso: [Cancellation](http://promisekit.org/docs/) */ - func `catch`(only: E.Type, on: Dispatcher = conf.D.return, policy: CatchPolicy = conf.catchPolicy, _ body: @escaping(E) -> Void) -> PMKCascadingFinalizer { + func `catch`(only: E.Type, on: Dispatcher = conf.dd, policy: CatchPolicy = conf.catchPolicy, _ body: @escaping(E) -> Void) -> PMKCascadingFinalizer + { let finalizer = PMKCascadingFinalizer() + finalizer.dispatchState = dispatchState.nextState(givenDispatcher: on, isTailFunction: true) pipe { switch $0 { case .failure(let error as E): guard policy == .allErrors || !error.isCancelled else { - return finalizer.pending.resolver.reject(error) + return finalizer.reject(error) } - on.dispatch { + finalizer.dispatch { body(error) - finalizer.pending.resolver.fulfill(()) + finalizer.fulfill() } case .failure(let error): - finalizer.pending.resolver.reject(error) + finalizer.reject(error) case .success: - finalizer.pending.resolver.fulfill(()) + finalizer.fulfill() } } return finalizer } } -public class PMKFinalizer { - let pending = Guarantee.pending() +public class PMKFinalizer: HasDispatchState { + + private let pending = Guarantee.pending() + var dispatchState: DispatchState { + get { return pending.guarantee.dispatchState } + set { pending.guarantee.dispatchState = newValue } + } /// `finally` is the same as `ensure`, but it is not chainable - public func finally(on: Dispatcher = conf.D.return, _ body: @escaping () -> Void) { + public func finally(on: Dispatcher = conf.dd, _ body: @escaping () -> Void) { pending.guarantee.done(on: on) { body() } } + + func fulfill() { + pending.resolve(()) + } } -public class PMKCascadingFinalizer { - let pending = Promise.pending() +public class PMKCascadingFinalizer: HasDispatchState { + + private let pending = Promise.pending() + var dispatchState: DispatchState { + get { return pending.promise.dispatchState } + set { pending.promise.dispatchState = newValue } + } + + func fulfill() { + pending.resolver.fulfill(()) + } + + func reject(_ error: Error) { + pending.resolver.reject(error) + } /** The provided closure executes when this promise rejects. @@ -140,7 +166,7 @@ public class PMKCascadingFinalizer { - SeeAlso: [Cancellation](http://promisekit.org/docs/) */ @discardableResult - public func `catch`(on: Dispatcher = conf.D.return, policy: CatchPolicy = conf.catchPolicy, _ body: @escaping(Error) -> Void) -> PMKFinalizer { + public func `catch`(on: Dispatcher = conf.dd, policy: CatchPolicy = conf.catchPolicy, _ body: @escaping(Error) -> Void) -> PMKFinalizer { return pending.promise.catch(on: on, policy: policy) { body($0) } @@ -161,7 +187,7 @@ public class PMKCascadingFinalizer { - Note: Since this method handles only specific errors, supplying a `CatchPolicy` is unsupported. - SeeAlso: [Cancellation](http://promisekit.org/docs/) */ - public func `catch`(only: E, on: Dispatcher = conf.D.return, _ body: @escaping(E) -> Void) -> PMKCascadingFinalizer where E: Equatable { + public func `catch`(only: E, on: Dispatcher = conf.dd, _ body: @escaping(E) -> Void) -> PMKCascadingFinalizer where E: Equatable { return pending.promise.catch(only: only, on: on) { body($0) } @@ -181,12 +207,26 @@ public class PMKCascadingFinalizer { - Returns: A promise finalizer that accepts additional `catch` clauses. - SeeAlso: [Cancellation](http://promisekit.org/docs/) */ - public func `catch`(only: E.Type, on: Dispatcher = conf.D.return, policy: CatchPolicy = conf.catchPolicy, _ body: @escaping(E) -> Void) -> PMKCascadingFinalizer { + public func `catch`(only: E.Type, on: Dispatcher = conf.dd, policy: CatchPolicy = conf.catchPolicy, _ body: @escaping(E) -> Void) -> PMKCascadingFinalizer + { return pending.promise.catch(only: only, on: on, policy: policy) { body($0) } } + /// Set a default Dispatcher for the chain. Within the chain, this Dispatcher will remain the + /// default until you change it, even if you dispatch individual closures to other Dispatchers. + /// + /// - Parameters: + /// - on: The new default Dispatcher. Use `.default` to return to normal dispatching. + + public func dispatch(on: Dispatcher) -> PMKCascadingFinalizer { + let nextFinalizer = PMKCascadingFinalizer() + nextFinalizer.dispatchState = dispatchState.dispatch(on: on) + pending.promise.pipe(to: nextFinalizer.pending.resolver.resolve) + return nextFinalizer + } + /** Consumes the Swift unused-result warning. - Note: You should `catch`, but in situations where you know you don’t need a `catch`, `cauterize` makes your intentions clear. @@ -219,15 +259,16 @@ public extension CatchMixin { - Parameter body: The handler to execute if this promise is rejected. - SeeAlso: [Cancellation](https://github.com/mxcl/PromiseKit/blob/master/Documents/CommonPatterns.md#cancellation) */ - func recover(on: Dispatcher = conf.D.map, policy: CatchPolicy = conf.catchPolicy, _ body: @escaping(Error) throws -> U) -> Promise where U.T == T { + func recover(on: Dispatcher = conf.dd, policy: CatchPolicy = conf.catchPolicy, _ body: @escaping(Error) throws -> U) -> Promise where U.T == T { let rp = Promise(.pending) + rp.dispatchState = dispatchState.nextState(givenDispatcher: on) pipe { switch $0 { case .success(let value): rp.box.seal(.success(value)) case .failure(let error): if policy == .allErrors || !error.isCancelled { - on.dispatch { + rp.dispatch { do { let rv = try body(error) guard rv !== rp else { throw PMKError.returnedSelf } @@ -262,14 +303,15 @@ public extension CatchMixin { - Note: Since this method recovers only specific errors, supplying a `CatchPolicy` is unsupported. - SeeAlso: [Cancellation](http://promisekit.org/docs/) */ - func recover(only: E, on: Dispatcher = conf.D.map, _ body: @escaping(E) throws -> U) -> Promise where U.T == T, E: Equatable { + func recover(only: E, on: Dispatcher = conf.dd, _ body: @escaping(E) throws -> U) -> Promise where U.T == T, E: Equatable { let rp = Promise(.pending) + rp.dispatchState = dispatchState.nextState(givenDispatcher: on) pipe { switch $0 { case .success(let value): rp.box.seal(.success(value)) case .failure(let error as E) where error == only: - on.dispatch { + rp.dispatch { do { let rv = try body(error) guard rv !== rp else { throw PMKError.returnedSelf } @@ -305,15 +347,17 @@ public extension CatchMixin { - Parameter body: The handler to execute if this promise is rejected with the provided error type. - SeeAlso: [Cancellation](http://promisekit.org/docs/) */ - func recover(only: E.Type, on: Dispatcher = conf.D.map, policy: CatchPolicy = conf.catchPolicy, _ body: @escaping(E) throws -> U) -> Promise where U.T == T { + func recover(only: E.Type, on: Dispatcher = conf.dd, policy: CatchPolicy = conf.catchPolicy, _ body: @escaping(E) throws -> U) -> Promise where U.T == T + { let rp = Promise(.pending) + rp.dispatchState = dispatchState.nextState(givenDispatcher: on) pipe { switch $0 { case .success(let value): rp.box.seal(.success(value)) case .failure(let error as E): if policy == .allErrors || !error.isCancelled { - on.dispatch { + rp.dispatch { do { let rv = try body(error) guard rv !== rp else { throw PMKError.returnedSelf } @@ -344,14 +388,15 @@ public extension CatchMixin { - SeeAlso: [Cancellation](https://github.com/mxcl/PromiseKit/blob/master/Documents/CommonPatterns.md#cancellation) */ @discardableResult - func recover(on: Dispatcher = conf.D.map, _ body: @escaping(Error) -> Guarantee) -> Guarantee { + func recover(on: Dispatcher = conf.dd, _ body: @escaping(Error) -> Guarantee) -> Guarantee { let rg = Guarantee(.pending) + rg.dispatchState = dispatchState.nextState(givenDispatcher: on) pipe { switch $0 { case .success(let value): rg.box.seal(value) case .failure(let error): - on.dispatch { + rg.dispatch { body(error).pipe(to: rg.box.seal) } } @@ -376,10 +421,11 @@ public extension CatchMixin { - Parameter body: The closure that executes when this promise resolves. - Returns: A new promise, resolved with this promise’s resolution. */ - func ensure(on: Dispatcher = conf.D.return, _ body: @escaping () -> Void) -> Promise { + func ensure(on: Dispatcher = conf.dd, _ body: @escaping () -> Void) -> Promise { let rp = Promise(.pending) + rp.dispatchState = dispatchState.nextState(givenDispatcher: on) pipe { result in - on.dispatch { + rp.dispatch { body() rp.box.seal(result) } @@ -405,10 +451,11 @@ public extension CatchMixin { - Parameter body: The closure that executes when this promise resolves. - Returns: A new promise, resolved with this promise’s resolution. */ - func ensureThen(on: Dispatcher = conf.D.return, _ body: @escaping () -> Guarantee) -> Promise { + func ensureThen(on: Dispatcher = conf.dd, _ body: @escaping () -> Guarantee) -> Promise { let rp = Promise(.pending) + rp.dispatchState = dispatchState.nextState(givenDispatcher: on) pipe { result in - on.dispatch { + rp.dispatch { body().done { rp.box.seal(result) } @@ -417,7 +464,6 @@ public extension CatchMixin { return rp } - /** Consumes the Swift unused-result warning. - Note: You should `catch`, but in situations where you know you don’t need a `catch`, `cauterize` makes your intentions clear. @@ -445,14 +491,15 @@ public extension CatchMixin where T == Void { - SeeAlso: [Cancellation](https://github.com/mxcl/PromiseKit/blob/master/Documents/CommonPatterns.md#cancellation) */ @discardableResult - func recover(on: Dispatcher = conf.D.map, _ body: @escaping(Error) -> Void) -> Guarantee { + func recover(on: Dispatcher = conf.dd, _ body: @escaping(Error) -> Void) -> Guarantee { let rg = Guarantee(.pending) + rg.dispatchState = dispatchState.nextState(givenDispatcher: on) pipe { switch $0 { case .success: rg.box.seal(()) case .failure(let error): - on.dispatch { + rg.dispatch { body(error) rg.box.seal(()) } @@ -471,15 +518,16 @@ public extension CatchMixin where T == Void { - Parameter body: The handler to execute if this promise is rejected. - SeeAlso: [Cancellation](https://github.com/mxcl/PromiseKit/blob/master/Documents/CommonPatterns.md#cancellation) */ - func recover(on: Dispatcher = conf.D.map, policy: CatchPolicy = conf.catchPolicy, _ body: @escaping(Error) throws -> Void) -> Promise { + func recover(on: Dispatcher = conf.dd, policy: CatchPolicy = conf.catchPolicy, _ body: @escaping(Error) throws -> Void) -> Promise { let rg = Promise(.pending) + rg.dispatchState = dispatchState.nextState(givenDispatcher: on) pipe { switch $0 { case .success: rg.box.seal(.success(())) case .failure(let error): if policy == .allErrors || !error.isCancelled { - on.dispatch { + rg.dispatch { do { rg.box.seal(.success(try body(error))) } catch { @@ -506,14 +554,15 @@ public extension CatchMixin where T == Void { - Note: Since this method recovers only specific errors, supplying a `CatchPolicy` is unsupported. - SeeAlso: [Cancellation](http://promisekit.org/docs/) */ - func recover(only: E, on: Dispatcher = conf.D.map, _ body: @escaping(E) throws -> Void) -> Promise where E: Equatable { + func recover(only: E, on: Dispatcher = conf.dd, _ body: @escaping(E) throws -> Void) -> Promise where E: Equatable { let rp = Promise(.pending) + rp.dispatchState = dispatchState.nextState(givenDispatcher: on) pipe { switch $0 { case .success: rp.box.seal(.success(())) case .failure(let error as E) where error == only: - on.dispatch { + rp.dispatch { do { rp.box.seal(.success(try body(error))) } catch { @@ -539,15 +588,16 @@ public extension CatchMixin where T == Void { - Parameter body: The handler to execute if this promise is rejected with the provided error type. - SeeAlso: [Cancellation](http://promisekit.org/docs/) */ - func recover(only: E.Type, on: Dispatcher = conf.D.map, policy: CatchPolicy = conf.catchPolicy, _ body: @escaping(E) throws -> Void) -> Promise { + func recover(only: E.Type, on: Dispatcher = conf.dd, policy: CatchPolicy = conf.catchPolicy, _ body: @escaping(E) throws -> Void) -> Promise { let rp = Promise(.pending) + rp.dispatchState = dispatchState.nextState(givenDispatcher: on) pipe { switch $0 { case .success: rp.box.seal(.success(())) case .failure(let error as E): if policy == .allErrors || !error.isCancelled { - on.dispatch { + rp.dispatch { do { rp.box.seal(.success(try body(error))) } catch { diff --git a/Sources/Configuration.swift b/Sources/Configuration.swift index 801527414..3017ba7f0 100644 --- a/Sources/Configuration.swift +++ b/Sources/Configuration.swift @@ -1,25 +1,45 @@ import Dispatch -/** - PromiseKit’s configurable parameters. +// PromiseKit’s configurable parameters. - Do not change these after any Promise machinery executes as the configuration object is not thread-safe. +public struct NoValue: Dispatcher { + public init() {} + public func dispatch(_ body: @escaping () -> Void) { + fatalError("NoValue dispatcher should never actually be used as a dispatcher") + } +} - We would like it to be, but sadly `Swift` does not expose `dispatch_once` et al. which is what we used to use in order to make the configuration immutable once first used. -*/ public struct PMKConfiguration { + + public var requireChainDispatcherConfirmation = true + public let dd: Dispatcher = SentinelDispatcher(type: .unspecified, flags: nil) // Default dispatcher as a function argument + /// Backward compatibility: the default Dispatcher to which handlers dispatch, represented as DispatchQueues. + @available(*, deprecated, message: "Use conf.setDefaultDispatchers(body:tail:) to set default dispatchers in PromiseKit 7+") public var Q: (map: DispatchQueue?, return: DispatchQueue?) { get { - let convertedMap = D.map is CurrentThreadDispatcher ? nil : D.map as? DispatchQueue - let convertedReturn = D.return is CurrentThreadDispatcher ? nil : D.return as? DispatchQueue + let convertedMap = _D.body is CurrentThreadDispatcher ? nil : _D.body as? DispatchQueue + let convertedReturn = _D.tail is CurrentThreadDispatcher ? nil : _D.tail as? DispatchQueue return (map: convertedMap, return: convertedReturn) } - set { D = (map: newValue.map ?? CurrentThreadDispatcher(), return: newValue.return ?? CurrentThreadDispatcher()) } + set { + verifyDUnread() + _D = (body: newValue.map ?? CurrentThreadDispatcher(), tail: newValue.return ?? CurrentThreadDispatcher()) + } } /// The default Dispatchers to which promise handlers dispatch - public var D: (map: Dispatcher, return: Dispatcher) = (map: DispatchQueue.main, return: DispatchQueue.main) + private static let defaultBodyDispatcher = DispatchQueue.main + private static let defaultTailDispatcher = DispatchQueue.main + + internal var _D: (body: Dispatcher, tail: Dispatcher) = (body: DispatchQueue.main, tail: DispatchQueue.main) + internal var D: (body: Dispatcher, tail: Dispatcher) { + mutating get { dRead = true; return _D } + set { _D = newValue } + } + + private var dRead = false + internal var testMode = false /// The default catch-policy for all `catch` and `resolve` public var catchPolicy = CatchPolicy.allErrorsExceptCancellation @@ -30,6 +50,43 @@ public struct PMKConfiguration { public var logHandler: (LogEvent) -> () = { event in print(event.asString()) } + + private func verifyDUnread() { + if dRead && !testMode { + conf.logHandler(.defaultDispatchersReset) + } + } + + mutating public func setDefaultDispatchers(body: Dispatcher = NoValue(), tail: Dispatcher = NoValue()) { + verifyDUnread() + if !(body is NoValue) { _D.body = body } + if !(tail is NoValue) { _D.tail = tail } + } + + fileprivate func determineDispatcher(_ dispatcher: DispatchQueue?, default: Dispatcher) -> Dispatcher? { + switch dispatcher { + case nil: + return CurrentThreadDispatcher() + case DispatchQueue.unspecified?: + return nil // Do nothing + case DispatchQueue.default?: + return `default` + case DispatchQueue.chain?: + fatalError("PromiseKit: .chain is not meaningful in the context of setDefaultDispatchers") + default: + return dispatcher! + } + } + + mutating public func setDefaultDispatchers(body: DispatchQueue? = .unspecified, tail: DispatchQueue? = .unspecified) { + verifyDUnread() + if let newBody = determineDispatcher(body, default: PMKConfiguration.defaultBodyDispatcher) { + _D.body = newBody + } + if let newTail = determineDispatcher(tail, default: PMKConfiguration.defaultTailDispatcher) { + _D.tail = newTail + } + } } /// Modify this as soon as possible in your application’s lifetime diff --git a/Sources/DispatchState.swift b/Sources/DispatchState.swift new file mode 100644 index 000000000..8f437c211 --- /dev/null +++ b/Sources/DispatchState.swift @@ -0,0 +1,298 @@ +import Dispatch + +/// A LocationWithinChain is a little state machine that tracks progress along the promise chain. +/// It detects and remembers the boundary between the body and the tail of the chain. The +/// tail begins at the first instance of `done`, `catch`, or `finally`. +/// +/// In addition, a chain dispatcher that was set somewhere within the body of a chain requires +/// "confirmation" at the point of transition to the tail. An unconfirmed chain executes normally, +/// but PromiseKit logs an error. +/// +/// To confirm, the first use of a chain dispatcher within the tail must be explicit. The easiest +/// way to do this is simply to include `on: .chain` in the argument of a PromiseKit function. +/// You may also set a chain dispatcher explicitly through `dispatch(on:)`. +/// +/// The idea is that it's easy to receive a chain with a chain dispatcher from lower-level code, +/// or to set a dispatcher without realizing that it will continue all the way to the end of the +/// chain unless you explicitly cancel it. So the user has to acknowledge that they want the chain +/// dispatcher to continue or they'll be warned. + +enum LocationWithinChain { + + case inBody // Known to be prior to body/tail transition + case inTail // Have entered tail, but not reached confirmation point (if there is one) + case primed // Chain dispatcher set, but we haven't seen the next closure yet; could be body or tail + case confirmed // API calls have confirmed the chain dispatcher, if any + case warned // Did not confirm the chain dispatcher; did (or should) warn + + var isInTail: Bool { + return self == .inTail || self == .confirmed || self == .warned + } + + /// Determine the next LocationWithinChain using the old value and information about the + /// context of the next dispatch. + /// + /// - Parameters: + /// - isTail: Current function is a tail function (`done`, `catch`, or `finally`) + /// - explicit: Current dispatch is explicit (e.g., `on: dispatcher`) + /// - hasChain: This chain has a chain dispatcher + /// - usedChain: The upcoming dispatcher was determined by the chain dispatcher + + mutating func advanceLocation(isTailFunction isTail: Bool, explicitDispatch explicit: Bool, hasChain: Bool, usedChain: Bool) { + switch self { + case .inBody: + if !isTail { self = .inBody; return } + fallthrough + case .inTail: + if !hasChain { self = .inTail; return } + switch (explicit, usedChain) { + case (true, true): self = .confirmed + case (true, false): self = .inTail + case (false, _): self = .warned + } + case .primed: + if !hasChain { fatalError("Shouldn't happen: primed confirmation state with no chain dispatcher") } + self = isTail ? .confirmed : .inBody + case .confirmed, .warned: + // Terminal states + return + } + } + + // Called by `dispatch(on: .chain), which doesn't have an inherent "is tail function" value + mutating func confirm() { + if self == .inTail || self == .primed { + self = .confirmed + } + } +} + +protocol HasDispatchState { + var dispatchState: DispatchState { get set } +} + +/// If you have a DispatchState, you can dispatch to it. This is essentially the +/// Dispatcher protocol, but that's public and all the DispatchState-related +/// items are internal. + +extension HasDispatchState { + func dispatch(_ body: @escaping () -> Void) { + dispatchState.dispatch(body) + } +} + +typealias SourcedDispatcher = ( + dispatcher: Dispatcher, // Dispatcher to use for dispatching this closure + explicit: Bool // Dispatcher chosen explicitly, or is it an implicit or default selection? +) + +/// A DispatchState tracks the state of the promise chain and stores the dispatcher +/// to be used for upcoming closures. It also tracks the chain dispatcher, if there +/// is one. +/// +/// Every Thenable has a DispatchState. + +struct DispatchState: Dispatcher { + + fileprivate var dispatcher: Dispatcher = conf.dd // Most recently used, or for next state, dispatcher to use + private var chainStrategy: ChainDispatchStrategy? // Active chain dispatcher, if any + private var location: LocationWithinChain = .inBody // Confirmation process tracker + + /// This is just a notational/Demeter convenience so clients don't have to fish out the + /// dispatcher themselves. DispatchStates should not be used as regular Dispatchers. + + func dispatch(_ body: @escaping () -> Void) { + dispatcher.dispatch(body) + } + + /// Calculate the next state, configured for chain dispatching + + func dispatch(on: Dispatcher) -> DispatchState { + + var state = nextState(givenDispatcher: on) + + if let on = on as? SentinelDispatcher { + switch on.type { + case .unspecified: + fatalError("`dispatch(on:)` requires a specific dispatcher") + case .default: + state.chainStrategy = nil + // Not necessary to update phase + case .chain: + if state.chainStrategy != nil { + conf.logHandler(.chainWithoutChainDispatcher) + } else { + // Does not change chain dispatcher, but does confirm if appropriate + state.location.confirm() + } + case .sticky: + state.chainStrategy = StickyStrategy() + state.location = .primed + } + } else { + state.chainStrategy = StandardStrategy(dispatcher: state.dispatcher) + state.location = .primed + } + + return state + } + + /// Given a Dispatcher (which may be a SentinelDispatcher encoding various special options (`.default`, + /// `.chain`, `.sticky`, or not specified) and an indication of whether the current function is a + /// tail-initiating function (that is, `done`, `catch`, or `finally`), produce the next DispatchState. + /// This is the DispatchState for the promise to be returned by the current function. + + func nextState(givenDispatcher given: Dispatcher, isTailFunction isTail: Bool = false) -> DispatchState { + + var nextState = self + + // First offer the context to the chain dispatcher (if any) to see if it's interested in + // handling the current situation. If not, fall back to standard dispatching. + if let chainDispatcher = chainStrategy, let chainResponse = chainDispatcher.nextDispatcher(previous: self, givenDispatcher: given) { + nextState.dispatcher = chainResponse.dispatcher + nextState.location.advanceLocation(isTailFunction: isTail, explicitDispatch: chainResponse.explicit, + hasChain: true, usedChain: true) + } else { + let disp = nextDispatcher(givenDispatcher: given, isTailFunction: isTail) + nextState.dispatcher = disp.dispatcher + nextState.location.advanceLocation(isTailFunction: isTail, explicitDispatch: disp.explicit, + hasChain: chainStrategy != nil, usedChain: false) + } + + // If we still have the initially-assigned default dispatcher, replace it with the global default + if let sentinel = nextState.dispatcher as? SentinelDispatcher, sentinel.type == .unspecified { + nextState.dispatcher = isTail || nextState.location.isInTail ? conf.D.tail : conf.D.body + } + + // If we have DispatchWorkItemFlags from a wrapper invocation, try to apply them to the dispatcher. + if let sentinel = given as? SentinelDispatcher { + nextState.dispatcher = sentinel.applyFlags(to: nextState.dispatcher) + } + + // If we are transitioning into the `.warned` confirmation state, print a warning about + // the need to confirm the chain dispatcher. + if location != .warned && nextState.location == .warned && conf.requireChainDispatcherConfirmation { + conf.logHandler(.failedToConfirmChainDispatcher) + } + + return nextState + } + + /// This is the basic dispatcher selection engine. In addition to an actual dispatcher, it + /// returns an indication of whether the dispatcher was selected explicitly or implicitly. + + private func nextDispatcher(givenDispatcher given: Dispatcher, isTailFunction isTail: Bool) -> SourcedDispatcher { + + var defaultDispatcher: Dispatcher { return isTail || location.isInTail ? conf.D.tail : conf.D.body } + + // Fake dispatcher - sentinel values propagated from wrapper + if let sentinel = given as? SentinelDispatcher { + switch sentinel.type { + case .unspecified: + return (dispatcher: defaultDispatcher, explicit: false) + case .default: + return (dispatcher: defaultDispatcher, explicit: true) + case .chain: + // ChainDispatchStrategy should have claimed this + fatalError("`on: .chain` used without a chain dispatcher") + case .sticky: + // Repeat previous dispatcher + return (dispatcher: self.dispatcher, explicit: true) + } + } + + // Real dispatcher + return (dispatcher: given, explicit: true) + } +} + +/// Chain dispatchers can potentially participate actively in the dispatch process, +/// so they are strategy objects rather than just flat Dispatchers. A chain dispatch +/// strategy may decline to participate in dispatcher selection by returning nil. + +protocol ChainDispatchStrategy { + func nextDispatcher(previous: DispatchState, givenDispatcher given: Dispatcher) -> SourcedDispatcher? +} + +/// Mimics a flat chain Dispatcher object +struct StandardStrategy: ChainDispatchStrategy { + + let dispatcher: Dispatcher + + func nextDispatcher(previous: DispatchState, givenDispatcher given: Dispatcher) -> SourcedDispatcher? { + + // Chain dispatcher has nothing to say about explicitly specified dispatchers + guard let sentinel = given as? SentinelDispatcher else { return nil } + + switch sentinel.type { + case .default: + // Explicit; punt + return nil + case .unspecified: + return (dispatcher: dispatcher, explicit: false) + case .chain: + return (dispatcher: dispatcher, explicit: true) + case .sticky: + return nil + + } + } +} + +/// The sticky strategy repeats the previous Dispatcher selection +struct StickyStrategy: ChainDispatchStrategy { + + func nextDispatcher(previous: DispatchState, givenDispatcher given: Dispatcher) -> SourcedDispatcher? { + + if let sentinel = given as? SentinelDispatcher { + switch sentinel.type { + case .default: + // Explicit; punt + return nil + case .unspecified: + return (dispatcher: previous.dispatcher, explicit: false) + case .chain, .sticky: + return (dispatcher: previous.dispatcher, explicit: true) + } + } else { + // Claim an explicit dispatcher because we do want to ratify the chain dispatcher in this case + return (dispatcher: given, explicit: true) + } + } +} + +// Unfortunately, DispatchState is a PromiseKit internal type, so it's impossible +// to add a protocol to, e.g., Thenable to certify that any particular Thenable +// has a DispatchState. The protocol would have to mention DispatchState, and +// therefore have "internal" access level, but you can't extend an internal protocol +// in a public protocol. This glue skirts that issue. + +extension Thenable { + var dispatchState: DispatchState { + get { return getDispatchState(self) } + set { setDispatchState(self, state: newValue) } + } +} + +extension CatchMixin { + var dispatchState: DispatchState { + get { return getDispatchState(self) } + set { setDispatchState(self, state: newValue) } + } +} + +func getDispatchState(_ entity: AnyObject) -> DispatchState { + if let hasState = entity as? HasDispatchState { + return hasState.dispatchState + } else { + fatalError("PromiseKit base object that should have a DispatchState in fact does not") + } +} + +func setDispatchState(_ entity: AnyObject, state: DispatchState) { + if var hasState = entity as? HasDispatchState { + hasState.dispatchState = state + } else { + fatalError("PromiseKit base object that should have a DispatchState in fact does not") + } +} diff --git a/Sources/Dispatcher.swift b/Sources/Dispatcher.swift index e27d92220..444a47bb9 100644 --- a/Sources/Dispatcher.swift +++ b/Sources/Dispatcher.swift @@ -25,7 +25,7 @@ public struct DispatchQueueDispatcher: Dispatcher { let queue: DispatchQueue let group: DispatchGroup? let qos: DispatchQoS? - let flags: DispatchWorkItemFlags? + var flags: DispatchWorkItemFlags? public init(queue: DispatchQueue, group: DispatchGroup? = nil, qos: DispatchQoS? = nil, flags: DispatchWorkItemFlags? = nil) { self.queue = queue @@ -33,6 +33,10 @@ public struct DispatchQueueDispatcher: Dispatcher { self.qos = qos self.flags = flags } + + mutating func replaceFlags(_ flags: DispatchWorkItemFlags) { + self.flags = flags + } public func dispatch(_ body: @escaping () -> Void) { queue.asyncD(group: group, qos: qos, flags: flags, execute: body) @@ -46,9 +50,8 @@ public struct DispatchQueueDispatcher: Dispatcher { /// multithreading while debugging `PromiseKit` chains. /// /// You can set `PromiseKit`'s default dispatching behavior to this mode -/// by setting `conf.Q.map` and/or `conf.Q.return` to `nil`. (This is the -/// same as assigning an instance of `CurrentThreadDispatcher` to these -/// variables.) +/// by calling conf.setDefaultDispatchers(body: nil, tail: nil) before +/// you create any promises. public struct CurrentThreadDispatcher: Dispatcher { public func dispatch(_ body: () -> Void) { @@ -62,13 +65,15 @@ extension DispatchQueue: Dispatcher { } } -// Used as default parameter for backward compatibility since clients may explicitly -// specify "nil" to turn off dispatching. We need to distinguish three cases: explicit -// queue, explicit nil, and no value specified. Dispatchers from conf.D cannot directly -// be used as default parameter values because they are not necessarily DispatchQueues. +// Sentinel values used in the API. Since Dispatcher is a protocol and cannot +// have static members, all sentinel values must go through the wrapper path. +// These are converted as rapidly as possible into SentinelDispatcher structs. public extension DispatchQueue { - static var pmkDefault = DispatchQueue(label: "org.promisekit.sentinel") + static let unspecified = DispatchQueue(label: "unspecified.promisekit.org") // Parameter not provided + static let `default` = DispatchQueue(label: "default.promisekit.org") // Explicit request for default behavior + static let chain = DispatchQueue(label: "chain.promisekit.org") // Execute on same Dispatcher as previous closure + static let sticky = DispatchQueue(label: "sticky.promisekit.org") // Reuse the previous dispatcher if not explicitly specified } public extension DispatchQueue { @@ -97,24 +102,37 @@ internal extension DispatchQueue { } } -// This hairball disambiguates all the various combinations of explicit arguments, default -// arguments, and configured defaults. In particular, a method that is given explicit work item -// flags but no DispatchQueue should still work (that is, the dispatcher should use those flags) -// as long as the configured default is actually some kind of DispatchQueue. +/// This function packages up a DispatchQueue and a DispatchWorkItemFlags? as a +/// Dispatcher for submission to the nonwrapper API. -internal func selectDispatcher(given: DispatchQueue?, configured: Dispatcher, flags: DispatchWorkItemFlags?) -> Dispatcher { - guard let given = given else { +extension DispatchQueue { + func convertToDispatcher(flags: DispatchWorkItemFlags?) -> Dispatcher { + switch self { + case .unspecified: return SentinelDispatcher(type: .unspecified, flags: flags) + case .default: return SentinelDispatcher(type: .default, flags: flags) + case .chain: return SentinelDispatcher(type: .chain, flags: flags) + case .sticky: return SentinelDispatcher(type: .sticky, flags: flags) + default: + if let flags = flags { + return self.asDispatcher(flags: flags) + } + return self + } + } +} + +extension Optional where Wrapped: DispatchQueue { + func convertToDispatcher(flags: DispatchWorkItemFlags?) -> Dispatcher { + if let mapped = map({ $0.convertToDispatcher(flags: flags) }) { return mapped } if flags != nil { - conf.logHandler(.nilDispatchQueueWithFlags) + conf.logHandler(.extraneousFlagsSpecified) } return CurrentThreadDispatcher() } - if given !== DispatchQueue.pmkDefault { - return given.asDispatcher(flags: flags) - } else if let flags = flags, let configured = configured as? DispatchQueue { - return configured.asDispatcher(flags: flags) - } else if flags != nil { - conf.logHandler(.extraneousFlagsSpecified) +} + +extension DispatchQueue { + static func ~=(_ a: DispatchQueue, _ b: DispatchQueue) -> Bool { + return a === b } - return configured } diff --git a/Sources/Dispatchers/SentinelDispatcher.swift b/Sources/Dispatchers/SentinelDispatcher.swift new file mode 100644 index 000000000..78ae1f73c --- /dev/null +++ b/Sources/Dispatchers/SentinelDispatcher.swift @@ -0,0 +1,34 @@ +import Dispatch + +/// Simple wrapper that packages dot arguments as a Dispatcher. + +struct SentinelDispatcher: Dispatcher { + + enum SentinelType { + case unspecified // Parameter not provided + case `default` // Explicit request for global default + case chain // Explicit request to use the chain dispatcher + case sticky // Continue using same dispatcher until changed + } + + let type: SentinelType + let flags: DispatchWorkItemFlags? + + func dispatch(_ body: @escaping () -> Void) { + fatalError("Attempted to dispatch a closure on a SentinelDispatcher") + } + + func applyFlags(to dispatcher: Dispatcher) -> Dispatcher { + guard let flags = flags else { return dispatcher } + // See if we can incorporate the provided flags into whatever dispatcher we ended up with + if var dqd = dispatcher as? DispatchQueueDispatcher { + dqd.replaceFlags(flags) + return dqd + } else if let queue = dispatcher as? DispatchQueue { + return queue.asDispatcher(flags: flags) + } else { + conf.logHandler(.extraneousFlagsSpecified) + return dispatcher + } + } +} diff --git a/Sources/Error.swift b/Sources/Error.swift index 1b8238a69..090236ab3 100644 --- a/Sources/Error.swift +++ b/Sources/Error.swift @@ -29,11 +29,8 @@ public enum PMKError: Error { /// `nil` was returned from `compactMap` case compactMap(Any, Any.Type) - /** - The lastValue or firstValue of a sequence was requested but the sequence was empty. - - Also used if all values of this collection failed the test passed to `firstValue(where:)`. - */ + /// The lastValue or firstValue of a sequence was requested but the sequence was empty. + /// Also used if all values of this collection failed the test passed to `firstValue(where:)`. case emptySequence } diff --git a/Sources/Guarantee.swift b/Sources/Guarantee.swift index a33e87d85..b1a10784a 100644 --- a/Sources/Guarantee.swift +++ b/Sources/Guarantee.swift @@ -5,8 +5,10 @@ import Dispatch A `Guarantee` is a functional abstraction around an asynchronous operation that cannot error. - See: `Thenable` */ -public final class Guarantee: Thenable { +public final class Guarantee: Thenable, HasDispatchState { + let box: PromiseKit.Box + var dispatchState: DispatchState = DispatchState() fileprivate init(box: SealedBox) { self.box = box @@ -115,10 +117,11 @@ public final class Guarantee: Thenable { public extension Guarantee { @discardableResult - func done(on: Dispatcher = conf.D.return, _ body: @escaping(T) -> Void) -> Guarantee { + func done(on: Dispatcher = conf.dd, _ body: @escaping(T) -> Void) -> Guarantee { let rg = Guarantee(.pending) + rg.dispatchState = dispatchState.nextState(givenDispatcher: on, isTailFunction: true) pipe { (value: T) in - on.dispatch { + rg.dispatch { body(value) rg.box.seal(()) } @@ -126,17 +129,18 @@ public extension Guarantee { return rg } - func get(on: Dispatcher = conf.D.return, _ body: @escaping (T) -> Void) -> Guarantee { + func get(on: Dispatcher = conf.dd, _ body: @escaping (T) -> Void) -> Guarantee { return map(on: on) { body($0) return $0 } } - func map(on: Dispatcher = conf.D.map, _ body: @escaping(T) -> U) -> Guarantee { + func map(on: Dispatcher = conf.dd, _ body: @escaping(T) -> U) -> Guarantee { let rg = Guarantee(.pending) + rg.dispatchState = dispatchState.nextState(givenDispatcher: on) pipe { value in - on.dispatch { + rg.dispatch { rg.box.seal(body(value)) } } @@ -144,16 +148,35 @@ public extension Guarantee { } @discardableResult - func then(on: Dispatcher = conf.D.map, _ body: @escaping(T) -> Guarantee) -> Guarantee { + func then(on: Dispatcher = conf.dd, _ body: @escaping(T) -> Guarantee) -> Guarantee { let rg = Guarantee(.pending) + rg.dispatchState = dispatchState.nextState(givenDispatcher: on) pipe { value in - on.dispatch { + rg.dispatch { body(value).pipe(to: rg.box.seal) } } return rg } + /// Set a default Dispatcher for the chain. Within the chain, this Dispatcher will remain the + /// default until you change it, even if you dispatch individual closures to other Dispatchers. + /// + /// - Note: If you set a chain dispatcher within the body of a promise chain, you must + /// "confirm" the chain dispatcher when it gets to the tail to avoid a warning from + /// PromiseKit. To do this, just include `on: .chain` as an argument to the chain's + /// first `done`, `catch`, or `finally`. + /// + /// - Parameters: + /// - on: The new default Dispatcher. Use `.default` to return to normal dispatching. + + func dispatch(on: Dispatcher) -> Guarantee { + let (guarantee, seal) = Guarantee.pending() + guarantee.dispatchState = dispatchState.dispatch(on: on) + pipe(to: seal) + return guarantee + } + func asVoid() -> Guarantee { return map(on: nil) { _ in } } @@ -194,7 +217,7 @@ public extension Guarantee where T: Sequence { // $0 => [2,4,6] } */ - func thenMap(on: Dispatcher = conf.D.map, _ transform: @escaping(T.Iterator.Element) -> Guarantee) -> Guarantee<[U]> { + func thenMap(on: Dispatcher = conf.dd, _ transform: @escaping(T.Iterator.Element) -> Guarantee) -> Guarantee<[U]> { return then(on: on) { when(fulfilled: $0.map(transform)) }.recover { diff --git a/Sources/LogEvent.swift b/Sources/LogEvent.swift index 7dc4349e6..1af71fb6c 100644 --- a/Sources/LogEvent.swift +++ b/Sources/LogEvent.swift @@ -28,12 +28,19 @@ public enum LogEvent { /// An error which occurred while resolving a promise was swallowed case cauterized(Error) - /// Odd arguments to DispatchQueue-compatibility layer - case nilDispatchQueueWithFlags - /// DispatchWorkItem flags specified for non-DispatchQueue Dispatcher case extraneousFlagsSpecified + + /// Entered the tail of a chain with a chain dispatcher and without confirming the intent to use + /// the chain dispatcher within the tail. + case failedToConfirmChainDispatcher + + /// Default dispatchers were modified after promises were created + case defaultDispatchersReset + /// Used `on: .chain` when no chain dispatcher is set + case chainWithoutChainDispatcher + public func asString() -> String { var message: String switch self { @@ -45,10 +52,14 @@ public enum LogEvent { message = " warning: pending guarantee deallocated" case .cauterized(let error): message = "cauterized-error: \(error)" - case .nilDispatchQueueWithFlags: - message = " warning: nil DispatchQueue specified, but DispatchWorkItemFlags were also supplied (ignored)" case .extraneousFlagsSpecified: message = " warning: DispatchWorkItemFlags flags specified, but default Dispatcher is not a DispatchQueue (ignored)" + case .failedToConfirmChainDispatcher: + return "Entered the tail of a promise chain with a chain-specific dispatcher (set by `dispatch(on:)`) without confirming the dispatcher. To confirm that it's the behavior you expect, and silence this warning, include an `on: .chain` parameter in the first call to `done`, `catch`, or `finally`." + case .defaultDispatchersReset: + return "Default dispatchers should not be modified after you have created promises. This restriction will be enforced in a future version of PromiseKit. Use `dispatch(on:)` to set local defaults." + case .chainWithoutChainDispatcher: + return "`on: .chain` was specified when no chain dispatcher is defined; ignoring" } return "PromiseKit:\(message)" } diff --git a/Sources/Promise.swift b/Sources/Promise.swift index 74e7d1db6..4f47ef03d 100644 --- a/Sources/Promise.swift +++ b/Sources/Promise.swift @@ -5,8 +5,10 @@ import Dispatch A `Promise` is a functional abstraction around a failable asynchronous operation. - See: `Thenable` */ -public final class Promise: Thenable, CatchMixin { +public final class Promise: Thenable, CatchMixin, HasDispatchState { + let box: Box> + var dispatchState: DispatchState = DispatchState() fileprivate init(box: SealedBox>) { self.box = box @@ -120,6 +122,24 @@ public final class Promise: Thenable, CatchMixin { self.cancellable = cancellable rejectIfCancelled = reject } + + /// Set a default Dispatcher for the chain. Within the chain, this Dispatcher will remain the + /// default until you change it, even if you dispatch individual closures to other Dispatchers. + /// + /// - Note: If you set a chain dispatcher within the body of a promise chain, you must + /// "confirm" the chain dispatcher when it gets to the tail to avoid a warning from + /// PromiseKit. To do this, just include `on: .chain` as an argument to the chain's + /// first `done`, `catch`, or `finally`. + /// + /// - Parameters: + /// - on: The new default Dispatcher. Use `.default` to return to normal dispatching. + + public func dispatch(on: Dispatcher) -> Promise { + let (promise, seal) = Promise.pending() + promise.dispatchState = dispatchState.dispatch(on: on) + pipe(to: seal.resolve) + return promise + } } public extension Promise { diff --git a/Sources/Thenable.swift b/Sources/Thenable.swift index 98206116f..28493b0cc 100644 --- a/Sources/Thenable.swift +++ b/Sources/Thenable.swift @@ -30,12 +30,13 @@ public extension Thenable { //… } */ - func then(on: Dispatcher = conf.D.map, _ body: @escaping(T) throws -> U) -> Promise { + func then(on: Dispatcher = conf.dd, _ body: @escaping(T) throws -> U) -> Promise { let rp = Promise(.pending) + rp.dispatchState = dispatchState.nextState(givenDispatcher: on) pipe { switch $0 { case .success(let value): - on.dispatch { + rp.dispatch { do { let rv = try body(value) guard rv !== rp else { throw PMKError.returnedSelf } @@ -68,12 +69,13 @@ public extension Thenable { //… } */ - func map(on: Dispatcher = conf.D.map, _ transform: @escaping(T) throws -> U) -> Promise { + func map(on: Dispatcher = conf.dd, _ transform: @escaping(T) throws -> U) -> Promise { let rp = Promise(.pending) + rp.dispatchState = dispatchState.nextState(givenDispatcher: on) pipe { switch $0 { case .success(let value): - on.dispatch { + rp.dispatch { do { rp.box.seal(.success(try transform(value))) } catch { @@ -102,12 +104,13 @@ public extension Thenable { // either `PMKError.compactMap` or a `JSONError` } */ - func compactMap(on: Dispatcher = conf.D.map, _ transform: @escaping(T) throws -> U?) -> Promise { + func compactMap(on: Dispatcher = conf.dd, _ transform: @escaping(T) throws -> U?) -> Promise { let rp = Promise(.pending) + rp.dispatchState = dispatchState.nextState(givenDispatcher: on) pipe { switch $0 { case .success(let value): - on.dispatch { + rp.dispatch { do { if let rv = try transform(value) { rp.box.seal(.success(rv)) @@ -141,12 +144,13 @@ public extension Thenable { print(response.data) } */ - func done(on: Dispatcher = conf.D.return, _ body: @escaping(T) throws -> Void) -> Promise { + func done(on: Dispatcher = conf.dd, _ body: @escaping(T) throws -> Void) -> Promise { let rp = Promise(.pending) + rp.dispatchState = dispatchState.nextState(givenDispatcher: on, isTailFunction: true) pipe { switch $0 { case .success(let value): - on.dispatch { + rp.dispatch { do { try body(value) rp.box.seal(.success(())) @@ -181,7 +185,7 @@ public extension Thenable { print(foo, " is Void") } */ - func get(on: Dispatcher = conf.D.return, _ body: @escaping (T) throws -> Void) -> Promise { + func get(on: Dispatcher = conf.dd, _ body: @escaping (T) throws -> Void) -> Promise { return map(on: on) { try body($0) return $0 @@ -199,17 +203,18 @@ public extension Thenable { promise.tap{ print($0) }.then{ /*…*/ } */ - func tap(on: Dispatcher = conf.D.map, _ body: @escaping(Result) -> Void) -> Promise { - return Promise { seal in - pipe { result in - on.dispatch { - body(result) - seal.resolve(result) - } + func tap(on: Dispatcher = conf.dd, _ body: @escaping(Result) -> Void) -> Promise { + let (promise, seal) = Promise.pending() + promise.dispatchState = dispatchState.nextState(givenDispatcher: on) + pipe { result in + promise.dispatch { + body(result) + seal.resolve(result) } } + return promise } - + /// - Returns: a new promise chained off this promise but with its value discarded. func asVoid() -> Promise { return map(on: nil) { _ in } @@ -298,7 +303,7 @@ public extension Thenable where T: Sequence { // $0 => [2,4,6] } */ - func mapValues(on: Dispatcher = conf.D.map, _ transform: @escaping(T.Iterator.Element) throws -> U) -> Promise<[U]> { + func mapValues(on: Dispatcher = conf.dd, _ transform: @escaping(T.Iterator.Element) throws -> U) -> Promise<[U]> { return map(on: on) { try $0.map(transform) } } @@ -313,7 +318,7 @@ public extension Thenable where T: Sequence { // $0 => [1,1,2,2,3,3] } */ - func flatMapValues(on: Dispatcher = conf.D.map, _ transform: @escaping(T.Iterator.Element) throws -> U) -> Promise<[U.Iterator.Element]> { + func flatMapValues(on: Dispatcher = conf.dd, _ transform: @escaping(T.Iterator.Element) throws -> U) -> Promise<[U.Iterator.Element]> { return map(on: on){ (foo: T) in try foo.flatMap{ try transform($0) } } @@ -331,7 +336,7 @@ public extension Thenable where T: Sequence { } */ - func compactMapValues(on: Dispatcher = conf.D.map, _ transform: @escaping(T.Iterator.Element) throws -> U?) -> Promise<[U]> { + func compactMapValues(on: Dispatcher = conf.dd, _ transform: @escaping(T.Iterator.Element) throws -> U?) -> Promise<[U]> { return map(on: on) { foo -> [U] in return try foo.compactMap(transform) } @@ -348,7 +353,7 @@ public extension Thenable where T: Sequence { // $0 => [2,4,6] } */ - func thenMap(on: Dispatcher = conf.D.map, _ transform: @escaping(T.Iterator.Element) throws -> U) -> Promise<[U.T]> { + func thenMap(on: Dispatcher = conf.dd, _ transform: @escaping(T.Iterator.Element) throws -> U) -> Promise<[U.T]> { return then(on: on) { when(fulfilled: try $0.map(transform)) } @@ -365,7 +370,7 @@ public extension Thenable where T: Sequence { // $0 => [1,1,2,2,3,3] } */ - func thenFlatMap(on: Dispatcher = conf.D.map, _ transform: @escaping(T.Iterator.Element) throws -> U) -> Promise<[U.T.Iterator.Element]> where U.T: Sequence { + func thenFlatMap(on: Dispatcher = conf.dd, _ transform: @escaping(T.Iterator.Element) throws -> U) -> Promise<[U.T.Iterator.Element]> where U.T: Sequence { return then(on: on) { when(fulfilled: try $0.map(transform)) }.map(on: nil) { @@ -384,7 +389,7 @@ public extension Thenable where T: Sequence { // $0 => [2,3] } */ - func filterValues(on: Dispatcher = conf.D.map, _ isIncluded: @escaping (T.Iterator.Element) -> Bool) -> Promise<[T.Iterator.Element]> { + func filterValues(on: Dispatcher = conf.dd, _ isIncluded: @escaping (T.Iterator.Element) -> Bool) -> Promise<[T.Iterator.Element]> { return map(on: on) { $0.filter(isIncluded) } @@ -403,7 +408,7 @@ public extension Thenable where T: Collection { } } - func firstValue(on: Dispatcher = conf.D.map, where test: @escaping (T.Iterator.Element) -> Bool) -> Promise { + func firstValue(on: Dispatcher = conf.dd, where test: @escaping (T.Iterator.Element) -> Bool) -> Promise { return map(on: on) { for x in $0 where test(x) { return x @@ -427,7 +432,7 @@ public extension Thenable where T: Collection { public extension Thenable where T: Sequence, T.Iterator.Element: Comparable { /// - Returns: a promise fulfilled with the sorted values of this `Sequence`. - func sortedValues(on: Dispatcher = conf.D.map) -> Promise<[T.Iterator.Element]> { + func sortedValues(on: Dispatcher = conf.dd) -> Promise<[T.Iterator.Element]> { return map(on: on){ $0.sorted() } } } diff --git a/Sources/Wrappers/CatchWrappers.swift b/Sources/Wrappers/CatchWrappers.swift index c8802a47b..b04d48ac3 100644 --- a/Sources/Wrappers/CatchWrappers.swift +++ b/Sources/Wrappers/CatchWrappers.swift @@ -18,8 +18,8 @@ public extension _PMKCatchWrappers { - SeeAlso: [Cancellation](http://https://github.com/mxcl/PromiseKit/blob/master/Documents/CommonPatterns.md#cancellation/docs/) */ @discardableResult - func `catch`(on: DispatchQueue? = .pmkDefault, flags: DispatchWorkItemFlags? = nil, policy: CatchPolicy = conf.catchPolicy, _ body: @escaping(Error) -> Void) -> Finalizer { - let dispatcher = selectDispatcher(given: on, configured: conf.D.return, flags: flags) + func `catch`(on: DispatchQueue? = .unspecified, flags: DispatchWorkItemFlags? = nil, policy: CatchPolicy = conf.catchPolicy, _ body: @escaping(Error) -> Void) -> Finalizer { + let dispatcher = on.convertToDispatcher(flags: flags) return `catch`(on: dispatcher, policy: policy, body) } @@ -38,10 +38,10 @@ public extension _PMKCatchWrappers { - Note: Since this method handles only specific errors, supplying a `CatchPolicy` is unsupported. - SeeAlso: [Cancellation](http://promisekit.org/docs/) */ - func `catch`(only: E, on: DispatchQueue? = .pmkDefault, flags: DispatchWorkItemFlags? = nil, _ body: @escaping(E) -> Void) + func `catch`(only: E, on: DispatchQueue? = .unspecified, flags: DispatchWorkItemFlags? = nil, _ body: @escaping(E) -> Void) -> CascadingFinalizer where E: Equatable { - let dispatcher = selectDispatcher(given: on, configured: conf.D.return, flags: flags) + let dispatcher = on.convertToDispatcher(flags: flags) return `catch`(only: only, on: dispatcher, body) } @@ -61,10 +61,10 @@ public extension _PMKCatchWrappers { - Parameter body: The handler to execute if this promise is rejected with the provided error type. - SeeAlso: [Cancellation](http://promisekit.org/docs/) */ - func `catch`(only: E.Type, on: DispatchQueue? = .pmkDefault, flags: DispatchWorkItemFlags? = nil, + func `catch`(only: E.Type, on: DispatchQueue? = .unspecified, flags: DispatchWorkItemFlags? = nil, policy: CatchPolicy = conf.catchPolicy, _ body: @escaping(E) -> Void) -> CascadingFinalizer { - let dispatcher = selectDispatcher(given: on, configured: conf.D.return, flags: flags) + let dispatcher = on.convertToDispatcher(flags: flags) return `catch`(only: only, on: dispatcher, policy: policy, body) } diff --git a/Sources/Wrappers/EnsureWrappers.swift b/Sources/Wrappers/EnsureWrappers.swift index 442907545..037d25b67 100644 --- a/Sources/Wrappers/EnsureWrappers.swift +++ b/Sources/Wrappers/EnsureWrappers.swift @@ -20,8 +20,8 @@ public extension _PMKSharedWrappers { - Parameter body: The closure that executes when this promise resolves. - Returns: A new promise, resolved with this promise’s resolution. */ - func ensure(on: DispatchQueue? = .pmkDefault, flags: DispatchWorkItemFlags? = nil, _ body: @escaping () -> Void) -> BaseOfT { - let dispatcher = selectDispatcher(given: on, configured: conf.D.return, flags: flags) + func ensure(on: DispatchQueue? = .unspecified, flags: DispatchWorkItemFlags? = nil, _ body: @escaping () -> Void) -> BaseOfT { + let dispatcher = on.convertToDispatcher(flags: flags) return ensure(on: dispatcher, body) } @@ -44,8 +44,8 @@ public extension _PMKSharedWrappers { - Parameter body: The closure that executes when this promise resolves. - Returns: A new promise, resolved with this promise’s resolution. */ - func ensureThen(on: DispatchQueue? = .pmkDefault, flags: DispatchWorkItemFlags? = nil, _ body: @escaping () -> VoidReturn) -> BaseOfT { - let dispatcher = selectDispatcher(given: on, configured: conf.D.return, flags: flags) + func ensureThen(on: DispatchQueue? = .unspecified, flags: DispatchWorkItemFlags? = nil, _ body: @escaping () -> VoidReturn) -> BaseOfT { + let dispatcher = on.convertToDispatcher(flags: flags) return ensureThen(on: dispatcher, body) } } diff --git a/Sources/Wrappers/FinalizerWrappers.swift b/Sources/Wrappers/FinalizerWrappers.swift new file mode 100644 index 000000000..983351b1c --- /dev/null +++ b/Sources/Wrappers/FinalizerWrappers.swift @@ -0,0 +1,29 @@ +import Dispatch + +extension PMKCascadingFinalizer { + + /// Set a default Dispatcher for the chain. Within the chain, this Dispatcher will remain the + /// default until you change it, even if you dispatch individual closures to other Dispatchers. + /// + /// - Parameter on: The new default queue. Use `.default` to return to normal dispatching. + /// - Parameter flags: `DispatchWorkItemFlags` to be applied when dispatching. + + func dispatch(on: DispatchQueue?, flags: DispatchWorkItemFlags? = nil) -> PMKCascadingFinalizer { + let dispatcher = on.convertToDispatcher(flags: flags) + return dispatch(on: dispatcher) + } +} + +extension CancellableCascadingFinalizer { + + /// Set a default Dispatcher for the chain. Within the chain, this Dispatcher will remain the + /// default until you change it, even if you dispatch individual closures to other Dispatchers. + /// + /// - Parameter on: The new default queue. Use `.default` to return to normal dispatching. + /// - Parameter flags: `DispatchWorkItemFlags` to be applied when dispatching. + + func dispatch(on: DispatchQueue?, flags: DispatchWorkItemFlags? = nil) -> CancellableCascadingFinalizer { + let dispatcher = on.convertToDispatcher(flags: flags) + return dispatch(on: dispatcher) + } +} diff --git a/Sources/Wrappers/FinallyWrappers.swift b/Sources/Wrappers/FinallyWrappers.swift index 09272102c..76f2f8bd2 100644 --- a/Sources/Wrappers/FinallyWrappers.swift +++ b/Sources/Wrappers/FinallyWrappers.swift @@ -3,8 +3,8 @@ import Dispatch public extension _PMKFinallyWrappers { /// `finally` is the same as `ensure`, but it is not chainable @discardableResult - func finally(on: DispatchQueue? = .pmkDefault, flags: DispatchWorkItemFlags? = nil, _ body: @escaping () -> Void) -> FinallyReturn { - let dispatcher = selectDispatcher(given: on, configured: conf.D.return, flags: flags) + func finally(on: DispatchQueue? = .unspecified, flags: DispatchWorkItemFlags? = nil, _ body: @escaping () -> Void) -> FinallyReturn { + let dispatcher = on.convertToDispatcher(flags: flags) return finally(on: dispatcher, body) } } diff --git a/Sources/Wrappers/GuaranteeWrappers.swift b/Sources/Wrappers/GuaranteeWrappers.swift index a3bd2d404..b1539cca6 100644 --- a/Sources/Wrappers/GuaranteeWrappers.swift +++ b/Sources/Wrappers/GuaranteeWrappers.swift @@ -6,32 +6,48 @@ import Dispatch public extension Guarantee { @discardableResult - func then(on: DispatchQueue? = .pmkDefault, flags: DispatchWorkItemFlags? = nil, _ body: @escaping(T) -> Guarantee) -> Guarantee { - let dispatcher = selectDispatcher(given: on, configured: conf.D.map, flags: flags) + func then(on: DispatchQueue? = .unspecified, flags: DispatchWorkItemFlags? = nil, _ body: @escaping(T) -> Guarantee) -> Guarantee { + let dispatcher = on.convertToDispatcher(flags: flags) return then(on: dispatcher, body) } - func map(on: DispatchQueue? = .pmkDefault, flags: DispatchWorkItemFlags? = nil, _ body: @escaping(T) -> U) -> Guarantee { - let dispatcher = selectDispatcher(given: on, configured: conf.D.map, flags: flags) + func map(on: DispatchQueue? = .unspecified, flags: DispatchWorkItemFlags? = nil, _ body: @escaping(T) -> U) -> Guarantee { + let dispatcher = on.convertToDispatcher(flags: flags) return map(on: dispatcher, body) } @discardableResult - func done(on: DispatchQueue? = .pmkDefault, flags: DispatchWorkItemFlags? = nil, _ body: @escaping(T) -> Void) -> Guarantee { - let dispatcher = selectDispatcher(given: on, configured: conf.D.return, flags: flags) + func done(on: DispatchQueue? = .unspecified, flags: DispatchWorkItemFlags? = nil, _ body: @escaping(T) -> Void) -> Guarantee { + let dispatcher = on.convertToDispatcher(flags: flags) return done(on: dispatcher, body) } - func get(on: DispatchQueue? = .pmkDefault, flags: DispatchWorkItemFlags? = nil, _ body: @escaping (T) -> Void) -> Guarantee { - let dispatcher = selectDispatcher(given: on, configured: conf.D.return, flags: flags) + func get(on: DispatchQueue? = .unspecified, flags: DispatchWorkItemFlags? = nil, _ body: @escaping (T) -> Void) -> Guarantee { + let dispatcher = on.convertToDispatcher(flags: flags) return get(on: dispatcher, body) } + + /// Set a default Dispatcher for the chain. Within the chain, this Dispatcher will remain the + /// default until you change it, even if you dispatch individual closures to other Dispatchers. + /// + /// - Note: If you set a chain dispatcher within the body of a promise chain, you must + /// "confirm" the chain dispatcher when it gets to the tail to avoid a warning from + /// PromiseKit. To do this, just include `on: .chain` as an argument to the chain's + /// first `done`, `catch`, or `finally`. + /// + /// - Parameter on: The new default queue. Use `.default` to return to normal dispatching. + /// - Parameter flags: `DispatchWorkItemFlags` to be applied when dispatching. + + func dispatch(on: DispatchQueue?, flags: DispatchWorkItemFlags? = nil) -> Guarantee { + let dispatcher = on.convertToDispatcher(flags: flags) + return dispatch(on: dispatcher) + } } public extension Guarantee where T: Sequence { - func thenMap(on: DispatchQueue? = .pmkDefault, flags: DispatchWorkItemFlags? = nil, _ transform: @escaping(T.Iterator.Element) -> Guarantee) -> Guarantee<[U]> { - let dispatcher = selectDispatcher(given: on, configured: conf.D.map, flags: flags) + func thenMap(on: DispatchQueue? = .unspecified, flags: DispatchWorkItemFlags? = nil, _ transform: @escaping(T.Iterator.Element) -> Guarantee) -> Guarantee<[U]> { + let dispatcher = on.convertToDispatcher(flags: flags) return thenMap(on: dispatcher, transform) } diff --git a/Sources/Wrappers/RecoverWrappers.swift b/Sources/Wrappers/RecoverWrappers.swift index e253d1a34..a3440eb07 100644 --- a/Sources/Wrappers/RecoverWrappers.swift +++ b/Sources/Wrappers/RecoverWrappers.swift @@ -21,10 +21,10 @@ public extension _PMKSharedWrappers { - Parameter body: The handler to execute if this promise is rejected. - SeeAlso: [Cancellation](https://github.com/mxcl/PromiseKit/blob/master/Documents/CommonPatterns.md#cancellation) */ - func recover(on: DispatchQueue? = .pmkDefault, flags: DispatchWorkItemFlags? = nil, policy: CatchPolicy = conf.catchPolicy, + func recover(on: DispatchQueue? = .unspecified, flags: DispatchWorkItemFlags? = nil, policy: CatchPolicy = conf.catchPolicy, _ body: @escaping(Error) throws -> U) -> BaseOfT where U.T == T { - let dispatcher = selectDispatcher(given: on, configured: conf.D.map, flags: flags) + let dispatcher = on.convertToDispatcher(flags: flags) return recover(on: dispatcher, policy: policy, body) } @@ -47,10 +47,10 @@ public extension _PMKSharedWrappers { - Note: Since this method recovers only specific errors, supplying a `CatchPolicy` is unsupported. - SeeAlso: [Cancellation](http://promisekit.org/docs/) */ - func recover(only: E, on: DispatchQueue? = .pmkDefault, flags: DispatchWorkItemFlags? = nil, + func recover(only: E, on: DispatchQueue? = .unspecified, flags: DispatchWorkItemFlags? = nil, _ body: @escaping(E) throws -> U) -> BaseOfT where U.T == T, E: Equatable { - let dispatcher = selectDispatcher(given: on, configured: conf.D.map, flags: flags) + let dispatcher = on.convertToDispatcher(flags: flags) return recover(only: only, on: dispatcher, body) } @@ -75,10 +75,10 @@ public extension _PMKSharedWrappers { - Parameter body: The handler to execute if this promise is rejected with the provided error type. - SeeAlso: [Cancellation](http://promisekit.org/docs/) */ - func recover(only: E.Type, on: DispatchQueue? = .pmkDefault, flags: DispatchWorkItemFlags? = nil, + func recover(only: E.Type, on: DispatchQueue? = .unspecified, flags: DispatchWorkItemFlags? = nil, policy: CatchPolicy = conf.catchPolicy, _ body: @escaping(E) throws -> U) -> BaseOfT where U.T == T { - let dispatcher = selectDispatcher(given: on, configured: conf.D.map, flags: flags) + let dispatcher = on.convertToDispatcher(flags: flags) return recover(only: only, on: dispatcher, policy: policy, body) } } @@ -97,10 +97,10 @@ public extension _PMKSharedVoidWrappers { - Parameter body: The handler to execute if this promise is rejected. - SeeAlso: [Cancellation](https://github.com/mxcl/PromiseKit/blob/master/Documents/CommonPatterns.md#cancellation) */ - func recover(on: DispatchQueue? = .pmkDefault, flags: DispatchWorkItemFlags? = nil, policy: CatchPolicy = conf.catchPolicy, + func recover(on: DispatchQueue? = .unspecified, flags: DispatchWorkItemFlags? = nil, policy: CatchPolicy = conf.catchPolicy, _ body: @escaping(Error) throws -> Void) -> BaseOfT { - let dispatcher = selectDispatcher(given: on, configured: conf.D.map, flags: flags) + let dispatcher = on.convertToDispatcher(flags: flags) return recover(on: dispatcher, policy: policy, body) } @@ -117,10 +117,10 @@ public extension _PMKSharedVoidWrappers { - Note: Since this method recovers only specific errors, supplying a `CatchPolicy` is unsupported. - SeeAlso: [Cancellation](http://promisekit.org/docs/) */ - func recover(only: E, on: DispatchQueue? = .pmkDefault, flags: DispatchWorkItemFlags? = nil, + func recover(only: E, on: DispatchQueue? = .unspecified, flags: DispatchWorkItemFlags? = nil, _ body: @escaping(E) throws -> Void) -> BaseOfT where E: Equatable { - let dispatcher = selectDispatcher(given: on, configured: conf.D.map, flags: flags) + let dispatcher = on.convertToDispatcher(flags: flags) return recover(only: only, on: dispatcher, body) } @@ -137,10 +137,10 @@ public extension _PMKSharedVoidWrappers { - Parameter body: The handler to execute if this promise is rejected with the provided error type. - SeeAlso: [Cancellation](http://promisekit.org/docs/) */ - func recover(only: E.Type, on: DispatchQueue? = .pmkDefault, flags: DispatchWorkItemFlags? = nil, + func recover(only: E.Type, on: DispatchQueue? = .unspecified, flags: DispatchWorkItemFlags? = nil, policy: CatchPolicy = conf.catchPolicy, _ body: @escaping(E) throws -> Void) -> BaseOfT { - let dispatcher = selectDispatcher(given: on, configured: conf.D.map, flags: flags) + let dispatcher = on.convertToDispatcher(flags: flags) return recover(only: only, on: dispatcher, policy: policy, body) } } @@ -160,8 +160,8 @@ public extension CatchMixin { - SeeAlso: [Cancellation](https://github.com/mxcl/PromiseKit/blob/master/Documents/CommonPatterns.md#cancellation) */ @discardableResult - func recover(on: DispatchQueue? = .pmkDefault, flags: DispatchWorkItemFlags? = nil, _ body: @escaping(Error) -> Guarantee) -> Guarantee { - let dispatcher = selectDispatcher(given: on, configured: conf.D.map, flags: flags) + func recover(on: DispatchQueue? = .unspecified, flags: DispatchWorkItemFlags? = nil, _ body: @escaping(Error) -> Guarantee) -> Guarantee { + let dispatcher = on.convertToDispatcher(flags: flags) return recover(on: dispatcher, body) } } @@ -181,8 +181,8 @@ public extension CatchMixin where T == Void { - SeeAlso: [Cancellation](https://github.com/mxcl/PromiseKit/blob/master/Documents/CommonPatterns.md#cancellation) */ @discardableResult - func recover(on: DispatchQueue? = .pmkDefault, flags: DispatchWorkItemFlags? = nil, _ body: @escaping(Error) -> Void) -> Guarantee { - let dispatcher = selectDispatcher(given: on, configured: conf.D.map, flags: flags) + func recover(on: DispatchQueue? = .unspecified, flags: DispatchWorkItemFlags? = nil, _ body: @escaping(Error) -> Void) -> Guarantee { + let dispatcher = on.convertToDispatcher(flags: flags) return recover(on: dispatcher, body) } } @@ -212,10 +212,10 @@ public extension CancellableCatchMixin { - Parameter body: The handler to execute if this promise is rejected. - SeeAlso: [Cancellation](https://github.com/mxcl/PromiseKit/blob/master/Documentation/CommonPatterns.md#cancellation) */ - func recover(on: DispatchQueue? = .pmkDefault, flags: DispatchWorkItemFlags? = nil, + func recover(on: DispatchQueue? = .unspecified, flags: DispatchWorkItemFlags? = nil, policy: CatchPolicy = conf.catchPolicy, _ body: @escaping(Error) throws -> V) -> CancellablePromise where V.U.T == C.T { - let dispatcher = selectDispatcher(given: on, configured: conf.D.map, flags: flags) + let dispatcher = on.convertToDispatcher(flags: flags) return recover(on: dispatcher, policy: policy, body) } @@ -238,10 +238,10 @@ public extension CancellableCatchMixin { - Note: Since this method recovers only specific errors, supplying a `CatchPolicy` is unsupported. - SeeAlso: [Cancellation](https://github.com/mxcl/PromiseKit/blob/master/Documentation/CommonPatterns.md#cancellation) */ - func recover(only: E, on: DispatchQueue? = .pmkDefault, flags: DispatchWorkItemFlags? = nil, + func recover(only: E, on: DispatchQueue? = .unspecified, flags: DispatchWorkItemFlags? = nil, _ body: @escaping(E) throws -> V) -> CancellablePromise where V.U.T == C.T, E: Equatable { - let dispatcher = selectDispatcher(given: on, configured: conf.D.map, flags: flags) + let dispatcher = on.convertToDispatcher(flags: flags) return recover(only: only, on: dispatcher, body) } @@ -266,10 +266,10 @@ public extension CancellableCatchMixin { - Parameter body: The handler to execute if this promise is rejected with the provided error type. - SeeAlso: [Cancellation](https://github.com/mxcl/PromiseKit/blob/master/Documentation/CommonPatterns.md#cancellation) */ - func recover(only: E.Type, on: DispatchQueue? = .pmkDefault, flags: DispatchWorkItemFlags? = nil, + func recover(only: E.Type, on: DispatchQueue? = .unspecified, flags: DispatchWorkItemFlags? = nil, policy: CatchPolicy = conf.catchPolicy, _ body: @escaping(E) throws -> V) -> CancellablePromise where V.U.T == C.T { - let dispatcher = selectDispatcher(given: on, configured: conf.D.map, flags: flags) + let dispatcher = on.convertToDispatcher(flags: flags) return recover(only: only, on: dispatcher, policy: policy, body) } } diff --git a/Sources/Wrappers/SequenceWrappers.swift b/Sources/Wrappers/SequenceWrappers.swift index afe2e5e98..70a0193ba 100644 --- a/Sources/Wrappers/SequenceWrappers.swift +++ b/Sources/Wrappers/SequenceWrappers.swift @@ -12,8 +12,8 @@ public extension Thenable where T: Sequence { // $0 => [2,4,6] } */ - func mapValues(on: DispatchQueue? = .pmkDefault, flags: DispatchWorkItemFlags? = nil, _ transform: @escaping(T.Iterator.Element) throws -> U) -> Promise<[U]> { - let dispatcher = selectDispatcher(given: on, configured: conf.D.map, flags: flags) + func mapValues(on: DispatchQueue? = .unspecified, flags: DispatchWorkItemFlags? = nil, _ transform: @escaping(T.Iterator.Element) throws -> U) -> Promise<[U]> { + let dispatcher = on.convertToDispatcher(flags: flags) return mapValues(on: dispatcher, transform) } @@ -28,8 +28,8 @@ public extension Thenable where T: Sequence { // $0 => [1,1,2,2,3,3] } */ - func flatMapValues(on: DispatchQueue? = .pmkDefault, flags: DispatchWorkItemFlags? = nil, _ transform: @escaping(T.Iterator.Element) throws -> U) -> Promise<[U.Iterator.Element]> { - let dispatcher = selectDispatcher(given: on, configured: conf.D.map, flags: flags) + func flatMapValues(on: DispatchQueue? = .unspecified, flags: DispatchWorkItemFlags? = nil, _ transform: @escaping(T.Iterator.Element) throws -> U) -> Promise<[U.Iterator.Element]> { + let dispatcher = on.convertToDispatcher(flags: flags) return flatMapValues(on: dispatcher, transform) } @@ -44,8 +44,8 @@ public extension Thenable where T: Sequence { // $0 => [1,2,3] } */ - func compactMapValues(on: DispatchQueue? = .pmkDefault, flags: DispatchWorkItemFlags? = nil, _ transform: @escaping(T.Iterator.Element) throws -> U?) -> Promise<[U]> { - let dispatcher = selectDispatcher(given: on, configured: conf.D.map, flags: flags) + func compactMapValues(on: DispatchQueue? = .unspecified, flags: DispatchWorkItemFlags? = nil, _ transform: @escaping(T.Iterator.Element) throws -> U?) -> Promise<[U]> { + let dispatcher = on.convertToDispatcher(flags: flags) return compactMapValues(on: dispatcher, transform) } @@ -60,8 +60,8 @@ public extension Thenable where T: Sequence { // $0 => [2,4,6] } */ - func thenMap(on: DispatchQueue? = .pmkDefault, flags: DispatchWorkItemFlags? = nil, _ transform: @escaping(T.Iterator.Element) throws -> U) -> Promise<[U.T]> { - let dispatcher = selectDispatcher(given: on, configured: conf.D.map, flags: flags) + func thenMap(on: DispatchQueue? = .unspecified, flags: DispatchWorkItemFlags? = nil, _ transform: @escaping(T.Iterator.Element) throws -> U) -> Promise<[U.T]> { + let dispatcher = on.convertToDispatcher(flags: flags) return thenMap(on: dispatcher, transform) } @@ -76,8 +76,8 @@ public extension Thenable where T: Sequence { // $0 => [1,1,2,2,3,3] } */ - func thenFlatMap(on: DispatchQueue? = .pmkDefault, flags: DispatchWorkItemFlags? = nil, _ transform: @escaping(T.Iterator.Element) throws -> U) -> Promise<[U.T.Iterator.Element]> where U.T: Sequence { - let dispatcher = selectDispatcher(given: on, configured: conf.D.map, flags: flags) + func thenFlatMap(on: DispatchQueue? = .unspecified, flags: DispatchWorkItemFlags? = nil, _ transform: @escaping(T.Iterator.Element) throws -> U) -> Promise<[U.T.Iterator.Element]> where U.T: Sequence { + let dispatcher = on.convertToDispatcher(flags: flags) return thenFlatMap(on: dispatcher, transform) } @@ -92,23 +92,23 @@ public extension Thenable where T: Sequence { // $0 => [2,3] } */ - func filterValues(on: DispatchQueue? = .pmkDefault, flags: DispatchWorkItemFlags? = nil, _ isIncluded: @escaping (T.Iterator.Element) -> Bool) -> Promise<[T.Iterator.Element]> { - let dispatcher = selectDispatcher(given: on, configured: conf.D.map, flags: flags) + func filterValues(on: DispatchQueue? = .unspecified, flags: DispatchWorkItemFlags? = nil, _ isIncluded: @escaping (T.Iterator.Element) -> Bool) -> Promise<[T.Iterator.Element]> { + let dispatcher = on.convertToDispatcher(flags: flags) return filterValues(on: dispatcher, isIncluded) } } public extension Thenable where T: Collection { - func firstValue(on: DispatchQueue? = .pmkDefault, flags: DispatchWorkItemFlags? = nil, where test: @escaping (T.Iterator.Element) -> Bool) -> Promise { - let dispatcher = selectDispatcher(given: on, configured: conf.D.map, flags: flags) + func firstValue(on: DispatchQueue? = .unspecified, flags: DispatchWorkItemFlags? = nil, where test: @escaping (T.Iterator.Element) -> Bool) -> Promise { + let dispatcher = on.convertToDispatcher(flags: flags) return firstValue(on: dispatcher, where: test) } } public extension Thenable where T: Sequence, T.Iterator.Element: Comparable { /// - Returns: a promise fulfilled with the sorted values of this `Sequence`. - func sortedValues(on: DispatchQueue? = .pmkDefault, flags: DispatchWorkItemFlags? = nil) -> Promise<[T.Iterator.Element]> { - let dispatcher = selectDispatcher(given: on, configured: conf.D.map, flags: flags) + func sortedValues(on: DispatchQueue? = .unspecified, flags: DispatchWorkItemFlags? = nil) -> Promise<[T.Iterator.Element]> { + let dispatcher = on.convertToDispatcher(flags: flags) return sortedValues(on: dispatcher) } } @@ -125,8 +125,8 @@ public extension CancellableThenable where U.T: Sequence { // $0 => [2,4,6] } */ - func mapValues(on: DispatchQueue? = .pmkDefault, flags: DispatchWorkItemFlags? = nil, _ transform: @escaping(U.T.Iterator.Element) throws -> V) -> CancellablePromise<[V]> { - let dispatcher = selectDispatcher(given: on, configured: conf.D.map, flags: flags) + func mapValues(on: DispatchQueue? = .unspecified, flags: DispatchWorkItemFlags? = nil, _ transform: @escaping(U.T.Iterator.Element) throws -> V) -> CancellablePromise<[V]> { + let dispatcher = on.convertToDispatcher(flags: flags) return mapValues(on: dispatcher, transform) } @@ -141,8 +141,8 @@ public extension CancellableThenable where U.T: Sequence { // $0 => [1,1,2,2,3,3] } */ - func flatMapValues(on: DispatchQueue? = .pmkDefault, flags: DispatchWorkItemFlags? = nil, _ transform: @escaping(U.T.Iterator.Element) throws -> V) -> CancellablePromise<[V.Iterator.Element]> { - let dispatcher = selectDispatcher(given: on, configured: conf.D.map, flags: flags) + func flatMapValues(on: DispatchQueue? = .unspecified, flags: DispatchWorkItemFlags? = nil, _ transform: @escaping(U.T.Iterator.Element) throws -> V) -> CancellablePromise<[V.Iterator.Element]> { + let dispatcher = on.convertToDispatcher(flags: flags) return flatMapValues(on: dispatcher, transform) } @@ -157,8 +157,8 @@ public extension CancellableThenable where U.T: Sequence { // $0 => [1,2,3] } */ - func compactMapValues(on: DispatchQueue? = .pmkDefault, flags: DispatchWorkItemFlags? = nil, _ transform: @escaping(U.T.Iterator.Element) throws -> V?) -> CancellablePromise<[V]> { - let dispatcher = selectDispatcher(given: on, configured: conf.D.map, flags: flags) + func compactMapValues(on: DispatchQueue? = .unspecified, flags: DispatchWorkItemFlags? = nil, _ transform: @escaping(U.T.Iterator.Element) throws -> V?) -> CancellablePromise<[V]> { + let dispatcher = on.convertToDispatcher(flags: flags) return compactMapValues(on: dispatcher, transform) } @@ -173,8 +173,8 @@ public extension CancellableThenable where U.T: Sequence { // $0 => [2,4,6] } */ - func thenMap(on: DispatchQueue? = .pmkDefault, flags: DispatchWorkItemFlags? = nil, _ transform: @escaping(U.T.Iterator.Element) throws -> V) -> CancellablePromise<[V.U.T]> { - let dispatcher = selectDispatcher(given: on, configured: conf.D.map, flags: flags) + func thenMap(on: DispatchQueue? = .unspecified, flags: DispatchWorkItemFlags? = nil, _ transform: @escaping(U.T.Iterator.Element) throws -> V) -> CancellablePromise<[V.U.T]> { + let dispatcher = on.convertToDispatcher(flags: flags) return thenMap(on: dispatcher, transform) } @@ -189,8 +189,8 @@ public extension CancellableThenable where U.T: Sequence { // $0 => [2,4,6] } */ - func thenMap(on: DispatchQueue? = .pmkDefault, flags: DispatchWorkItemFlags? = nil, _ transform: @escaping(U.T.Iterator.Element) throws -> V) -> CancellablePromise<[V.T]> { - let dispatcher = selectDispatcher(given: on, configured: conf.D.map, flags: flags) + func thenMap(on: DispatchQueue? = .unspecified, flags: DispatchWorkItemFlags? = nil, _ transform: @escaping(U.T.Iterator.Element) throws -> V) -> CancellablePromise<[V.T]> { + let dispatcher = on.convertToDispatcher(flags: flags) return thenMap(on: dispatcher, transform) } @@ -205,8 +205,8 @@ public extension CancellableThenable where U.T: Sequence { // $0 => [1,1,2,2,3,3] } */ - func thenFlatMap(on: DispatchQueue? = .pmkDefault, flags: DispatchWorkItemFlags? = nil, _ transform: @escaping(U.T.Iterator.Element) throws -> V) -> CancellablePromise<[V.U.T.Iterator.Element]> where V.U.T: Sequence { - let dispatcher = selectDispatcher(given: on, configured: conf.D.map, flags: flags) + func thenFlatMap(on: DispatchQueue? = .unspecified, flags: DispatchWorkItemFlags? = nil, _ transform: @escaping(U.T.Iterator.Element) throws -> V) -> CancellablePromise<[V.U.T.Iterator.Element]> where V.U.T: Sequence { + let dispatcher = on.convertToDispatcher(flags: flags) return thenFlatMap(on: dispatcher, transform) } @@ -221,8 +221,8 @@ public extension CancellableThenable where U.T: Sequence { // $0 => [1,1,2,2,3,3] } */ - func thenFlatMap(on: DispatchQueue? = .pmkDefault, flags: DispatchWorkItemFlags? = nil, _ transform: @escaping(U.T.Iterator.Element) throws -> V) -> CancellablePromise<[V.T.Iterator.Element]> where V.T: Sequence { - let dispatcher = selectDispatcher(given: on, configured: conf.D.map, flags: flags) + func thenFlatMap(on: DispatchQueue? = .unspecified, flags: DispatchWorkItemFlags? = nil, _ transform: @escaping(U.T.Iterator.Element) throws -> V) -> CancellablePromise<[V.T.Iterator.Element]> where V.T: Sequence { + let dispatcher = on.convertToDispatcher(flags: flags) return thenFlatMap(on: dispatcher, transform) } @@ -237,23 +237,23 @@ public extension CancellableThenable where U.T: Sequence { // $0 => [2,3] } */ - func filterValues(on: DispatchQueue? = .pmkDefault, flags: DispatchWorkItemFlags? = nil, _ isIncluded: @escaping (U.T.Iterator.Element) -> Bool) -> CancellablePromise<[U.T.Iterator.Element]> { - let dispatcher = selectDispatcher(given: on, configured: conf.D.map, flags: flags) + func filterValues(on: DispatchQueue? = .unspecified, flags: DispatchWorkItemFlags? = nil, _ isIncluded: @escaping (U.T.Iterator.Element) -> Bool) -> CancellablePromise<[U.T.Iterator.Element]> { + let dispatcher = on.convertToDispatcher(flags: flags) return filterValues(on: dispatcher, isIncluded) } } public extension CancellableThenable where U.T: Collection { - func firstValue(on: DispatchQueue? = .pmkDefault, flags: DispatchWorkItemFlags? = nil, where test: @escaping (U.T.Iterator.Element) -> Bool) -> CancellablePromise { - let dispatcher = selectDispatcher(given: on, configured: conf.D.map, flags: flags) + func firstValue(on: DispatchQueue? = .unspecified, flags: DispatchWorkItemFlags? = nil, where test: @escaping (U.T.Iterator.Element) -> Bool) -> CancellablePromise { + let dispatcher = on.convertToDispatcher(flags: flags) return firstValue(on: dispatcher, where: test) } } public extension CancellableThenable where U.T: Sequence, U.T.Iterator.Element: Comparable { /// - Returns: a cancellable promise fulfilled with the sorted values of this `Sequence`. - func sortedValues(on: DispatchQueue? = .pmkDefault, flags: DispatchWorkItemFlags? = nil) -> CancellablePromise<[U.T.Iterator.Element]> { - let dispatcher = selectDispatcher(given: on, configured: conf.D.map, flags: flags) + func sortedValues(on: DispatchQueue? = .unspecified, flags: DispatchWorkItemFlags? = nil) -> CancellablePromise<[U.T.Iterator.Element]> { + let dispatcher = on.convertToDispatcher(flags: flags) return sortedValues(on: dispatcher) } } diff --git a/Sources/Wrappers/ThenableWrappers.swift b/Sources/Wrappers/ThenableWrappers.swift index c9ea63b72..57268b4e5 100644 --- a/Sources/Wrappers/ThenableWrappers.swift +++ b/Sources/Wrappers/ThenableWrappers.swift @@ -19,8 +19,8 @@ public extension _PMKSharedWrappers { - Parameter body: The closure that is executed when this Promise is fulfilled. - Returns: A new promise fulfilled as `Void`. */ - func done(on: DispatchQueue? = .pmkDefault, flags: DispatchWorkItemFlags? = nil, _ body: @escaping(T) throws -> Void) -> BaseOfVoid { - let dispatcher = selectDispatcher(given: on, configured: conf.D.return, flags: flags) + func done(on: DispatchQueue? = .unspecified, flags: DispatchWorkItemFlags? = nil, _ body: @escaping(T) throws -> Void) -> BaseOfVoid { + let dispatcher = on.convertToDispatcher(flags: flags) return done(on: dispatcher, body) } @@ -45,8 +45,8 @@ public extension _PMKSharedWrappers { - Parameter body: The closure that is executed when this Promise is fulfilled. - Returns: A new promise that is resolved with the value that the handler is fed. */ - func get(on: DispatchQueue? = .pmkDefault, flags: DispatchWorkItemFlags? = nil, _ body: @escaping (T) throws -> Void) -> BaseOfT { - let dispatcher = selectDispatcher(given: on, configured: conf.D.return, flags: flags) + func get(on: DispatchQueue? = .unspecified, flags: DispatchWorkItemFlags? = nil, _ body: @escaping (T) throws -> Void) -> BaseOfT { + let dispatcher = on.convertToDispatcher(flags: flags) return get(on: dispatcher, body) } @@ -62,10 +62,27 @@ public extension _PMKSharedWrappers { - Parameter body: The closure that is executed with Result of Promise. - Returns: A new promise that is resolved with the result that the handler is fed. */ - func tap(on: DispatchQueue? = .pmkDefault, flags: DispatchWorkItemFlags? = nil, _ body: @escaping(Result) -> Void) -> BaseOfT { - let dispatcher = selectDispatcher(given: on, configured: conf.D.map, flags: flags) + func tap(on: DispatchQueue? = .unspecified, flags: DispatchWorkItemFlags? = nil, _ body: @escaping(Result) -> Void) -> BaseOfT { + let dispatcher = on.convertToDispatcher(flags: flags) return tap(on: dispatcher, body) } + + /// Set a default Dispatcher for the chain. Within the chain, this Dispatcher will remain the + /// default until you change it, even if you dispatch individual closures to other Dispatchers. + /// + /// - Note: If you set a chain dispatcher within the body of a promise chain, you must + /// "confirm" the chain dispatcher when it gets to the tail to avoid a warning from + /// PromiseKit. To do this, just include `on: .chain` as an argument to the chain's + /// first `done`, `catch`, or `finally`. + /// + /// - Parameter on: The new default queue. Use `.default` to return to normal dispatching. + /// - Parameter flags: `DispatchWorkItemFlags` to be applied when dispatching. + + func dispatch(on: DispatchQueue?, flags: DispatchWorkItemFlags? = nil) -> BaseOfT { + let dispatcher = on.convertToDispatcher(flags: flags) + return dispatch(on: dispatcher) + } + } public extension Thenable { @@ -88,8 +105,8 @@ public extension Thenable { - Parameter body: The closure that executes when this promise fulfills. It must return a promise. - Returns: A new promise that resolves when the promise returned from the provided closure resolves. */ - func then(on: DispatchQueue? = .pmkDefault, flags: DispatchWorkItemFlags? = nil, _ body: @escaping(T) throws -> U) -> Promise { - let dispatcher = selectDispatcher(given: on, configured: conf.D.map, flags: flags) + func then(on: DispatchQueue? = .unspecified, flags: DispatchWorkItemFlags? = nil, _ body: @escaping(T) throws -> U) -> Promise { + let dispatcher = on.convertToDispatcher(flags: flags) return then(on: dispatcher, body) } @@ -111,8 +128,8 @@ public extension Thenable { //… } */ - func map(on: DispatchQueue? = .pmkDefault, flags: DispatchWorkItemFlags? = nil, _ transform: @escaping(T) throws -> U) -> Promise { - let dispatcher = selectDispatcher(given: on, configured: conf.D.map, flags: flags) + func map(on: DispatchQueue? = .unspecified, flags: DispatchWorkItemFlags? = nil, _ transform: @escaping(T) throws -> U) -> Promise { + let dispatcher = on.convertToDispatcher(flags: flags) return map(on: dispatcher, transform) } @@ -131,8 +148,8 @@ public extension Thenable { // either `PMKError.compactMap` or a `JSONError` } */ - func compactMap(on: DispatchQueue? = .pmkDefault, flags: DispatchWorkItemFlags? = nil, _ transform: @escaping(T) throws -> U?) -> Promise { - let dispatcher = selectDispatcher(given: on, configured: conf.D.map, flags: flags) + func compactMap(on: DispatchQueue? = .unspecified, flags: DispatchWorkItemFlags? = nil, _ transform: @escaping(T) throws -> U?) -> Promise { + let dispatcher = on.convertToDispatcher(flags: flags) return compactMap(on: dispatcher, transform) } } @@ -159,8 +176,8 @@ public extension CancellableThenable { - Parameter body: The closure that executes when this cancellable promise fulfills. It must return a cancellable promise. - Returns: A new cancellable promise that resolves when the promise returned from the provided closure resolves. */ - func then(on: DispatchQueue? = .pmkDefault, flags: DispatchWorkItemFlags? = nil, _ body: @escaping (U.T) throws -> V) -> CancellablePromise { - let dispatcher = selectDispatcher(given: on, configured: conf.D.map, flags: flags) + func then(on: DispatchQueue? = .unspecified, flags: DispatchWorkItemFlags? = nil, _ body: @escaping (U.T) throws -> V) -> CancellablePromise { + let dispatcher = on.convertToDispatcher(flags: flags) return then(on: dispatcher, body) } @@ -184,8 +201,8 @@ public extension CancellableThenable { - Parameter body: The closure that executes when this cancellable promise fulfills. It must return a promise (not a cancellable promise). - Returns: A new cancellable promise that resolves when the promise returned from the provided closure resolves. */ - func then(on: DispatchQueue? = .pmkDefault, flags: DispatchWorkItemFlags? = nil, _ body: @escaping (U.T) throws -> V) -> CancellablePromise { - let dispatcher = selectDispatcher(given: on, configured: conf.D.map, flags: flags) + func then(on: DispatchQueue? = .unspecified, flags: DispatchWorkItemFlags? = nil, _ body: @escaping (U.T) throws -> V) -> CancellablePromise { + let dispatcher = on.convertToDispatcher(flags: flags) return then(on: dispatcher, body) } @@ -209,8 +226,8 @@ public extension CancellableThenable { - Parameter transform: The closure that is executed when this CancellablePromise is fulfilled. It must return a non-promise and non-cancellable-promise. - Returns: A new cancellable promise that is resolved with the value returned from the provided closure. */ - func map(on: DispatchQueue? = .pmkDefault, flags: DispatchWorkItemFlags? = nil, _ transform: @escaping (U.T) throws -> V) -> CancellablePromise { - let dispatcher = selectDispatcher(given: on, configured: conf.D.map, flags: flags) + func map(on: DispatchQueue? = .unspecified, flags: DispatchWorkItemFlags? = nil, _ transform: @escaping (U.T) throws -> V) -> CancellablePromise { + let dispatcher = on.convertToDispatcher(flags: flags) return map(on: dispatcher, transform) } @@ -232,9 +249,10 @@ public extension CancellableThenable { //… context.cancel() */ - func compactMap(on: DispatchQueue? = .pmkDefault, flags: DispatchWorkItemFlags? = nil, _ transform: @escaping (U.T) throws -> V?) -> CancellablePromise { - let dispatcher = selectDispatcher(given: on, configured: conf.D.map, flags: flags) + func compactMap(on: DispatchQueue? = .unspecified, flags: DispatchWorkItemFlags? = nil, _ transform: @escaping (U.T) throws -> V?) -> CancellablePromise { + let dispatcher = on.convertToDispatcher(flags: flags) return compactMap(on: dispatcher, transform) } + } diff --git a/Sources/Wrappers/WrapperProtocols.swift b/Sources/Wrappers/WrapperProtocols.swift index c668583b8..afbdabcb1 100644 --- a/Sources/Wrappers/WrapperProtocols.swift +++ b/Sources/Wrappers/WrapperProtocols.swift @@ -32,6 +32,8 @@ public protocol _PMKSharedWrappers { func ensure(on: Dispatcher, _ body: @escaping () -> Void) -> BaseOfT func ensureThen(on: Dispatcher, _ body: @escaping () -> VoidReturn) -> BaseOfT + + func dispatch(on: Dispatcher) -> BaseOfT } extension Promise: _PMKSharedWrappers { diff --git a/Tests/A+/Swift/0.0.0.swift b/Tests/A+/Swift/0.0.0.swift index 578c6dd54..05ea7164f 100644 --- a/Tests/A+/Swift/0.0.0.swift +++ b/Tests/A+/Swift/0.0.0.swift @@ -1,4 +1,4 @@ -import PromiseKit +@testable import PromiseKit import Dispatch import XCTest @@ -12,7 +12,8 @@ private let timeout: TimeInterval = 10 extension XCTestCase { func describe(_ description: String, file: StaticString = #file, line: UInt = #line, body: () throws -> Void) { - PromiseKit.conf.Q.map = .main + conf.testMode = true // Allow free setting of default dispatchers + conf.setDefaultDispatchers(body: .main) do { try body() diff --git a/Tests/Cancel/ChainDispatcherTests.swift b/Tests/Cancel/ChainDispatcherTests.swift new file mode 100644 index 000000000..2d4f9986b --- /dev/null +++ b/Tests/Cancel/ChainDispatcherTests.swift @@ -0,0 +1,316 @@ +import XCTest +@testable import PromiseKit + +class ChainDispatcherTests: XCTestCase { + + var defaultBodyDispatcher = RecordingDispatcher() + var defaultTailDispatcher = RecordingDispatcher() + + override func setUp() { + conf.testMode = true // Allow free resetting of defaults without warnings + defaultBodyDispatcher.dispatchCount = 0 + defaultTailDispatcher.dispatchCount = 0 + conf.setDefaultDispatchers(body: defaultBodyDispatcher, tail: defaultTailDispatcher) + } + + override func tearDown() { + conf.setDefaultDispatchers(body: .default, tail: .default) + } + + func captureLog(_ body: () -> Void) -> String? { + + var logOutput: String? = nil + + func captureLogger(_ event: LogEvent) { + logOutput = "\(event)" + } + + let oldLogger = conf.logHandler + conf.logHandler = captureLogger + body() + conf.logHandler = oldLogger + return logOutput + } + + func testSimpleChain() { + let ex = expectation(description: "Simple chain") + Promise.value(42).cancellize().then { + Promise.value($0 + 10) + }.map { + $0 + 20 + }.get { _ in + // NOP - should be body dispatcher + }.done { + ex.fulfill() + XCTAssert($0 == 72) + }.cauterize() + waitForExpectations(timeout: 1) + XCTAssert(defaultBodyDispatcher.dispatchCount == 3) + XCTAssert(defaultTailDispatcher.dispatchCount == 1) + } + + func testPermanentTail() { + let ex = expectation(description: "Permanent tail") + Promise.value(42).cancellize().then { + Promise.value($0 + 10) + }.map { + $0 + }.get { _ in + // NOP - should be body dispatcher + }.done { + XCTAssert($0 == 52) + }.get { _ in + // NOP - should be tail dispatcher + }.map { + 123 // Tail + }.catch { error in + // NOP - not dispatched + }.finally { + ex.fulfill() + } + waitForExpectations(timeout: 1) + XCTAssert(defaultBodyDispatcher.dispatchCount == 3) + XCTAssert(defaultTailDispatcher.dispatchCount == 4) + } + + func testSimpleChainDispatcher() { + let ex = expectation(description: "Simple chain dispatcher") + let dispatcher = RecordingDispatcher() + let log = captureLog { + Promise.value(42).cancellize().then { + Promise.value($0 + 10) + }.map { + $0 + }.dispatch(on: dispatcher).get { _ in + // NOP - should be body dispatcher + }.done { + XCTAssert($0 == 52) + }.get { _ in + // NOP - should be tail dispatcher + }.map { + 123 // Tail + }.catch { error in + // NOP - not dispatched + }.finally { + ex.fulfill() + } + } + waitForExpectations(timeout: 1) + XCTAssert(defaultBodyDispatcher.dispatchCount == 2) + XCTAssert(defaultTailDispatcher.dispatchCount == 0) + XCTAssert(dispatcher.dispatchCount == 5) + XCTAssert(log == "failedToConfirmChainDispatcher") + } + + func testRConfirmedChainDispatcher() { + let ex = expectation(description: "Simple chain dispatcher") + let dispatcher = RecordingDispatcher() + let log = captureLog { + Promise.value(42).cancellize().then { + Promise.value($0 + 10) + }.map { + $0 + }.dispatch(on: dispatcher).get { _ in + // NOP - should be body dispatcher + }.done(on: .chain) { + XCTAssert($0 == 52) + }.get { _ in + // NOP - should be tail dispatcher + }.map { + 123 // Tail + }.catch { error in + // NOP - not dispatched + }.finally { + ex.fulfill() + } + } + waitForExpectations(timeout: 1) + XCTAssert(defaultBodyDispatcher.dispatchCount == 2) + XCTAssert(defaultTailDispatcher.dispatchCount == 0) + XCTAssert(dispatcher.dispatchCount == 5) + XCTAssert(log == nil) + } + + func testResetChainDispatcher() { + let ex = expectation(description: "Simple chain dispatcher") + let dispatcher = RecordingDispatcher() + let log = captureLog { + Promise.value(42).cancellize().dispatch(on: dispatcher).then { + Promise.value($0 + 10) + }.map { + $0 + }.dispatch(on: .default).get { _ in + // NOP - should be body dispatcher + }.done { + XCTAssert($0 == 52) + }.get { _ in + // NOP - should be tail dispatcher + }.map { + 123 // Tail + }.catch { error in + // NOP - not dispatched + }.finally { + ex.fulfill() + } + } + waitForExpectations(timeout: 1) + XCTAssert(defaultBodyDispatcher.dispatchCount == 1) + XCTAssert(defaultTailDispatcher.dispatchCount == 4) + XCTAssert(dispatcher.dispatchCount == 2) + XCTAssert(log == nil) + } + + func testThresholdChainDispatcher() { + let ex = expectation(description: "Simple chain dispatcher") + let dispatcher = RecordingDispatcher() + let log = captureLog { + Promise.value(42).cancellize().then { + Promise.value($0 + 10) + }.map { + $0 + }.dispatch(on: dispatcher).done { + XCTAssert($0 == 52) + }.get { _ in + // NOP - should be tail dispatcher + }.map { + 123 // Tail + }.catch { error in + // NOP - not dispatched + }.finally { + ex.fulfill() + } + } + waitForExpectations(timeout: 1) + XCTAssert(defaultBodyDispatcher.dispatchCount == 2) + XCTAssert(defaultTailDispatcher.dispatchCount == 0) + XCTAssert(dispatcher.dispatchCount == 4) + XCTAssert(log == nil) + } + + func testIndefinitelyDelayedConfirmationChainDispatcher() { + let ex = expectation(description: "Simple chain dispatcher") + let dispatcher = RecordingDispatcher() + let log = captureLog { + Promise.value(42).cancellize().dispatch(on: dispatcher).then { + Promise.value($0 + 10) + }.map { + $0 + }.done(on: defaultTailDispatcher) { + XCTAssert($0 == 52) + }.map(on: defaultTailDispatcher) { + 123 // Tail + }.catch(on: defaultTailDispatcher) { error in + // NOP - not dispatched + }.finally(on: defaultTailDispatcher) { + ex.fulfill() + } + } + waitForExpectations(timeout: 1) + XCTAssert(defaultBodyDispatcher.dispatchCount == 0) + XCTAssert(defaultTailDispatcher.dispatchCount == 3) + XCTAssert(dispatcher.dispatchCount == 2) + XCTAssert(log == nil) + } + + func testDelayedConfirmationChainDispatcher() { + let ex = expectation(description: "Simple chain dispatcher") + let dispatcher = RecordingDispatcher() + let log = captureLog { + Promise.value(42).cancellize().dispatch(on: dispatcher).then { + Promise.value($0 + 10) + }.map { + $0 + }.done(on: defaultTailDispatcher) { + XCTAssert($0 == 52) + }.map(on: defaultTailDispatcher) { + 123 // Tail + }.catch { error in + // NOP - not dispatched + }.finally { + ex.fulfill() + } + } + waitForExpectations(timeout: 1) + XCTAssert(defaultBodyDispatcher.dispatchCount == 0) + XCTAssert(defaultTailDispatcher.dispatchCount == 2) + XCTAssert(dispatcher.dispatchCount == 3) + XCTAssert(log == "failedToConfirmChainDispatcher") + } + + func testStickyChainDispatcher() { + let ex = expectation(description: "Sticky chain dispatcher") + let dispatcher = RecordingDispatcher() + let log = captureLog { + Promise.value(42).cancellize().dispatch(on: .sticky).then { + Promise.value($0 + 10) + }.map { + $0 + }.done(on: dispatcher) { + XCTAssert($0 == 52) + }.map { + 123 // Tail + }.catch { error in + // NOP - not dispatched + }.finally { + ex.fulfill() + } + } + waitForExpectations(timeout: 1) + XCTAssert(defaultBodyDispatcher.dispatchCount == 2) + XCTAssert(defaultTailDispatcher.dispatchCount == 0) + XCTAssert(dispatcher.dispatchCount == 3) + XCTAssert(log == nil) + } + + func testUnconfirmedStickyChainDispatcher() { + let ex = expectation(description: "Unconfirmed sticky chain dispatcher") + let dispatcher = RecordingDispatcher() + let log = captureLog { + Promise.value(42).cancellize().dispatch(on: .sticky).then(on: dispatcher) { + Promise.value($0 + 10) + }.map { + $0 + }.done { + XCTAssert($0 == 52) + }.map(on: defaultTailDispatcher) { + 123 // Tail + }.catch { error in + // NOP - not dispatched + }.finally { + ex.fulfill() + } + } + waitForExpectations(timeout: 1) + XCTAssert(defaultBodyDispatcher.dispatchCount == 0) + XCTAssert(defaultTailDispatcher.dispatchCount == 2) + XCTAssert(dispatcher.dispatchCount == 3) + XCTAssert(log == "failedToConfirmChainDispatcher") + } + + func testAdHocStickyDispatchers() { + let ex = expectation(description: "Ad hoc sticky dispatching") + let dispatcher = RecordingDispatcher() + let log = captureLog { + Promise.value(42).cancellize().then(on: dispatcher) { + Promise.value($0 + 10) + }.map(on: .sticky) { + $0 + }.done(on: .sticky) { + XCTAssert($0 == 52) + }.map(on: defaultBodyDispatcher) { + 123 // Tail + }.catch(on: .sticky) { error in + // NOP - not dispatched + }.finally { + ex.fulfill() + } + } + waitForExpectations(timeout: 1) + XCTAssert(defaultBodyDispatcher.dispatchCount == 1) + XCTAssert(defaultTailDispatcher.dispatchCount == 1) + XCTAssert(dispatcher.dispatchCount == 3) + XCTAssert(log == nil) + } +} + + diff --git a/Tests/Cancel/DefaultDispatchQueueTests.swift b/Tests/Cancel/DefaultDispatchQueueTests.swift index 2cc3c79ee..1fc98869a 100644 --- a/Tests/Cancel/DefaultDispatchQueueTests.swift +++ b/Tests/Cancel/DefaultDispatchQueueTests.swift @@ -1,5 +1,5 @@ import class Foundation.Thread -import PromiseKit +@testable import PromiseKit import Dispatch import XCTest @@ -10,13 +10,12 @@ class CancellableDefaultDispatchQueueTest: XCTestCase { let myQueue = DispatchQueue(label: "myQueue") override func setUp() { - // can actually only set the default queue once - // - See: PMKSetDefaultDispatchQueue - conf.Q = (myQueue, myQueue) + conf.testMode = true // Allow free setting of default dispatchers + conf.setDefaultDispatchers(body: myQueue, tail: myQueue) } override func tearDown() { - conf.Q = (.main, .main) + conf.setDefaultDispatchers(body: .main, tail: .main) } func testOverrodeDefaultThenQueue() { diff --git a/Tests/Cancel/DispatcherTests.swift b/Tests/Cancel/DispatcherTests.swift index 574c8b493..2d5a1932a 100644 --- a/Tests/Cancel/DispatcherTests.swift +++ b/Tests/Cancel/DispatcherTests.swift @@ -1,5 +1,5 @@ import Dispatch -import PromiseKit +@testable import PromiseKit import XCTest fileprivate let queueIDKey = DispatchSpecificKey() @@ -48,8 +48,9 @@ class DispatcherTests: XCTestCase { let ex = expectation(description: "DispatchQueue compatibility") - let oldConf = PromiseKit.conf.D - PromiseKit.conf.D = (map: dispatcher, return: dispatcher) + let oldConf = conf.D + conf.testMode = true + conf.D = (body: dispatcher, tail: dispatcher) let background = DispatchQueue.global(qos: .background) background.setSpecific(key: queueIDKey, value: 100) diff --git a/Tests/Cancel/XCTestManifests.swift b/Tests/Cancel/XCTestManifests.swift index 2973390df..2dfbf3354 100644 --- a/Tests/Cancel/XCTestManifests.swift +++ b/Tests/Cancel/XCTestManifests.swift @@ -141,6 +141,25 @@ extension CatchableTests { ] } +extension ChainDispatcherTests { + // DO NOT MODIFY: This is autogenerated, use: + // `swift test --generate-linuxmain` + // to regenerate. + static let __allTests__ChainDispatcherTests = [ + ("testAdHocStickyDispatchers", testAdHocStickyDispatchers), + ("testDelayedConfirmationChainDispatcher", testDelayedConfirmationChainDispatcher), + ("testIndefinitelyDelayedConfirmationChainDispatcher", testIndefinitelyDelayedConfirmationChainDispatcher), + ("testPermanentTail", testPermanentTail), + ("testRConfirmedChainDispatcher", testRConfirmedChainDispatcher), + ("testResetChainDispatcher", testResetChainDispatcher), + ("testSimpleChain", testSimpleChain), + ("testSimpleChainDispatcher", testSimpleChainDispatcher), + ("testStickyChainDispatcher", testStickyChainDispatcher), + ("testThresholdChainDispatcher", testThresholdChainDispatcher), + ("testUnconfirmedStickyChainDispatcher", testUnconfirmedStickyChainDispatcher), + ] +} + extension DispatchWrapperTests { // DO NOT MODIFY: This is autogenerated, use: // `swift test --generate-linuxmain` @@ -411,6 +430,7 @@ public func __allTests() -> [XCTestCaseEntry] { testCase(CancellablePromiseTests.__allTests__CancellablePromiseTests), testCase(CancellationTests.__allTests__CancellationTests), testCase(CatchableTests.__allTests__CatchableTests), + testCase(ChainDispatcherTests.__allTests__ChainDispatcherTests), testCase(DispatchWrapperTests.__allTests__DispatchWrapperTests), testCase(DispatcherTests.__allTests__DispatcherTests), testCase(GuaranteeTests.__allTests__GuaranteeTests), diff --git a/Tests/Core/ChainDispatcherTests.swift b/Tests/Core/ChainDispatcherTests.swift new file mode 100644 index 000000000..c7cefe7ab --- /dev/null +++ b/Tests/Core/ChainDispatcherTests.swift @@ -0,0 +1,315 @@ +import XCTest +@testable import PromiseKit + +class ChainDispatcherTests: XCTestCase { + + var defaultBodyDispatcher = RecordingDispatcher() + var defaultTailDispatcher = RecordingDispatcher() + + override func setUp() { + conf.testMode = true // Allow free resetting of defaults without warnings + defaultBodyDispatcher.dispatchCount = 0 + defaultTailDispatcher.dispatchCount = 0 + conf.setDefaultDispatchers(body: defaultBodyDispatcher, tail: defaultTailDispatcher) + } + + override func tearDown() { + conf.setDefaultDispatchers(body: .default, tail: .default) + } + + func captureLog(_ body: () -> Void) -> String? { + + var logOutput: String? = nil + + func captureLogger(_ event: LogEvent) { + logOutput = "\(event)" + } + + let oldLogger = conf.logHandler + conf.logHandler = captureLogger + body() + conf.logHandler = oldLogger + return logOutput + } + + func testSimpleChain() { + let ex = expectation(description: "Simple chain") + Promise.value(42).then { + Promise.value($0 + 10) + }.map { + $0 + 20 + }.get { _ in + // NOP - should be body dispatcher + }.done { + ex.fulfill() + XCTAssert($0 == 72) + }.cauterize() + waitForExpectations(timeout: 1) + XCTAssert(defaultBodyDispatcher.dispatchCount == 3) + XCTAssert(defaultTailDispatcher.dispatchCount == 1) + } + + func testPermanentTail() { + let ex = expectation(description: "Permanent tail") + Promise.value(42).then { + Promise.value($0 + 10) + }.map { + $0 + 20 + }.get { _ in + // NOP - should be body dispatcher + }.done { + XCTAssert($0 == 72) + }.get { _ in + // NOP - should be tail dispatcher + }.map { + 123 // Tail + }.catch { error in + // NOP - not dispatched + }.finally { + ex.fulfill() + } + waitForExpectations(timeout: 1) + XCTAssert(defaultBodyDispatcher.dispatchCount == 3) + XCTAssert(defaultTailDispatcher.dispatchCount == 4) + } + + func testSimpleChainDispatcher() { + let ex = expectation(description: "Simple chain dispatcher") + let dispatcher = RecordingDispatcher() + let log = captureLog { + Promise.value(42).then { + Promise.value($0 + 10) + }.map { + $0 + 20 + }.dispatch(on: dispatcher).get { _ in + // NOP - should be body dispatcher + }.done { + XCTAssert($0 == 72) + }.get { _ in + // NOP - should be tail dispatcher + }.map { + 123 // Tail + }.catch { error in + // NOP - not dispatched + }.finally { + ex.fulfill() + } + } + waitForExpectations(timeout: 1) + XCTAssert(defaultBodyDispatcher.dispatchCount == 2) + XCTAssert(defaultTailDispatcher.dispatchCount == 0) + XCTAssert(dispatcher.dispatchCount == 5) + XCTAssert(log == "failedToConfirmChainDispatcher") + } + + func testRConfirmedChainDispatcher() { + let ex = expectation(description: "Simple chain dispatcher") + let dispatcher = RecordingDispatcher() + let log = captureLog { + Promise.value(42).then { + Promise.value($0 + 10) + }.map { + $0 + 20 + }.dispatch(on: dispatcher).get { _ in + // NOP - should be body dispatcher + }.done(on: .chain) { + XCTAssert($0 == 72) + }.get { _ in + // NOP - should be tail dispatcher + }.map { + 123 // Tail + }.catch { error in + // NOP - not dispatched + }.finally { + ex.fulfill() + } + } + waitForExpectations(timeout: 1) + XCTAssert(defaultBodyDispatcher.dispatchCount == 2) + XCTAssert(defaultTailDispatcher.dispatchCount == 0) + XCTAssert(dispatcher.dispatchCount == 5) + XCTAssert(log == nil) + } + + func testResetChainDispatcher() { + let ex = expectation(description: "Simple chain dispatcher") + let dispatcher = RecordingDispatcher() + let log = captureLog { + Promise.value(42).dispatch(on: dispatcher).then { + Promise.value($0 + 10) + }.map { + $0 + 20 + }.dispatch(on: .default).get { _ in + // NOP - should be body dispatcher + }.done { + XCTAssert($0 == 72) + }.get { _ in + // NOP - should be tail dispatcher + }.map { + 123 // Tail + }.catch { error in + // NOP - not dispatched + }.finally { + ex.fulfill() + } + } + waitForExpectations(timeout: 1) + XCTAssert(defaultBodyDispatcher.dispatchCount == 1) + XCTAssert(defaultTailDispatcher.dispatchCount == 4) + XCTAssert(dispatcher.dispatchCount == 2) + XCTAssert(log == nil) + } + + func testThresholdChainDispatcher() { + let ex = expectation(description: "Simple chain dispatcher") + let dispatcher = RecordingDispatcher() + let log = captureLog { + Promise.value(42).then { + Promise.value($0 + 10) + }.map { + $0 + 20 + }.dispatch(on: dispatcher).done { + XCTAssert($0 == 72) + }.get { _ in + // NOP - should be tail dispatcher + }.map { + 123 // Tail + }.catch { error in + // NOP - not dispatched + }.finally { + ex.fulfill() + } + } + waitForExpectations(timeout: 1) + XCTAssert(defaultBodyDispatcher.dispatchCount == 2) + XCTAssert(defaultTailDispatcher.dispatchCount == 0) + XCTAssert(dispatcher.dispatchCount == 4) + XCTAssert(log == nil) + } + + func testIndefinitelyDelayedConfirmationChainDispatcher() { + let ex = expectation(description: "Simple chain dispatcher") + let dispatcher = RecordingDispatcher() + let log = captureLog { + Promise.value(42).dispatch(on: dispatcher).then { + Promise.value($0 + 10) + }.map { + $0 + 20 + }.done(on: defaultTailDispatcher) { + XCTAssert($0 == 72) + }.map(on: defaultTailDispatcher) { + 123 // Tail + }.catch(on: defaultTailDispatcher) { error in + // NOP - not dispatched + }.finally(on: defaultTailDispatcher) { + ex.fulfill() + } + } + waitForExpectations(timeout: 1) + XCTAssert(defaultBodyDispatcher.dispatchCount == 0) + XCTAssert(defaultTailDispatcher.dispatchCount == 3) + XCTAssert(dispatcher.dispatchCount == 2) + XCTAssert(log == nil) + } + + func testDelayedConfirmationChainDispatcher() { + let ex = expectation(description: "Simple chain dispatcher") + let dispatcher = RecordingDispatcher() + let log = captureLog { + Promise.value(42).dispatch(on: dispatcher).then { + Promise.value($0 + 10) + }.map { + $0 + 20 + }.done(on: defaultTailDispatcher) { + XCTAssert($0 == 72) + }.map(on: defaultTailDispatcher) { + 123 // Tail + }.catch { error in + // NOP - not dispatched + }.finally { + ex.fulfill() + } + } + waitForExpectations(timeout: 1) + XCTAssert(defaultBodyDispatcher.dispatchCount == 0) + XCTAssert(defaultTailDispatcher.dispatchCount == 2) + XCTAssert(dispatcher.dispatchCount == 3) + XCTAssert(log == "failedToConfirmChainDispatcher") + } + + func testStickyChainDispatcher() { + let ex = expectation(description: "Sticky chain dispatcher") + let dispatcher = RecordingDispatcher() + let log = captureLog { + Promise.value(42).dispatch(on: .sticky).then { + Promise.value($0 + 10) + }.map { + $0 + 20 + }.done(on: dispatcher) { + XCTAssert($0 == 72) + }.map { + 123 // Tail + }.catch { error in + // NOP - not dispatched + }.finally { + ex.fulfill() + } + } + waitForExpectations(timeout: 1) + XCTAssert(defaultBodyDispatcher.dispatchCount == 2) + XCTAssert(defaultTailDispatcher.dispatchCount == 0) + XCTAssert(dispatcher.dispatchCount == 3) + XCTAssert(log == nil) + } + + func testUnconfirmedStickyChainDispatcher() { + let ex = expectation(description: "Unconfirmed sticky chain dispatcher") + let dispatcher = RecordingDispatcher() + let log = captureLog { + Promise.value(42).dispatch(on: .sticky).then(on: dispatcher) { + Promise.value($0 + 10) + }.map { + $0 + 20 + }.done { + XCTAssert($0 == 72) + }.map(on: defaultTailDispatcher) { + 123 // Tail + }.catch { error in + // NOP - not dispatched + }.finally { + ex.fulfill() + } + } + waitForExpectations(timeout: 1) + XCTAssert(defaultBodyDispatcher.dispatchCount == 0) + XCTAssert(defaultTailDispatcher.dispatchCount == 2) + XCTAssert(dispatcher.dispatchCount == 3) + XCTAssert(log == "failedToConfirmChainDispatcher") + } + + func testAdHocStickyDispatchers() { + let ex = expectation(description: "Ad hoc sticky dispatching") + let dispatcher = RecordingDispatcher() + let log = captureLog { + Promise.value(42).then(on: dispatcher) { + Promise.value($0 + 10) + }.map(on: .sticky) { + $0 + 20 + }.done(on: .sticky) { + XCTAssert($0 == 72) + }.map(on: defaultBodyDispatcher) { + 123 // Tail + }.catch(on: .sticky) { error in + // NOP - not dispatched + }.finally { + ex.fulfill() + } + } + waitForExpectations(timeout: 1) + XCTAssert(defaultBodyDispatcher.dispatchCount == 1) + XCTAssert(defaultTailDispatcher.dispatchCount == 1) + XCTAssert(dispatcher.dispatchCount == 3) + XCTAssert(log == nil) + } +} + diff --git a/Tests/Core/DefaultDispatchQueueTests.swift b/Tests/Core/DefaultDispatchQueueTests.swift index f9908e26a..775e08c62 100644 --- a/Tests/Core/DefaultDispatchQueueTests.swift +++ b/Tests/Core/DefaultDispatchQueueTests.swift @@ -7,7 +7,7 @@ // import class Foundation.Thread -import PromiseKit +@testable import PromiseKit import Dispatch import XCTest @@ -19,13 +19,12 @@ class PMKDefaultDispatchQueueTest: XCTestCase { let myQueue = DispatchQueue(label: "myQueue") override func setUp() { - // can actually only set the default queue once - // - See: PMKSetDefaultDispatchQueue - conf.Q = (myQueue, myQueue) + conf.testMode = true // Allow free setting of default dispatchers + conf.setDefaultDispatchers(body: myQueue, tail: myQueue) } override func tearDown() { - conf.Q = (.main, .main) + conf.setDefaultDispatchers(body: .main, tail: .main) } func testOverrodeDefaultThenQueue() { diff --git a/Tests/Core/DispatcherTests.swift b/Tests/Core/DispatcherTests.swift index dc1eb83f9..c9cc0ee10 100644 --- a/Tests/Core/DispatcherTests.swift +++ b/Tests/Core/DispatcherTests.swift @@ -1,5 +1,5 @@ import Dispatch -import PromiseKit +@testable import PromiseKit import XCTest fileprivate let queueIDKey = DispatchSpecificKey() @@ -33,13 +33,15 @@ class DispatcherTests: XCTestCase { dispatcherB = RecordingDispatcher() } - func testConfD() { - let ex = expectation(description: "conf.D") - let oldConf = PromiseKit.conf.D - PromiseKit.conf.D.map = dispatcher - PromiseKit.conf.D.return = dispatcherB - XCTAssertNil(PromiseKit.conf.Q.map, "conf.Q.map not nil") // Not representable as DispatchQueues - XCTAssertNil(PromiseKit.conf.Q.return, "conf.Q.return not nil") + @available(*, deprecated) + func testConfQRepresentation() { + let ex = expectation(description: "Default dispatchers") + let oldConf = conf.D + conf.testMode = true + conf.D.body = dispatcher + conf.D.tail = dispatcherB + XCTAssertNil(conf.Q.map, "conf.Q.map not nil") // Not representable as DispatchQueues + XCTAssertNil(conf.Q.return, "conf.Q.return not nil") Promise { seal in seal.fulfill(42) }.map { @@ -52,16 +54,21 @@ class DispatcherTests: XCTestCase { }.cauterize() waitForExpectations(timeout: 1) let testQueue = DispatchQueue(label: "test queue") - PromiseKit.conf.D.map = testQueue // Assign DispatchQueue to Dispatcher variable - PromiseKit.conf.Q.return = testQueue // Assign DispatchQueue to DispatchQueue variable - XCTAssert(PromiseKit.conf.Q.map === testQueue, "did not get main DispatchQueue back from map") - XCTAssert((PromiseKit.conf.D.return as? DispatchQueue)! === testQueue, "did not get main DispatchQueue back from return") - PromiseKit.conf.D = oldConf + conf.setDefaultDispatchers(body: testQueue) // Assign DispatchQueue to Dispatcher variable + conf.Q.return = testQueue // Assign DispatchQueue to DispatchQueue variable + XCTAssert(conf.Q.map === testQueue, "did not get main DispatchQueue back from map") + XCTAssert((conf.D.tail as? DispatchQueue)! === testQueue, "did not get main DispatchQueue back from return") + conf.D = oldConf } - func testPMKDefaultIdentity() { - // If this identity does not hold, the DispatchQueue wrapper API will not behave correctly - XCTAssert(DispatchQueue.pmkDefault === DispatchQueue.pmkDefault, "DispatchQueues are not object-identity-preserving on this platform") + func testDispatchQueueIdentities() { + // If these identities does not hold, the DispatchQueue wrapper API will not behave correctly + XCTAssert(DispatchQueue.unspecified === DispatchQueue.unspecified, "DispatchQueues are not object-identity-preserving on this platform") + XCTAssert(DispatchQueue.chain === DispatchQueue.chain, "DispatchQueues are not object-identity-preserving on this platform") + XCTAssert(DispatchQueue.default === DispatchQueue.default, "DispatchQueues are not object-identity-preserving on this platform") + XCTAssert(DispatchQueue.unspecified !== DispatchQueue.chain, "DispatchQueues are not object-identity-preserving on this platform") + XCTAssert(DispatchQueue.chain !== DispatchQueue.default, "DispatchQueues are not object-identity-preserving on this platform") + XCTAssert(DispatchQueue.default !== DispatchQueue.unspecified, "DispatchQueues are not object-identity-preserving on this platform") } func testDispatcherWithThrow() { @@ -81,8 +88,9 @@ class DispatcherTests: XCTestCase { let ex = expectation(description: "DispatchQueue compatibility") - let oldConf = PromiseKit.conf.D - PromiseKit.conf.D = (map: dispatcher, return: dispatcher) + let oldConf = conf.D + conf.testMode = true + conf.D = (body: dispatcher, tail: dispatcher) let background = DispatchQueue.global(qos: .background) background.setSpecific(key: queueIDKey, value: 100) diff --git a/Tests/Core/LoggingTests.swift b/Tests/Core/LoggingTests.swift index 955f43cb0..d0df761e6 100644 --- a/Tests/Core/LoggingTests.swift +++ b/Tests/Core/LoggingTests.swift @@ -143,14 +143,15 @@ class LoggingTests: XCTestCase { conf.logHandler = captureLogger Guarantee.value(42).done(on: nil, flags: .barrier) { _ in } - XCTAssertEqual ("nilDispatchQueueWithFlags", logOutput!) + XCTAssertEqual ("extraneousFlagsSpecified", logOutput!) } // Verify extraneousFlagsSpecified is logged func testExtraneousFlagsSpecified() { conf.logHandler = captureLogger - conf.D.return = CurrentThreadDispatcher() + conf.testMode = true + conf.D.tail = CurrentThreadDispatcher() Guarantee.value(42).done(flags: .barrier) { _ in } XCTAssertEqual ("extraneousFlagsSpecified", logOutput!) } diff --git a/Tests/Core/XCTestManifests.swift b/Tests/Core/XCTestManifests.swift index 055e694d6..02ca1492e 100644 --- a/Tests/Core/XCTestManifests.swift +++ b/Tests/Core/XCTestManifests.swift @@ -83,6 +83,25 @@ extension CatchableTests { ] } +extension ChainDispatcherTests { + // DO NOT MODIFY: This is autogenerated, use: + // `swift test --generate-linuxmain` + // to regenerate. + static let __allTests__ChainDispatcherTests = [ + ("testAdHocStickyDispatchers", testAdHocStickyDispatchers), + ("testDelayedConfirmationChainDispatcher", testDelayedConfirmationChainDispatcher), + ("testIndefinitelyDelayedConfirmationChainDispatcher", testIndefinitelyDelayedConfirmationChainDispatcher), + ("testPermanentTail", testPermanentTail), + ("testRConfirmedChainDispatcher", testRConfirmedChainDispatcher), + ("testResetChainDispatcher", testResetChainDispatcher), + ("testSimpleChain", testSimpleChain), + ("testSimpleChainDispatcher", testSimpleChainDispatcher), + ("testStickyChainDispatcher", testStickyChainDispatcher), + ("testThresholdChainDispatcher", testThresholdChainDispatcher), + ("testUnconfirmedStickyChainDispatcher", testUnconfirmedStickyChainDispatcher), + ] +} + extension DispatchWrapperTests { // DO NOT MODIFY: This is autogenerated, use: // `swift test --generate-linuxmain` @@ -102,13 +121,13 @@ extension DispatcherTests { // `swift test --generate-linuxmain` // to regenerate. static let __allTests__DispatcherTests = [ - ("testConfD", testConfD), + ("testConfQRepresentation", testConfQRepresentation), ("testDispatcherExtensionCanThrowInBody", testDispatcherExtensionCanThrowInBody), ("testDispatcherExtensionReturnsGuarantee", testDispatcherExtensionReturnsGuarantee), ("testDispatcherWithThrow", testDispatcherWithThrow), + ("testDispatchQueueIdentities", testDispatchQueueIdentities), ("testDispatchQueueSelection", testDispatchQueueSelection), ("testMapValues", testMapValues), - ("testPMKDefaultIdentity", testPMKDefaultIdentity), ("testRecover", testRecover), ] } @@ -344,6 +363,7 @@ public func __allTests() -> [XCTestCaseEntry] { testCase(AfterTests.__allTests__AfterTests), testCase(CancellationTests.__allTests__CancellationTests), testCase(CatchableTests.__allTests__CatchableTests), + testCase(ChainDispatcherTests.__allTests__ChainDispatcherTests), testCase(DispatchWrapperTests.__allTests__DispatchWrapperTests), testCase(DispatcherTests.__allTests__DispatcherTests), testCase(DispatcherTypeTests.__allTests__DispatcherTypeTests),