From 132f6088fa1b9fac605b00e0c2086db61e48120a Mon Sep 17 00:00:00 2001 From: Garth Snyder Date: Sun, 17 Jun 2018 23:39:52 -0700 Subject: [PATCH 01/26] First draft of Dispatcher conversion --- PromiseKit.xcodeproj/project.pbxproj | 4 + Sources/Box.swift | 12 +- Sources/Catchable.swift | 47 ++- Sources/Configuration.swift | 10 +- Sources/Deprecations.swift | 10 +- Sources/Dispatcher.swift | 475 +++++++++++++++++++++++++++ Sources/Guarantee.swift | 42 ++- Sources/Promise.swift | 27 ++ Sources/Thenable.swift | 60 ++-- 9 files changed, 608 insertions(+), 79 deletions(-) create mode 100644 Sources/Dispatcher.swift diff --git a/PromiseKit.xcodeproj/project.pbxproj b/PromiseKit.xcodeproj/project.pbxproj index fe1c30598..4c2c1e9a5 100644 --- a/PromiseKit.xcodeproj/project.pbxproj +++ b/PromiseKit.xcodeproj/project.pbxproj @@ -78,6 +78,7 @@ 63CF6D80203CD19200EC8927 /* ThenableTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 63CF6D7F203CD19200EC8927 /* ThenableTests.swift */; }; 63D9B2EF203385FD0075C00B /* race.m in Sources */ = {isa = PBXBuildFile; fileRef = 63D9B2EE203385FD0075C00B /* race.m */; }; 63D9B2F120338D5D0075C00B /* Deprecations.swift in Sources */ = {isa = PBXBuildFile; fileRef = 63D9B2F020338D5D0075C00B /* Deprecations.swift */; }; + BB2524DE20D729A60010F7B0 /* Dispatcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB2524DD20D729A60010F7B0 /* Dispatcher.swift */; }; C013F7382048E3B6006B57B1 /* MockNodeEnvironment.swift in Sources */ = {isa = PBXBuildFile; fileRef = C013F7372048E3B6006B57B1 /* MockNodeEnvironment.swift */; }; C013F73A2049076A006B57B1 /* JSPromise.swift in Sources */ = {isa = PBXBuildFile; fileRef = C013F7392049076A006B57B1 /* JSPromise.swift */; }; C013F73C20494291006B57B1 /* JSAdapter.swift in Sources */ = {isa = PBXBuildFile; fileRef = C013F73B20494291006B57B1 /* JSAdapter.swift */; }; @@ -219,6 +220,7 @@ 63CF6D7F203CD19200EC8927 /* ThenableTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThenableTests.swift; sourceTree = ""; }; 63D9B2EE203385FD0075C00B /* race.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = race.m; path = Sources/race.m; sourceTree = ""; }; 63D9B2F020338D5D0075C00B /* Deprecations.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = Deprecations.swift; path = Sources/Deprecations.swift; sourceTree = ""; }; + BB2524DD20D729A60010F7B0 /* Dispatcher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = Dispatcher.swift; path = Sources/Dispatcher.swift; sourceTree = ""; }; C013F7372048E3B6006B57B1 /* MockNodeEnvironment.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = MockNodeEnvironment.swift; path = "Tests/JS-A+/MockNodeEnvironment.swift"; sourceTree = ""; }; C013F7392049076A006B57B1 /* JSPromise.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = JSPromise.swift; path = "Tests/JS-A+/JSPromise.swift"; sourceTree = ""; }; C013F73B20494291006B57B1 /* JSAdapter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = JSAdapter.swift; path = "Tests/JS-A+/JSAdapter.swift"; sourceTree = ""; }; @@ -437,6 +439,7 @@ 6330B5E01F2E991200D60528 /* Configuration.swift */, 63B18AEB1F2D205C00B79E37 /* CustomStringConvertible.swift */, 63D9B2F020338D5D0075C00B /* Deprecations.swift */, + BB2524DD20D729A60010F7B0 /* Dispatcher.swift */, ); name = Sources.swift; sourceTree = ""; @@ -742,6 +745,7 @@ 63B18AEC1F2D205C00B79E37 /* CustomStringConvertible.swift in Sources */, 6330B5E11F2E991200D60528 /* Configuration.swift in Sources */, 63B912AA1F1D7B1300D49110 /* firstly.swift in Sources */, + BB2524DE20D729A60010F7B0 /* Dispatcher.swift in Sources */, 636A29211F1C1716001229C2 /* Thenable.swift in Sources */, 632FBBE31F33B273008F8FBB /* Catchable.swift in Sources */, 63B0AC851D595E6300FA21D9 /* dispatch_promise.m in Sources */, diff --git a/Sources/Box.swift b/Sources/Box.swift index 3bf7ecd7a..cf0f99af9 100644 --- a/Sources/Box.swift +++ b/Sources/Box.swift @@ -84,18 +84,14 @@ class EmptyBox: Box { } -extension Optional where Wrapped: DispatchQueue { +extension Optional: Dispatcher where Wrapped: Dispatcher { @inline(__always) - func async(flags: DispatchWorkItemFlags?, _ body: @escaping() -> Void) { + public func async(execute body: @escaping () -> Void) { switch self { case .none: body() - case .some(let q): - if let flags = flags { - q.async(flags: flags, execute: body) - } else { - q.async(execute: body) - } + case .some(let dispatcher): + dispatcher.async(execute: body) } } } diff --git a/Sources/Catchable.swift b/Sources/Catchable.swift index 41ec7277e..8a22fd2e2 100644 --- a/Sources/Catchable.swift +++ b/Sources/Catchable.swift @@ -14,14 +14,14 @@ public extension CatchMixin { of a chain. Often utility promises will not have a catch, instead delegating the error handling to the caller. - - Parameter on: The queue to which the provided closure dispatches. + - Parameter on: The dispatcher that executes the provided closure. - Parameter policy: The default policy does not execute your handler for cancellation errors. - Parameter execute: The handler to execute if this promise is rejected. - Returns: A promise finalizer. - SeeAlso: [Cancellation](http://promisekit.org/docs/) */ @discardableResult - func `catch`(on: DispatchQueue? = conf.Q.return, flags: DispatchWorkItemFlags? = nil, policy: CatchPolicy = conf.catchPolicy, _ body: @escaping(Error) -> Void) -> PMKFinalizer { + func `catch`(on: Dispatcher? = conf.D.return, policy: CatchPolicy = conf.catchPolicy, _ body: @escaping(Error) -> Void) -> PMKFinalizer { let finalizer = PMKFinalizer() pipe { switch $0 { @@ -29,7 +29,7 @@ public extension CatchMixin { guard policy == .allErrors || !error.isCancelled else { fallthrough } - on.async(flags: flags) { + on.async { body(error) finalizer.pending.resolve(()) } @@ -45,8 +45,8 @@ public class PMKFinalizer { let pending = Guarantee.pending() /// `finally` is the same as `ensure`, but it is not chainable - public func finally(on: DispatchQueue? = conf.Q.return, flags: DispatchWorkItemFlags? = nil, _ body: @escaping () -> Void) { - pending.guarantee.done(on: on, flags: flags) { + public func finally(on: Dispatcher? = conf.D.return, _ body: @escaping () -> Void) { + pending.guarantee.done(on: on) { body() } } @@ -68,11 +68,11 @@ public extension CatchMixin { return .value(CLLocation.chicago) } - - Parameter on: The queue to which the provided closure dispatches. + - Parameter on: The dispatcher that executes the provided closure. - Parameter body: The handler to execute if this promise is rejected. - SeeAlso: [Cancellation](http://promisekit.org/docs/) */ - func recover(on: DispatchQueue? = conf.Q.map, flags: DispatchWorkItemFlags? = nil, policy: CatchPolicy = conf.catchPolicy, _ body: @escaping(Error) throws -> U) -> Promise where U.T == T { + func recover(on: Dispatcher? = conf.D.map, policy: CatchPolicy = conf.catchPolicy, _ body: @escaping(Error) throws -> U) -> Promise where U.T == T { let rp = Promise(.pending) pipe { switch $0 { @@ -80,7 +80,7 @@ public extension CatchMixin { rp.box.seal(.fulfilled(value)) case .rejected(let error): if policy == .allErrors || !error.isCancelled { - on.async(flags: flags) { + on.async { do { let rv = try body(error) guard rv !== rp else { throw PMKError.returnedSelf } @@ -101,19 +101,19 @@ public extension CatchMixin { The provided closure executes when this promise rejects. This variant of `recover` requires the handler to return a Guarantee, thus it returns a Guarantee itself and your closure cannot `throw`. - Note it is logically impossible for this to take a `catchPolicy`, thus `allErrors` are handled. - - Parameter on: The queue to which the provided closure dispatches. + - Parameter on: The dispatcher that executes the provided closure. - Parameter body: The handler to execute if this promise is rejected. - SeeAlso: [Cancellation](http://promisekit.org/docs/) */ @discardableResult - func recover(on: DispatchQueue? = conf.Q.map, flags: DispatchWorkItemFlags? = nil, _ body: @escaping(Error) -> Guarantee) -> Guarantee { + func recover(on: Dispatcher? = conf.D.map, _ body: @escaping(Error) -> Guarantee) -> Guarantee { let rg = Guarantee(.pending) pipe { switch $0 { case .fulfilled(let value): rg.box.seal(value) case .rejected(let error): - on.async(flags: flags) { + on.async { body(error).pipe(to: rg.box.seal) } } @@ -134,14 +134,14 @@ public extension CatchMixin { //… } - - Parameter on: The queue to which the provided closure dispatches. + - Parameter on: The dispatcher that executes the provided closure. - Parameter body: The closure that executes when this promise resolves. - Returns: A new promise, resolved with this promise’s resolution. */ - func ensure(on: DispatchQueue? = conf.Q.return, flags: DispatchWorkItemFlags? = nil, _ body: @escaping () -> Void) -> Promise { + func ensure(on: Dispatcher? = conf.D.return, _ body: @escaping () -> Void) -> Promise { let rp = Promise(.pending) pipe { result in - on.async(flags: flags) { + on.async { body() rp.box.seal(result) } @@ -163,14 +163,14 @@ public extension CatchMixin { //… } - - Parameter on: The queue to which the provided closure dispatches. + - Parameter on: The dispatcher that executes the provided closure. - Parameter body: The closure that executes when this promise resolves. - Returns: A new promise, resolved with this promise’s resolution. */ - func ensureThen(on: DispatchQueue? = conf.Q.return, flags: DispatchWorkItemFlags? = nil, _ body: @escaping () -> Guarantee) -> Promise { + func ensureThen(on: Dispatcher? = conf.D.return, _ body: @escaping () -> Guarantee) -> Promise { let rp = Promise(.pending) pipe { result in - on.async(flags: flags) { + on.async { body().done { rp.box.seal(result) } @@ -180,7 +180,6 @@ public extension CatchMixin { } - /** 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. @@ -201,19 +200,19 @@ public extension CatchMixin where T == Void { This variant of `recover` is specialized for `Void` promises and de-errors your chain returning a `Guarantee`, thus you cannot `throw` and you must handle all errors including cancellation. - - Parameter on: The queue to which the provided closure dispatches. + - Parameter on: The dispatcher that executes the provided closure. - Parameter body: The handler to execute if this promise is rejected. - SeeAlso: [Cancellation](http://promisekit.org/docs/) */ @discardableResult - func recover(on: DispatchQueue? = conf.Q.map, flags: DispatchWorkItemFlags? = nil, _ body: @escaping(Error) -> Void) -> Guarantee { + func recover(on: Dispatcher? = conf.D.map, flags: DispatchWorkItemFlags? = nil, _ body: @escaping(Error) -> Void) -> Guarantee { let rg = Guarantee(.pending) pipe { switch $0 { case .fulfilled: rg.box.seal(()) case .rejected(let error): - on.async(flags: flags) { + on.async { body(error) rg.box.seal(()) } @@ -227,11 +226,11 @@ public extension CatchMixin where T == Void { This variant of `recover` ensures that no error is thrown from the handler and allows specifying a catch policy. - - Parameter on: The queue to which the provided closure dispatches. + - Parameter on: The dispatcher that executes the provided closure. - Parameter body: The handler to execute if this promise is rejected. - SeeAlso: [Cancellation](http://promisekit.org/docs/) */ - func recover(on: DispatchQueue? = conf.Q.map, flags: DispatchWorkItemFlags? = nil, policy: CatchPolicy = conf.catchPolicy, _ body: @escaping(Error) throws -> Void) -> Promise { + func recover(on: Dispatcher? = conf.D.map, policy: CatchPolicy = conf.catchPolicy, _ body: @escaping(Error) throws -> Void) -> Promise { let rg = Promise(.pending) pipe { switch $0 { @@ -239,7 +238,7 @@ public extension CatchMixin where T == Void { rg.box.seal(.fulfilled(())) case .rejected(let error): if policy == .allErrors || !error.isCancelled { - on.async(flags: flags) { + on.async { do { rg.box.seal(.fulfilled(try body(error))) } catch { diff --git a/Sources/Configuration.swift b/Sources/Configuration.swift index 4891c45e8..0e64d5787 100644 --- a/Sources/Configuration.swift +++ b/Sources/Configuration.swift @@ -2,8 +2,14 @@ import Dispatch /// PromiseKit’s configurable parameters public struct PMKConfiguration { - /// The default queues that promises handlers dispatch to - public var Q: (map: DispatchQueue?, return: DispatchQueue?) = (map: DispatchQueue.main, return: DispatchQueue.main) + /// Backward compatibility: default DispatchQueues that promise handlers dispatch to + public var Q: (map: DispatchQueue?, return: DispatchQueue?) { + get { return (map: D.map as? DispatchQueue, return: D.return as? DispatchQueue) } + set { D = (map: newValue.map, return: newValue.return) } + } + + /// The default Dispatchers that promise handlers dispatch to + public var D: (map: Dispatcher?, return: Dispatcher?) = (map: DispatchQueue.main, return: DispatchQueue.main) /// The default catch-policy for all `catch` and `resolve` public var catchPolicy = CatchPolicy.allErrorsExceptCancellation diff --git a/Sources/Deprecations.swift b/Sources/Deprecations.swift index ac4eb364b..27300a502 100644 --- a/Sources/Deprecations.swift +++ b/Sources/Deprecations.swift @@ -46,7 +46,7 @@ public extension Thenable { #if PMKFullDeprecations /// disabled due to ambiguity with the other `.flatMap` @available(*, deprecated: 6.1, message: "See: `compactMap`") - func flatMap(on: DispatchQueue? = conf.Q.map, _ transform: @escaping(T) throws -> U?) -> Promise { + func flatMap(on: DispatchQueue? = .pmkDefault, _ transform: @escaping(T) throws -> U?) -> Promise { return compactMap(on: on, transform) } #endif @@ -56,19 +56,19 @@ public extension Thenable where T: Sequence { #if PMKFullDeprecations /// disabled due to ambiguity with the other `.map` @available(*, deprecated, message: "See: `mapValues`") - func map(on: DispatchQueue? = conf.Q.map, _ transform: @escaping(T.Iterator.Element) throws -> U) -> Promise<[U]> { + func map(on: DispatchQueue? = .pmkDefault, _ transform: @escaping(T.Iterator.Element) throws -> U) -> Promise<[U]> { return mapValues(on: on, transform) } /// disabled due to ambiguity with the other `.flatMap` @available(*, deprecated, message: "See: `flatMapValues`") - func flatMap(on: DispatchQueue? = conf.Q.map, _ transform: @escaping(T.Iterator.Element) throws -> U) -> Promise<[U.Iterator.Element]> { + func flatMap(on: DispatchQueue? = .pmkDefault, _ transform: @escaping(T.Iterator.Element) throws -> U) -> Promise<[U.Iterator.Element]> { return flatMapValues(on: on, transform) } #endif @available(*, deprecated, message: "See: `filterValues`") - func filter(on: DispatchQueue? = conf.Q.map, test: @escaping (T.Iterator.Element) -> Bool) -> Promise<[T.Iterator.Element]> { + func filter(on: DispatchQueue? = .pmkDefault, test: @escaping (T.Iterator.Element) -> Bool) -> Promise<[T.Iterator.Element]> { return filterValues(on: on, test) } } @@ -87,7 +87,7 @@ public extension Thenable where T: Collection { public extension Thenable where T: Sequence, T.Iterator.Element: Comparable { @available(*, deprecated, message: "See: `sortedValues`") - func sorted(on: DispatchQueue? = conf.Q.map) -> Promise<[T.Iterator.Element]> { + func sorted(on: DispatchQueue? = .pmkDefault) -> Promise<[T.Iterator.Element]> { return sortedValues(on: on) } } diff --git a/Sources/Dispatcher.swift b/Sources/Dispatcher.swift new file mode 100644 index 000000000..0750f1e54 --- /dev/null +++ b/Sources/Dispatcher.swift @@ -0,0 +1,475 @@ +import Dispatch + +public protocol Dispatcher { + func async(execute work: @escaping () -> Void) +} + +public struct DispatchQueueDispatcher: Dispatcher { + + let queue: DispatchQueue + let flags: DispatchWorkItemFlags + + init(queue: DispatchQueue, flags: DispatchWorkItemFlags) { + self.queue = queue + self.flags = flags + } + + public func async(execute work: @escaping () -> Void) { + queue.async(flags: flags, execute: work) + } + +} + +extension DispatchQueue: Dispatcher { + + /// Explicit declaration required; actual function signature is not identical to protocol + public func async(execute work: @escaping () -> Void) { + async(execute: work) + } + +} + +/// 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. + +public extension DispatchQueue { + static var pmkDefault = DispatchQueue(label: "org.promisekit.sentinel") +} + +extension DispatchQueue { + + public func asDispatcher(withFlags flags: DispatchWorkItemFlags? = nil) -> Dispatcher { + if let flags = flags { + return DispatchQueueDispatcher(queue: self, flags: flags) + } + return self + } + +} + +/// 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. +/// +/// TODO: should conf.D = nil turn off dispatching even if explicit dispatch arguments are given? + +fileprivate func selectDispatcher(given: DispatchQueue?, configured: Dispatcher?, flags: DispatchWorkItemFlags?) -> Dispatcher? { + guard let given = given else { + if flags != nil { + print("PromiseKit: warning: nil DispatchQueue specified, but DispatchWorkItemFlags were also supplied (ignored)") + } + return nil + } + if given !== DispatchQueue.pmkDefault { + return given.asDispatcher(withFlags: flags) + } else if let flags = flags, let configured = configured as? DispatchQueue { + return configured.asDispatcher(withFlags: flags) + } else if flags != nil && configured != nil { + print("PromiseKit: warning: DispatchWorkItemFlags flags specified, but default dispatcher is not a DispatchQueue (ignored)") + } + return configured +} + +/// Backward compatibility for DispatchQueues in public API + +public extension Guarantee { + + @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) + return done(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) + return map(on: dispatcher, body) + } + + @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) + return then(on: dispatcher, body) + } + +} + +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) + return thenMap(on: dispatcher, transform) + } + +} + +public extension Thenable { + + /** + The provided closure executes when this promise resolves. + + This allows chaining promises. The promise returned by the provided closure is resolved before the promise returned by this closure resolves. + + - Parameter on: The queue to which the provided closure dispatches. + - 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. For example: + + firstly { + URLSession.shared.dataTask(.promise, with: url1) + }.then { response in + transform(data: response.data) + }.done { transformation in + //… + } + */ + 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) + return then(on: dispatcher, body) + } + + /** + The provided closure is executed when this promise is resolved. + + This is like `then` but it requires the closure to return a non-promise. + + - Parameter on: The queue to which the provided closure dispatches. + - Parameter transform: The closure that is executed when this Promise is fulfilled. It must return a non-promise. + - Returns: A new promise that is resolved with the value returned from the provided closure. For example: + + firstly { + URLSession.shared.dataTask(.promise, with: url1) + }.map { response in + response.data.length + }.done { length in + //… + } + */ + 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) + return map(on: dispatcher, transform) + } + + /** + The provided closure is executed when this promise is resolved. + + In your closure return an `Optional`, if you return `nil` the resulting promise is rejected with `PMKError.compactMap`, otherwise the promise is fulfilled with the unwrapped value. + + firstly { + URLSession.shared.dataTask(.promise, with: url) + }.compactMap { + try JSONSerialization.jsonObject(with: $0.data) as? [String: String] + }.done { dictionary in + //… + }.catch { + // 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) + return compactMap(on: dispatcher, transform) + } + + /** + The provided closure is executed when this promise is resolved. + + Equivalent to `map { x -> Void in`, but since we force the `Void` return Swift + is happier and gives you less hassle about your closure’s qualification. + + - Parameter on: The queue to which the provided closure dispatches. + - Parameter body: The closure that is executed when this Promise is fulfilled. + - Returns: A new promise fulfilled as `Void`. + + firstly { + URLSession.shared.dataTask(.promise, with: url) + }.done { response in + print(response.data) + } + */ + func done(on: DispatchQueue? = .pmkDefault, flags: DispatchWorkItemFlags? = nil, _ body: @escaping(T) throws -> Void) -> Promise { + let dispatcher = selectDispatcher(given: on, configured: conf.D.return, flags: flags) + return done(on: dispatcher, body) + } + + /** + The provided closure is executed when this promise is resolved. + + This is like `done` but it returns the same value that the handler is fed. + `get` immutably accesses the fulfilled value; the returned Promise maintains that value. + + - Parameter on: The queue to which the provided closure dispatches. + - 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. For example: + + firstly { + .value(1) + }.get { foo in + print(foo, " is 1") + }.done { foo in + print(foo, " is 1") + }.done { foo in + print(foo, " is Void") + } + */ + func get(on: DispatchQueue? = .pmkDefault, flags: DispatchWorkItemFlags? = nil, _ body: @escaping (T) throws -> Void) -> Promise { + let dispatcher = selectDispatcher(given: on, configured: conf.D.return, flags: flags) + return get(on: dispatcher, body) + } +} + +public extension Thenable where T: Sequence { + /** + `Promise<[T]>` => `T` -> `U` => `Promise<[U]>` + + firstly { + .value([1,2,3]) + }.mapValues { integer in + integer * 2 + }.done { + // $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) + return mapValues(on: dispatcher, transform) + } + + /** + `Promise<[T]>` => `T` -> `[U]` => `Promise<[U]>` + + firstly { + .value([1,2,3]) + }.flatMapValues { integer in + [integer, integer] + }.done { + // $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) + return flatMapValues(on: dispatcher, transform) + } + + /** + `Promise<[T]>` => `T` -> `U?` => `Promise<[U]>` + + firstly { + .value(["1","2","a","3"]) + }.compactMapValues { + Int($0) + }.done { + // $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) + return compactMapValues(on: dispatcher, transform) + } + + /** + `Promise<[T]>` => `T` -> `Promise` => `Promise<[U]>` + + firstly { + .value([1,2,3]) + }.thenMap { integer in + .value(integer * 2) + }.done { + // $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) + return thenMap(on: dispatcher, transform) + } + + /** + `Promise<[T]>` => `T` -> `Promise<[U]>` => `Promise<[U]>` + + firstly { + .value([1,2,3]) + }.thenFlatMap { integer in + .value([integer, integer]) + }.done { + // $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) + return thenFlatMap(on: dispatcher, transform) + } + + /** + `Promise<[T]>` => `T` -> Bool => `Promise<[U]>` + + firstly { + .value([1,2,3]) + }.filterValues { + $0 > 1 + }.done { + // $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) + 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) + 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) + return sortedValues(on: dispatcher) + } +} + +public extension CatchMixin { + /** + The provided closure executes when this promise rejects. + + Rejecting a promise cascades: rejecting all subsequent promises (unless + recover is invoked) thus you will typically place your catch at the end + of a chain. Often utility promises will not have a catch, instead + delegating the error handling to the caller. + + - Parameter on: The queue to which the provided closure dispatches. + - Parameter policy: The default policy does not execute your handler for cancellation errors. + - Parameter execute: The handler to execute if this promise is rejected. + - Returns: A promise finalizer. + - SeeAlso: [Cancellation](http://promisekit.org/docs/) + */ + @discardableResult + func `catch`(on: DispatchQueue? = .pmkDefault, flags: DispatchWorkItemFlags? = nil, policy: CatchPolicy = conf.catchPolicy, _ body: @escaping(Error) -> Void) -> PMKFinalizer { + let dispatcher = selectDispatcher(given: on, configured: conf.D.return, flags: flags) + return `catch`(on: dispatcher, policy: policy, body) + } + + /** + The provided closure executes when this promise rejects. + + Unlike `catch`, `recover` continues the chain. + Use `recover` in circumstances where recovering the chain from certain errors is a possibility. For example: + + firstly { + CLLocationManager.requestLocation() + }.recover { error in + guard error == CLError.unknownLocation else { throw error } + return .value(CLLocation.chicago) + } + + - Parameter on: The queue to which the provided closure dispatches. + - Parameter body: The handler to execute if this promise is rejected. + - SeeAlso: [Cancellation](http://promisekit.org/docs/) + */ + func recover(on: DispatchQueue? = conf.Q.map, flags: DispatchWorkItemFlags? = nil, policy: CatchPolicy = conf.catchPolicy, _ body: @escaping(Error) throws -> U) -> Promise where U.T == T { + let dispatcher = selectDispatcher(given: on, configured: conf.D.map, flags: flags) + return recover(on: dispatcher, policy: policy, body) + } + + /** + The provided closure executes when this promise rejects. + This variant of `recover` requires the handler to return a Guarantee, thus it returns a Guarantee itself and your closure cannot `throw`. + - Note it is logically impossible for this to take a `catchPolicy`, thus `allErrors` are handled. + - Parameter on: The queue to which the provided closure dispatches. + - Parameter body: The handler to execute if this promise is rejected. + - SeeAlso: [Cancellation](http://promisekit.org/docs/) + */ + @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) + return recover(on: dispatcher, body) + } + + /** + The provided closure executes when this promise resolves, whether it rejects or not. + + firstly { + UIApplication.shared.networkActivityIndicatorVisible = true + }.done { + //… + }.ensure { + UIApplication.shared.networkActivityIndicatorVisible = false + }.catch { + //… + } + + - Parameter on: The queue to which the provided closure dispatches. + - 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) -> Promise { + let dispatcher = selectDispatcher(given: on, configured: conf.D.return, flags: flags) + return ensure(on: dispatcher, body) + } + + /** + The provided closure executes when this promise resolves, whether it rejects or not. + The chain waits on the returned `Guarantee`. + + firstly { + setup() + }.done { + //… + }.ensureThen { + teardown() // -> Guarante + }.catch { + //… + } + + - Parameter on: The queue to which the provided closure dispatches. + - 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 () -> Guarantee) -> Promise { + let dispatcher = selectDispatcher(given: on, configured: conf.D.return, flags: flags) + return ensureThen(on: dispatcher, body) + } +} + +public extension PMKFinalizer { + /// `finally` is the same as `ensure`, but it is not chainable + public func finally(on: DispatchQueue? = .pmkDefault, flags: DispatchWorkItemFlags? = nil, _ body: @escaping () -> Void) { + let dispatcher = selectDispatcher(given: on, configured: conf.D.return, flags: flags) + return finally(on: dispatcher, body) + } +} + +public extension CatchMixin where T == Void { + + /** + The provided closure executes when this promise rejects. + + This variant of `recover` is specialized for `Void` promises and de-errors your chain returning a `Guarantee`, thus you cannot `throw` and you must handle all errors including cancellation. + + - Parameter on: The queue to which the provided closure dispatches. + - Parameter body: The handler to execute if this promise is rejected. + - SeeAlso: [Cancellation](http://promisekit.org/docs/) + */ + @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) + return recover(on: dispatcher, body) + } + + /** + The provided closure executes when this promise rejects. + + This variant of `recover` ensures that no error is thrown from the handler and allows specifying a catch policy. + + - Parameter on: The queue to which the provided closure dispatches. + - Parameter body: The handler to execute if this promise is rejected. + - SeeAlso: [Cancellation](http://promisekit.org/docs/) + */ + func recover(on: DispatchQueue? = .pmkDefault, flags: DispatchWorkItemFlags? = nil, policy: CatchPolicy = conf.catchPolicy, _ body: @escaping(Error) throws -> Void) -> Promise { + let dispatcher = selectDispatcher(given: on, configured: conf.D.map, flags: flags) + return recover(on: dispatcher, policy: policy, body) + } +} diff --git a/Sources/Guarantee.swift b/Sources/Guarantee.swift index 300759973..be806d044 100644 --- a/Sources/Guarantee.swift +++ b/Sources/Guarantee.swift @@ -66,10 +66,10 @@ public class Guarantee: Thenable { public extension Guarantee { @discardableResult - func done(on: DispatchQueue? = conf.Q.return, flags: DispatchWorkItemFlags? = nil, _ body: @escaping(T) -> Void) -> Guarantee { + func done(on: Dispatcher? = conf.D.return, _ body: @escaping(T) -> Void) -> Guarantee { let rg = Guarantee(.pending) pipe { (value: T) in - on.async(flags: flags) { + on.async { body(value) rg.box.seal(()) } @@ -84,10 +84,10 @@ public extension Guarantee { } } - func map(on: DispatchQueue? = conf.Q.map, flags: DispatchWorkItemFlags? = nil, _ body: @escaping(T) -> U) -> Guarantee { + func map(on: Dispatcher? = conf.D.map, _ body: @escaping(T) -> U) -> Guarantee { let rg = Guarantee(.pending) pipe { value in - on.async(flags: flags) { + on.async { rg.box.seal(body(value)) } } @@ -95,10 +95,10 @@ public extension Guarantee { } @discardableResult - func then(on: DispatchQueue? = conf.Q.map, flags: DispatchWorkItemFlags? = nil, _ body: @escaping(T) -> Guarantee) -> Guarantee { + func then(on: Dispatcher? = conf.D.map, flags: DispatchWorkItemFlags? = nil, _ body: @escaping(T) -> Guarantee) -> Guarantee { let rg = Guarantee(.pending) pipe { value in - on.async(flags: flags) { + on.async { body(value).pipe(to: rg.box.seal) } } @@ -106,7 +106,7 @@ public extension Guarantee { } public func asVoid() -> Guarantee { - return map(on: nil) { _ in } + return map { _ in } } /** @@ -135,7 +135,7 @@ public extension Guarantee { public extension Guarantee where T: Sequence { /** - `Guarantee<[T]>` => `T` -> `Guarantee` => `Guaranetee<[U]>` + `Guarantee<[T]>` => `T` -> `Guarantee` => `Guarantee<[U]>` firstly { .value([1,2,3]) @@ -145,8 +145,8 @@ public extension Guarantee where T: Sequence { // $0 => [2,4,6] } */ - func thenMap(on: DispatchQueue? = conf.Q.map, flags: DispatchWorkItemFlags? = nil, _ transform: @escaping(T.Iterator.Element) -> Guarantee) -> Guarantee<[U]> { - return then(on: on, flags: flags) { + func thenMap(on: Dispatcher? = conf.D.map, _ transform: @escaping(T.Iterator.Element) -> Guarantee) -> Guarantee<[U]> { + return then(on: on) { when(fulfilled: $0.map(transform)) }.recover { // if happens then is bug inside PromiseKit @@ -188,6 +188,28 @@ public extension DispatchQueue { } } +public extension Dispatcher { + /** + Asynchronously executes the provided closure on a Dispatcher. + + dispatcher.guarantee { + md5(input) + }.done { md5 in + //… + } + + - Parameter body: The closure that resolves this promise. + - Returns: A new `Guarantee` resolved by the result of the provided closure. + - Note: There is no Promise/Thenable version of this due to Swift compiler ambiguity issues. + */ + func guarantee(execute body: @escaping () -> T) -> Guarantee { + let rg = Guarantee(.pending) + async { + rg.box.seal(body()) + } + return rg + } +} #if os(Linux) import func CoreFoundation._CFIsMainThread diff --git a/Sources/Promise.swift b/Sources/Promise.swift index c78ae8532..0af0b11af 100644 --- a/Sources/Promise.swift +++ b/Sources/Promise.swift @@ -168,6 +168,33 @@ public extension DispatchQueue { } } +public extension Dispatcher { + /** + Asynchronously executes the provided closure on a Dispatcher. + + dispatcher.promise { + try md5(input) + }.done { md5 in + //… + } + + - Parameter body: The closure that resolves this promise. + - Returns: A new `Promise` resolved by the result of the provided closure. + - Note: There is no Promise/Thenable version of this due to Swift compiler ambiguity issues. + */ + func promise(execute body: @escaping () throws -> T) -> Promise { + let promise = Promise(.pending) + async { + do { + promise.box.seal(.fulfilled(try body())) + } catch { + promise.box.seal(.rejected(error)) + } + } + return promise + } +} + /// used by our extensions to provide unambiguous functions with the same name as the original function public enum PMKNamespacer { diff --git a/Sources/Thenable.swift b/Sources/Thenable.swift index 776237207..c37e4db5b 100644 --- a/Sources/Thenable.swift +++ b/Sources/Thenable.swift @@ -18,7 +18,7 @@ public extension Thenable { This allows chaining promises. The promise returned by the provided closure is resolved before the promise returned by this closure resolves. - - Parameter on: The queue to which the provided closure dispatches. + - Parameter on: The dispatcher that executes the provided closure. - 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. For example: @@ -30,12 +30,12 @@ public extension Thenable { //… } */ - func then(on: DispatchQueue? = conf.Q.map, flags: DispatchWorkItemFlags? = nil, _ body: @escaping(T) throws -> U) -> Promise { + func then(on: Dispatcher? = conf.D.map, _ body: @escaping(T) throws -> U) -> Promise { let rp = Promise(.pending) pipe { switch $0 { case .fulfilled(let value): - on.async(flags: flags) { + on.async { do { let rv = try body(value) guard rv !== rp else { throw PMKError.returnedSelf } @@ -56,7 +56,7 @@ public extension Thenable { This is like `then` but it requires the closure to return a non-promise. - - Parameter on: The queue to which the provided closure dispatches. + - Parameter on: The dispatcher that executes the provided closure. - Parameter transform: The closure that is executed when this Promise is fulfilled. It must return a non-promise. - Returns: A new promise that is resolved with the value returned from the provided closure. For example: @@ -68,12 +68,12 @@ public extension Thenable { //… } */ - func map(on: DispatchQueue? = conf.Q.map, flags: DispatchWorkItemFlags? = nil, _ transform: @escaping(T) throws -> U) -> Promise { + func map(on: Dispatcher? = conf.D.map, _ transform: @escaping(T) throws -> U) -> Promise { let rp = Promise(.pending) pipe { switch $0 { case .fulfilled(let value): - on.async(flags: flags) { + on.async { do { rp.box.seal(.fulfilled(try transform(value))) } catch { @@ -102,12 +102,12 @@ public extension Thenable { // either `PMKError.compactMap` or a `JSONError` } */ - func compactMap(on: DispatchQueue? = conf.Q.map, flags: DispatchWorkItemFlags? = nil, _ transform: @escaping(T) throws -> U?) -> Promise { + func compactMap(on: Dispatcher? = conf.D.map, _ transform: @escaping(T) throws -> U?) -> Promise { let rp = Promise(.pending) pipe { switch $0 { case .fulfilled(let value): - on.async(flags: flags) { + on.async { do { if let rv = try transform(value) { rp.box.seal(.fulfilled(rv)) @@ -131,7 +131,7 @@ public extension Thenable { Equivalent to `map { x -> Void in`, but since we force the `Void` return Swift is happier and gives you less hassle about your closure’s qualification. - - Parameter on: The queue to which the provided closure dispatches. + - Parameter on: The dispatcher that executes the provided closure. - Parameter body: The closure that is executed when this Promise is fulfilled. - Returns: A new promise fulfilled as `Void`. @@ -141,12 +141,12 @@ public extension Thenable { print(response.data) } */ - func done(on: DispatchQueue? = conf.Q.return, flags: DispatchWorkItemFlags? = nil, _ body: @escaping(T) throws -> Void) -> Promise { + func done(on: Dispatcher? = conf.Q.return, _ body: @escaping(T) throws -> Void) -> Promise { let rp = Promise(.pending) pipe { switch $0 { case .fulfilled(let value): - on.async(flags: flags) { + on.async { do { try body(value) rp.box.seal(.fulfilled(())) @@ -167,7 +167,7 @@ public extension Thenable { This is like `done` but it returns the same value that the handler is fed. `get` immutably accesses the fulfilled value; the returned Promise maintains that value. - - Parameter on: The queue to which the provided closure dispatches. + - Parameter on: The dispatcher that executes the provided closure. - 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. For example: @@ -181,7 +181,7 @@ public extension Thenable { print(foo, " is Void") } */ - func get(on: DispatchQueue? = conf.Q.return, flags: DispatchWorkItemFlags? = nil, _ body: @escaping (T) throws -> Void) -> Promise { + func get(on: Dispatcher? = conf.D.return, _ body: @escaping (T) throws -> Void) -> Promise { return map(on: on, flags: flags) { try body($0) return $0 @@ -212,7 +212,7 @@ public extension Thenable { /// - Returns: a new promise chained off this promise but with its value discarded. func asVoid() -> Promise { - return map(on: nil) { _ in } + return map { _ in } } } @@ -286,8 +286,8 @@ public extension Thenable where T: Sequence { // $0 => [2,4,6] } */ - func mapValues(on: DispatchQueue? = conf.Q.map, flags: DispatchWorkItemFlags? = nil, _ transform: @escaping(T.Iterator.Element) throws -> U) -> Promise<[U]> { - return map(on: on, flags: flags){ try $0.map(transform) } + func mapValues(on: Dispatcher? = conf.D.map, _ transform: @escaping(T.Iterator.Element) throws -> U) -> Promise<[U]> { + return map(on: on) { try $0.map(transform) } } /** @@ -301,8 +301,8 @@ public extension Thenable where T: Sequence { // $0 => [1,1,2,2,3,3] } */ - func flatMapValues(on: DispatchQueue? = conf.Q.map, flags: DispatchWorkItemFlags? = nil, _ transform: @escaping(T.Iterator.Element) throws -> U) -> Promise<[U.Iterator.Element]> { - return map(on: on, flags: flags){ (foo: T) in + func flatMapValues(on: Dispatcher? = conf.D.map, _ transform: @escaping(T.Iterator.Element) throws -> U) -> Promise<[U.Iterator.Element]> { + return map(on: on){ (foo: T) in try foo.flatMap{ try transform($0) } } } @@ -318,8 +318,8 @@ public extension Thenable where T: Sequence { // $0 => [1,2,3] } */ - func compactMapValues(on: DispatchQueue? = conf.Q.map, flags: DispatchWorkItemFlags? = nil, _ transform: @escaping(T.Iterator.Element) throws -> U?) -> Promise<[U]> { - return map(on: on, flags: flags) { foo -> [U] in + func compactMapValues(on: Dispatcher? = conf.D.map, _ transform: @escaping(T.Iterator.Element) throws -> U?) -> Promise<[U]> { + return map(on: on) { foo -> [U] in #if !swift(>=3.3) || (swift(>=4) && !swift(>=4.1)) return try foo.flatMap(transform) #else @@ -339,8 +339,8 @@ public extension Thenable where T: Sequence { // $0 => [2,4,6] } */ - func thenMap(on: DispatchQueue? = conf.Q.map, flags: DispatchWorkItemFlags? = nil, _ transform: @escaping(T.Iterator.Element) throws -> U) -> Promise<[U.T]> { - return then(on: on, flags: flags) { + func thenMap(on: Dispatcher? = conf.D.map, _ transform: @escaping(T.Iterator.Element) throws -> U) -> Promise<[U.T]> { + return then(on: on) { when(fulfilled: try $0.map(transform)) } } @@ -356,8 +356,8 @@ public extension Thenable where T: Sequence { // $0 => [1,1,2,2,3,3] } */ - func thenFlatMap(on: DispatchQueue? = conf.Q.map, flags: DispatchWorkItemFlags? = nil, _ transform: @escaping(T.Iterator.Element) throws -> U) -> Promise<[U.T.Iterator.Element]> where U.T: Sequence { - return then(on: on, flags: flags) { + func thenFlatMap(on: Dispatcher? = conf.D.map, _ 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) { $0.flatMap{ $0 } @@ -375,8 +375,8 @@ public extension Thenable where T: Sequence { // $0 => [2,3] } */ - func filterValues(on: DispatchQueue? = conf.Q.map, flags: DispatchWorkItemFlags? = nil, _ isIncluded: @escaping (T.Iterator.Element) -> Bool) -> Promise<[T.Iterator.Element]> { - return map(on: on, flags: flags) { + func filterValues(on: Dispatcher? = conf.D.map, _ isIncluded: @escaping (T.Iterator.Element) -> Bool) -> Promise<[T.Iterator.Element]> { + return map(on: on) { $0.filter(isIncluded) } } @@ -394,8 +394,8 @@ public extension Thenable where T: Collection { } } - func firstValue(on: DispatchQueue? = conf.Q.map, flags: DispatchWorkItemFlags? = nil, where test: @escaping (T.Iterator.Element) -> Bool) -> Promise { - return map(on: on, flags: flags) { + func firstValue(on: Dispatcher? = conf.D.map, where test: @escaping (T.Iterator.Element) -> Bool) -> Promise { + return map(on: on) { for x in $0 where test(x) { return x } @@ -418,7 +418,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: DispatchQueue? = conf.Q.map, flags: DispatchWorkItemFlags? = nil) -> Promise<[T.Iterator.Element]> { - return map(on: on, flags: flags){ $0.sorted() } + func sortedValues(on: Dispatcher? = conf.D.map) -> Promise<[T.Iterator.Element]> { + return map(on: on){ $0.sorted() } } } From b4e39b595b8ddf7714de8c383862759b593e5ed9 Mon Sep 17 00:00:00 2001 From: Garth Snyder Date: Mon, 18 Jun 2018 00:43:50 -0700 Subject: [PATCH 02/26] Put back map(on: nil) --- Sources/Guarantee.swift | 2 +- Sources/Thenable.swift | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Sources/Guarantee.swift b/Sources/Guarantee.swift index be806d044..0b71f64af 100644 --- a/Sources/Guarantee.swift +++ b/Sources/Guarantee.swift @@ -106,7 +106,7 @@ public extension Guarantee { } public func asVoid() -> Guarantee { - return map { _ in } + return map(on: nil) { _ in } } /** diff --git a/Sources/Thenable.swift b/Sources/Thenable.swift index c37e4db5b..a56f53f19 100644 --- a/Sources/Thenable.swift +++ b/Sources/Thenable.swift @@ -212,7 +212,7 @@ public extension Thenable { /// - Returns: a new promise chained off this promise but with its value discarded. func asVoid() -> Promise { - return map { _ in } + return map(on: nil) { _ in } } } From 8cd0121f0c8284f66de4069d443439a03b1fcc14 Mon Sep 17 00:00:00 2001 From: Garth Snyder Date: Mon, 18 Jun 2018 01:25:24 -0700 Subject: [PATCH 03/26] Rename async() method args to forestall ambiguity; compiles --- Sources/Box.swift | 6 +++--- Sources/Dispatcher.swift | 10 +++++----- Sources/Thenable.swift | 2 +- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/Sources/Box.swift b/Sources/Box.swift index cf0f99af9..c0f9ebd99 100644 --- a/Sources/Box.swift +++ b/Sources/Box.swift @@ -84,14 +84,14 @@ class EmptyBox: Box { } -extension Optional: Dispatcher where Wrapped: Dispatcher { +extension Optional: Dispatcher where Wrapped == Dispatcher { @inline(__always) - public func async(execute body: @escaping () -> Void) { + public func async(_ body: @escaping () -> Void) { switch self { case .none: body() case .some(let dispatcher): - dispatcher.async(execute: body) + dispatcher.async(body) } } } diff --git a/Sources/Dispatcher.swift b/Sources/Dispatcher.swift index 0750f1e54..d5e0807b2 100644 --- a/Sources/Dispatcher.swift +++ b/Sources/Dispatcher.swift @@ -1,7 +1,7 @@ import Dispatch public protocol Dispatcher { - func async(execute work: @escaping () -> Void) + func async(_ body: @escaping () -> Void) } public struct DispatchQueueDispatcher: Dispatcher { @@ -14,8 +14,8 @@ public struct DispatchQueueDispatcher: Dispatcher { self.flags = flags } - public func async(execute work: @escaping () -> Void) { - queue.async(flags: flags, execute: work) + public func async(_ body: @escaping () -> Void) { + queue.async(flags: flags, execute: body) } } @@ -23,8 +23,8 @@ public struct DispatchQueueDispatcher: Dispatcher { extension DispatchQueue: Dispatcher { /// Explicit declaration required; actual function signature is not identical to protocol - public func async(execute work: @escaping () -> Void) { - async(execute: work) + public func async(_ body: @escaping () -> Void) { + async(execute: body) } } diff --git a/Sources/Thenable.swift b/Sources/Thenable.swift index a56f53f19..b944a3896 100644 --- a/Sources/Thenable.swift +++ b/Sources/Thenable.swift @@ -182,7 +182,7 @@ public extension Thenable { } */ func get(on: Dispatcher? = conf.D.return, _ body: @escaping (T) throws -> Void) -> Promise { - return map(on: on, flags: flags) { + return map(on: on) { try body($0) return $0 } From 18baef69bd26679da04b9cac6592933f6d9c2bcc Mon Sep 17 00:00:00 2001 From: Garth Snyder Date: Tue, 19 Jun 2018 18:36:08 -0700 Subject: [PATCH 04/26] Working on Dispatcher tests --- PromiseKit.xcodeproj/project.pbxproj | 4 + Sources/Dispatcher.swift | 2 +- Sources/Thenable.swift | 2 +- Tests/CorePromise/DispatcherTests.swift | 105 ++++++++++++++++++++++++ 4 files changed, 111 insertions(+), 2 deletions(-) create mode 100644 Tests/CorePromise/DispatcherTests.swift diff --git a/PromiseKit.xcodeproj/project.pbxproj b/PromiseKit.xcodeproj/project.pbxproj index 4c2c1e9a5..f64ee9797 100644 --- a/PromiseKit.xcodeproj/project.pbxproj +++ b/PromiseKit.xcodeproj/project.pbxproj @@ -79,6 +79,7 @@ 63D9B2EF203385FD0075C00B /* race.m in Sources */ = {isa = PBXBuildFile; fileRef = 63D9B2EE203385FD0075C00B /* race.m */; }; 63D9B2F120338D5D0075C00B /* Deprecations.swift in Sources */ = {isa = PBXBuildFile; fileRef = 63D9B2F020338D5D0075C00B /* Deprecations.swift */; }; BB2524DE20D729A60010F7B0 /* Dispatcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB2524DD20D729A60010F7B0 /* Dispatcher.swift */; }; + BB4AF7C520D820700008333D /* DispatcherTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB4AF7C320D819360008333D /* DispatcherTests.swift */; }; C013F7382048E3B6006B57B1 /* MockNodeEnvironment.swift in Sources */ = {isa = PBXBuildFile; fileRef = C013F7372048E3B6006B57B1 /* MockNodeEnvironment.swift */; }; C013F73A2049076A006B57B1 /* JSPromise.swift in Sources */ = {isa = PBXBuildFile; fileRef = C013F7392049076A006B57B1 /* JSPromise.swift */; }; C013F73C20494291006B57B1 /* JSAdapter.swift in Sources */ = {isa = PBXBuildFile; fileRef = C013F73B20494291006B57B1 /* JSAdapter.swift */; }; @@ -221,6 +222,7 @@ 63D9B2EE203385FD0075C00B /* race.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = race.m; path = Sources/race.m; sourceTree = ""; }; 63D9B2F020338D5D0075C00B /* Deprecations.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = Deprecations.swift; path = Sources/Deprecations.swift; sourceTree = ""; }; BB2524DD20D729A60010F7B0 /* Dispatcher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = Dispatcher.swift; path = Sources/Dispatcher.swift; sourceTree = ""; }; + BB4AF7C320D819360008333D /* DispatcherTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DispatcherTests.swift; sourceTree = ""; }; C013F7372048E3B6006B57B1 /* MockNodeEnvironment.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = MockNodeEnvironment.swift; path = "Tests/JS-A+/MockNodeEnvironment.swift"; sourceTree = ""; }; C013F7392049076A006B57B1 /* JSPromise.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = JSPromise.swift; path = "Tests/JS-A+/JSPromise.swift"; sourceTree = ""; }; C013F73B20494291006B57B1 /* JSAdapter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = JSAdapter.swift; path = "Tests/JS-A+/JSAdapter.swift"; sourceTree = ""; }; @@ -349,6 +351,7 @@ 635D64161D59635300BC0AF5 /* StressTests.swift */, 635D640D1D59635300BC0AF5 /* ZalgoTests.swift */, 639BF755203DF02C00FA577B /* Utilities.swift */, + BB4AF7C320D819360008333D /* DispatcherTests.swift */, ); name = Core; path = Tests/CorePromise; @@ -674,6 +677,7 @@ 635D64221D59635300BC0AF5 /* ZalgoTests.swift in Sources */, 635D64271D59635300BC0AF5 /* RaceTests.swift in Sources */, 632FBBE51F33B338008F8FBB /* CatchableTests.swift in Sources */, + BB4AF7C520D820700008333D /* DispatcherTests.swift in Sources */, 63CF6D80203CD19200EC8927 /* ThenableTests.swift in Sources */, 635D642B1D59635300BC0AF5 /* StressTests.swift in Sources */, 630A805A203CEF6800D25F23 /* WhenTests.m in Sources */, diff --git a/Sources/Dispatcher.swift b/Sources/Dispatcher.swift index d5e0807b2..bff518f39 100644 --- a/Sources/Dispatcher.swift +++ b/Sources/Dispatcher.swift @@ -369,7 +369,7 @@ public extension CatchMixin { - Parameter body: The handler to execute if this promise is rejected. - SeeAlso: [Cancellation](http://promisekit.org/docs/) */ - func recover(on: DispatchQueue? = conf.Q.map, flags: DispatchWorkItemFlags? = nil, policy: CatchPolicy = conf.catchPolicy, _ body: @escaping(Error) throws -> U) -> Promise where U.T == T { + func recover(on: DispatchQueue? = .pmkDefault, flags: DispatchWorkItemFlags? = nil, policy: CatchPolicy = conf.catchPolicy, _ body: @escaping(Error) throws -> U) -> Promise where U.T == T { let dispatcher = selectDispatcher(given: on, configured: conf.D.map, flags: flags) return recover(on: dispatcher, policy: policy, body) } diff --git a/Sources/Thenable.swift b/Sources/Thenable.swift index b944a3896..7516d6535 100644 --- a/Sources/Thenable.swift +++ b/Sources/Thenable.swift @@ -141,7 +141,7 @@ public extension Thenable { print(response.data) } */ - func done(on: Dispatcher? = conf.Q.return, _ body: @escaping(T) throws -> Void) -> Promise { + func done(on: Dispatcher? = conf.D.return, _ body: @escaping(T) throws -> Void) -> Promise { let rp = Promise(.pending) pipe { switch $0 { diff --git a/Tests/CorePromise/DispatcherTests.swift b/Tests/CorePromise/DispatcherTests.swift new file mode 100644 index 000000000..64ae85178 --- /dev/null +++ b/Tests/CorePromise/DispatcherTests.swift @@ -0,0 +1,105 @@ +import PromiseKit +import XCTest + +class RecordingDispatcher: Dispatcher { + + var dispatchCount = 0 + + func async(_ body: @escaping () -> Void) { + dispatchCount += 1 + DispatchQueue.global(qos: .background).async(execute: body) + } + +} + +class DispatcherTests: XCTestCase { + + var dispatcher = RecordingDispatcher() + var dispatcherB = RecordingDispatcher() + + override func setUp() { + dispatcher = RecordingDispatcher() + 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) // Not representable as DispatchQueues + XCTAssertNil(PromiseKit.conf.Q.return) + Promise { seal in + seal.fulfill(42) + }.map { + $0 + 10 + }.done() { + XCTAssertEqual($0, 52) + XCTAssertEqual(self.dispatcher.dispatchCount, 1) + XCTAssertEqual(self.dispatcherB.dispatchCount, 1) + ex.fulfill() + }.cauterize() + waitForExpectations(timeout: 1) + PromiseKit.conf.D.map = DispatchQueue.main + PromiseKit.conf.Q.return = .main + XCTAssert(PromiseKit.conf.Q.map === DispatchQueue.main) + XCTAssert((PromiseKit.conf.D.return as? DispatchQueue)! === DispatchQueue.main) + PromiseKit.conf.D = oldConf + } + + func testDispatcherWithThrow() { + let ex = expectation(description: "Dispatcher with throw") + Promise { seal in + seal.fulfill(42) + }.map(on: dispatcher) { _ in + throw PMKError.badInput + }.catch(on: dispatcher) { _ in + ex.fulfill() + } + waitForExpectations(timeout: 1) + XCTAssertEqual(self.dispatcher.dispatchCount, 2) + } + + func testDispatchQueueBackwardCompatibility() { + let ex = expectation(description: "DispatchQueue compatibility") + let oldConf = PromiseKit.conf.D + PromiseKit.conf.D = (map: dispatcher, return: dispatcher) + Promise.value(42).map(on: .global(qos: .background), flags: .barrier) { (x: Int) -> Int in + return x + 10 + }.then(on: .main, flags: []) { + XCTAssertEqual($0, 52) + return Promise.value(50) + }.done(on: .global(qos: .userInitiated)) { + XCTAssertEqual($0, 50) + ex.fulfill() + }.cauterize() + waitForExpectations(timeout: 1) + XCTAssertEqual(self.dispatcher.dispatchCount, 0) + PromiseKit.conf.D = oldConf + } + + func testDispatcherPromiseExtension() { + let ex = expectation(description: "Dispatcher.promise") + dispatcher.promise { + return 42 + }.done(on: dispatcher) { + XCTAssertEqual($0, 42) + XCTAssertEqual(self.dispatcher.dispatchCount, 2) + ex.fulfill() + }.cauterize() + waitForExpectations(timeout: 1) + } + + func testDispatcherGuaranteeExtension() { + let ex = expectation(description: "Dispatcher.guarantee") + dispatcher.guarantee { + return 42 + }.done(on: .main) { + XCTAssertEqual($0, 42) + XCTAssertEqual(self.dispatcher.dispatchCount, 1) + ex.fulfill() + } + waitForExpectations(timeout: 1) + } + +} From 1ac7e213877e870b9dd69d99da051cd78a78f6d2 Mon Sep 17 00:00:00 2001 From: Garth Snyder Date: Wed, 20 Jun 2018 12:54:41 -0700 Subject: [PATCH 05/26] Make Dispatcher arguments nonoptional --- Sources/Box.swift | 13 ------------- Sources/Catchable.swift | 30 +++++++++++++++--------------- Sources/Configuration.swift | 4 ++-- Sources/Dispatcher.swift | 28 +++++++++++++++------------- Sources/Guarantee.swift | 16 ++++++++-------- Sources/Promise.swift | 2 +- Sources/Thenable.swift | 34 +++++++++++++++++----------------- 7 files changed, 58 insertions(+), 69 deletions(-) diff --git a/Sources/Box.swift b/Sources/Box.swift index c0f9ebd99..862269f4d 100644 --- a/Sources/Box.swift +++ b/Sources/Box.swift @@ -82,16 +82,3 @@ class EmptyBox: Box { } } } - - -extension Optional: Dispatcher where Wrapped == Dispatcher { - @inline(__always) - public func async(_ body: @escaping () -> Void) { - switch self { - case .none: - body() - case .some(let dispatcher): - dispatcher.async(body) - } - } -} diff --git a/Sources/Catchable.swift b/Sources/Catchable.swift index 8a22fd2e2..83c1c33f7 100644 --- a/Sources/Catchable.swift +++ b/Sources/Catchable.swift @@ -21,7 +21,7 @@ public extension CatchMixin { - SeeAlso: [Cancellation](http://promisekit.org/docs/) */ @discardableResult - func `catch`(on: Dispatcher? = conf.D.return, policy: CatchPolicy = conf.catchPolicy, _ body: @escaping(Error) -> Void) -> PMKFinalizer { + func `catch`(on: Dispatcher = conf.D.return, policy: CatchPolicy = conf.catchPolicy, _ body: @escaping(Error) -> Void) -> PMKFinalizer { let finalizer = PMKFinalizer() pipe { switch $0 { @@ -29,7 +29,7 @@ public extension CatchMixin { guard policy == .allErrors || !error.isCancelled else { fallthrough } - on.async { + on.dispatch { body(error) finalizer.pending.resolve(()) } @@ -45,7 +45,7 @@ public class PMKFinalizer { let pending = Guarantee.pending() /// `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.D.return, _ body: @escaping () -> Void) { pending.guarantee.done(on: on) { body() } @@ -72,7 +72,7 @@ public extension CatchMixin { - Parameter body: The handler to execute if this promise is rejected. - SeeAlso: [Cancellation](http://promisekit.org/docs/) */ - 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.D.map, policy: CatchPolicy = conf.catchPolicy, _ body: @escaping(Error) throws -> U) -> Promise where U.T == T { let rp = Promise(.pending) pipe { switch $0 { @@ -80,7 +80,7 @@ public extension CatchMixin { rp.box.seal(.fulfilled(value)) case .rejected(let error): if policy == .allErrors || !error.isCancelled { - on.async { + on.dispatch { do { let rv = try body(error) guard rv !== rp else { throw PMKError.returnedSelf } @@ -106,14 +106,14 @@ public extension CatchMixin { - SeeAlso: [Cancellation](http://promisekit.org/docs/) */ @discardableResult - func recover(on: Dispatcher? = conf.D.map, _ body: @escaping(Error) -> Guarantee) -> Guarantee { + func recover(on: Dispatcher = conf.D.map, _ body: @escaping(Error) -> Guarantee) -> Guarantee { let rg = Guarantee(.pending) pipe { switch $0 { case .fulfilled(let value): rg.box.seal(value) case .rejected(let error): - on.async { + on.dispatch { body(error).pipe(to: rg.box.seal) } } @@ -138,10 +138,10 @@ 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.D.return, _ body: @escaping () -> Void) -> Promise { let rp = Promise(.pending) pipe { result in - on.async { + on.dispatch { body() rp.box.seal(result) } @@ -167,10 +167,10 @@ 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.D.return, _ body: @escaping () -> Guarantee) -> Promise { let rp = Promise(.pending) pipe { result in - on.async { + on.dispatch { body().done { rp.box.seal(result) } @@ -205,14 +205,14 @@ public extension CatchMixin where T == Void { - SeeAlso: [Cancellation](http://promisekit.org/docs/) */ @discardableResult - func recover(on: Dispatcher? = conf.D.map, flags: DispatchWorkItemFlags? = nil, _ body: @escaping(Error) -> Void) -> Guarantee { + func recover(on: Dispatcher = conf.D.map, _ body: @escaping(Error) -> Void) -> Guarantee { let rg = Guarantee(.pending) pipe { switch $0 { case .fulfilled: rg.box.seal(()) case .rejected(let error): - on.async { + on.dispatch { body(error) rg.box.seal(()) } @@ -230,7 +230,7 @@ public extension CatchMixin where T == Void { - Parameter body: The handler to execute if this promise is rejected. - SeeAlso: [Cancellation](http://promisekit.org/docs/) */ - func recover(on: Dispatcher? = conf.D.map, policy: CatchPolicy = conf.catchPolicy, _ body: @escaping(Error) throws -> Void) -> Promise { + func recover(on: Dispatcher = conf.D.map, policy: CatchPolicy = conf.catchPolicy, _ body: @escaping(Error) throws -> Void) -> Promise { let rg = Promise(.pending) pipe { switch $0 { @@ -238,7 +238,7 @@ public extension CatchMixin where T == Void { rg.box.seal(.fulfilled(())) case .rejected(let error): if policy == .allErrors || !error.isCancelled { - on.async { + on.dispatch { do { rg.box.seal(.fulfilled(try body(error))) } catch { diff --git a/Sources/Configuration.swift b/Sources/Configuration.swift index 0e64d5787..65aec1351 100644 --- a/Sources/Configuration.swift +++ b/Sources/Configuration.swift @@ -5,11 +5,11 @@ public struct PMKConfiguration { /// Backward compatibility: default DispatchQueues that promise handlers dispatch to public var Q: (map: DispatchQueue?, return: DispatchQueue?) { get { return (map: D.map as? DispatchQueue, return: D.return as? DispatchQueue) } - set { D = (map: newValue.map, return: newValue.return) } + set { D = (map: newValue.map ?? CurrentThreadDispatcher(), return: newValue.return ?? CurrentThreadDispatcher()) } } /// The default Dispatchers that promise handlers dispatch to - public var D: (map: Dispatcher?, return: Dispatcher?) = (map: DispatchQueue.main, return: DispatchQueue.main) + public var D: (map: Dispatcher, return: Dispatcher) = (map: DispatchQueue.main, return: DispatchQueue.main) /// The default catch-policy for all `catch` and `resolve` public var catchPolicy = CatchPolicy.allErrorsExceptCancellation diff --git a/Sources/Dispatcher.swift b/Sources/Dispatcher.swift index bff518f39..2d7a244b8 100644 --- a/Sources/Dispatcher.swift +++ b/Sources/Dispatcher.swift @@ -1,10 +1,10 @@ import Dispatch public protocol Dispatcher { - func async(_ body: @escaping () -> Void) + func dispatch(_ body: @escaping () -> Void) } -public struct DispatchQueueDispatcher: Dispatcher { +public class DispatchQueueDispatcher: Dispatcher { let queue: DispatchQueue let flags: DispatchWorkItemFlags @@ -14,19 +14,23 @@ public struct DispatchQueueDispatcher: Dispatcher { self.flags = flags } - public func async(_ body: @escaping () -> Void) { + public func dispatch(_ body: @escaping () -> Void) { queue.async(flags: flags, execute: body) } } +public struct CurrentThreadDispatcher: Dispatcher { + public func dispatch(_ body: @escaping () -> Void) { + body() + } +} + extension DispatchQueue: Dispatcher { - /// Explicit declaration required; actual function signature is not identical to protocol - public func async(_ body: @escaping () -> Void) { + public func dispatch(_ body: @escaping () -> Void) { async(execute: body) } - } /// Used as default parameter for backward compatibility since clients may explicitly @@ -38,15 +42,13 @@ public extension DispatchQueue { static var pmkDefault = DispatchQueue(label: "org.promisekit.sentinel") } -extension DispatchQueue { - - public func asDispatcher(withFlags flags: DispatchWorkItemFlags? = nil) -> Dispatcher { +public extension DispatchQueue { + func asDispatcher(withFlags flags: DispatchWorkItemFlags? = nil) -> Dispatcher { if let flags = flags { return DispatchQueueDispatcher(queue: self, flags: flags) } return self } - } /// This hairball disambiguates all the various combinations of explicit arguments, default @@ -56,18 +58,18 @@ extension DispatchQueue { /// /// TODO: should conf.D = nil turn off dispatching even if explicit dispatch arguments are given? -fileprivate func selectDispatcher(given: DispatchQueue?, configured: Dispatcher?, flags: DispatchWorkItemFlags?) -> Dispatcher? { +fileprivate func selectDispatcher(given: DispatchQueue?, configured: Dispatcher, flags: DispatchWorkItemFlags?) -> Dispatcher { guard let given = given else { if flags != nil { print("PromiseKit: warning: nil DispatchQueue specified, but DispatchWorkItemFlags were also supplied (ignored)") } - return nil + return CurrentThreadDispatcher() } if given !== DispatchQueue.pmkDefault { return given.asDispatcher(withFlags: flags) } else if let flags = flags, let configured = configured as? DispatchQueue { return configured.asDispatcher(withFlags: flags) - } else if flags != nil && configured != nil { + } else if flags != nil { print("PromiseKit: warning: DispatchWorkItemFlags flags specified, but default dispatcher is not a DispatchQueue (ignored)") } return configured diff --git a/Sources/Guarantee.swift b/Sources/Guarantee.swift index 0b71f64af..d9be32f6b 100644 --- a/Sources/Guarantee.swift +++ b/Sources/Guarantee.swift @@ -66,10 +66,10 @@ public class Guarantee: Thenable { public extension Guarantee { @discardableResult - func done(on: Dispatcher? = conf.D.return, _ body: @escaping(T) -> Void) -> Guarantee { + func done(on: Dispatcher = conf.D.return, _ body: @escaping(T) -> Void) -> Guarantee { let rg = Guarantee(.pending) pipe { (value: T) in - on.async { + on.dispatch { body(value) rg.box.seal(()) } @@ -84,10 +84,10 @@ public extension Guarantee { } } - func map(on: Dispatcher? = conf.D.map, _ body: @escaping(T) -> U) -> Guarantee { + func map(on: Dispatcher = conf.D.map, _ body: @escaping(T) -> U) -> Guarantee { let rg = Guarantee(.pending) pipe { value in - on.async { + on.dispatch { rg.box.seal(body(value)) } } @@ -95,10 +95,10 @@ public extension Guarantee { } @discardableResult - func then(on: Dispatcher? = conf.D.map, flags: DispatchWorkItemFlags? = nil, _ body: @escaping(T) -> Guarantee) -> Guarantee { + func then(on: Dispatcher = conf.D.map, flags: DispatchWorkItemFlags? = nil, _ body: @escaping(T) -> Guarantee) -> Guarantee { let rg = Guarantee(.pending) pipe { value in - on.async { + on.dispatch { body(value).pipe(to: rg.box.seal) } } @@ -145,7 +145,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.D.map, _ transform: @escaping(T.Iterator.Element) -> Guarantee) -> Guarantee<[U]> { return then(on: on) { when(fulfilled: $0.map(transform)) }.recover { @@ -204,7 +204,7 @@ public extension Dispatcher { */ func guarantee(execute body: @escaping () -> T) -> Guarantee { let rg = Guarantee(.pending) - async { + dispatch { rg.box.seal(body()) } return rg diff --git a/Sources/Promise.swift b/Sources/Promise.swift index 0af0b11af..a66cee001 100644 --- a/Sources/Promise.swift +++ b/Sources/Promise.swift @@ -184,7 +184,7 @@ public extension Dispatcher { */ func promise(execute body: @escaping () throws -> T) -> Promise { let promise = Promise(.pending) - async { + dispatch { do { promise.box.seal(.fulfilled(try body())) } catch { diff --git a/Sources/Thenable.swift b/Sources/Thenable.swift index 7516d6535..0cb935bdf 100644 --- a/Sources/Thenable.swift +++ b/Sources/Thenable.swift @@ -30,12 +30,12 @@ public extension Thenable { //… } */ - func then(on: Dispatcher? = conf.D.map, _ body: @escaping(T) throws -> U) -> Promise { + func then(on: Dispatcher = conf.D.map, _ body: @escaping(T) throws -> U) -> Promise { let rp = Promise(.pending) pipe { switch $0 { case .fulfilled(let value): - on.async { + on.dispatch { do { let rv = try body(value) guard rv !== rp else { throw PMKError.returnedSelf } @@ -68,12 +68,12 @@ public extension Thenable { //… } */ - func map(on: Dispatcher? = conf.D.map, _ transform: @escaping(T) throws -> U) -> Promise { + func map(on: Dispatcher = conf.D.map, _ transform: @escaping(T) throws -> U) -> Promise { let rp = Promise(.pending) pipe { switch $0 { case .fulfilled(let value): - on.async { + on.dispatch { do { rp.box.seal(.fulfilled(try transform(value))) } catch { @@ -102,12 +102,12 @@ 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.D.map, _ transform: @escaping(T) throws -> U?) -> Promise { let rp = Promise(.pending) pipe { switch $0 { case .fulfilled(let value): - on.async { + on.dispatch { do { if let rv = try transform(value) { rp.box.seal(.fulfilled(rv)) @@ -141,12 +141,12 @@ public extension Thenable { print(response.data) } */ - func done(on: Dispatcher? = conf.D.return, _ body: @escaping(T) throws -> Void) -> Promise { + func done(on: Dispatcher = conf.D.return, _ body: @escaping(T) throws -> Void) -> Promise { let rp = Promise(.pending) pipe { switch $0 { case .fulfilled(let value): - on.async { + on.dispatch { do { try body(value) rp.box.seal(.fulfilled(())) @@ -181,7 +181,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.D.return, _ body: @escaping (T) throws -> Void) -> Promise { return map(on: on) { try body($0) return $0 @@ -286,7 +286,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.D.map, _ transform: @escaping(T.Iterator.Element) throws -> U) -> Promise<[U]> { return map(on: on) { try $0.map(transform) } } @@ -301,7 +301,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.D.map, _ transform: @escaping(T.Iterator.Element) throws -> U) -> Promise<[U.Iterator.Element]> { return map(on: on){ (foo: T) in try foo.flatMap{ try transform($0) } } @@ -318,7 +318,7 @@ public extension Thenable where T: Sequence { // $0 => [1,2,3] } */ - func compactMapValues(on: Dispatcher? = conf.D.map, _ transform: @escaping(T.Iterator.Element) throws -> U?) -> Promise<[U]> { + func compactMapValues(on: Dispatcher = conf.D.map, _ transform: @escaping(T.Iterator.Element) throws -> U?) -> Promise<[U]> { return map(on: on) { foo -> [U] in #if !swift(>=3.3) || (swift(>=4) && !swift(>=4.1)) return try foo.flatMap(transform) @@ -339,7 +339,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.D.map, _ transform: @escaping(T.Iterator.Element) throws -> U) -> Promise<[U.T]> { return then(on: on) { when(fulfilled: try $0.map(transform)) } @@ -356,7 +356,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.D.map, _ 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) { @@ -375,7 +375,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.D.map, _ isIncluded: @escaping (T.Iterator.Element) -> Bool) -> Promise<[T.Iterator.Element]> { return map(on: on) { $0.filter(isIncluded) } @@ -394,7 +394,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.D.map, where test: @escaping (T.Iterator.Element) -> Bool) -> Promise { return map(on: on) { for x in $0 where test(x) { return x @@ -418,7 +418,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.D.map) -> Promise<[T.Iterator.Element]> { return map(on: on){ $0.sorted() } } } From 88b7937c9ae1da4257732cd961b5a589c760ff2f Mon Sep 17 00:00:00 2001 From: Garth Snyder Date: Wed, 20 Jun 2018 14:24:59 -0700 Subject: [PATCH 06/26] Finish tests --- Sources/Guarantee.swift | 2 +- Tests/CorePromise/DispatcherTests.swift | 56 +++++++++++++++++++++---- 2 files changed, 48 insertions(+), 10 deletions(-) diff --git a/Sources/Guarantee.swift b/Sources/Guarantee.swift index d9be32f6b..b8200a569 100644 --- a/Sources/Guarantee.swift +++ b/Sources/Guarantee.swift @@ -95,7 +95,7 @@ public extension Guarantee { } @discardableResult - func then(on: Dispatcher = conf.D.map, flags: DispatchWorkItemFlags? = nil, _ body: @escaping(T) -> Guarantee) -> Guarantee { + func then(on: Dispatcher = conf.D.map, _ body: @escaping(T) -> Guarantee) -> Guarantee { let rg = Guarantee(.pending) pipe { value in on.dispatch { diff --git a/Tests/CorePromise/DispatcherTests.swift b/Tests/CorePromise/DispatcherTests.swift index 64ae85178..6206ef10d 100644 --- a/Tests/CorePromise/DispatcherTests.swift +++ b/Tests/CorePromise/DispatcherTests.swift @@ -1,13 +1,23 @@ import PromiseKit import XCTest +fileprivate let queueIDKey = DispatchSpecificKey() + class RecordingDispatcher: Dispatcher { + static var queueIndex = 1 + var dispatchCount = 0 + let queue: DispatchQueue + + init() { + queue = DispatchQueue(label: "org.promisekit.testqueue \(RecordingDispatcher.queueIndex)") + RecordingDispatcher.queueIndex += 1 + } - func async(_ body: @escaping () -> Void) { + func dispatch(_ body: @escaping () -> Void) { dispatchCount += 1 - DispatchQueue.global(qos: .background).async(execute: body) + queue.async(execute: body) } } @@ -60,22 +70,50 @@ class DispatcherTests: XCTestCase { XCTAssertEqual(self.dispatcher.dispatchCount, 2) } - func testDispatchQueueBackwardCompatibility() { + func testDispatchQueueSelection() { + let ex = expectation(description: "DispatchQueue compatibility") + let oldConf = PromiseKit.conf.D PromiseKit.conf.D = (map: dispatcher, return: dispatcher) + + DispatchQueue.global(qos: .background).setSpecific(key: queueIDKey, value: 100) + DispatchQueue.main.setSpecific(key: queueIDKey, value: 102) + dispatcher.queue.setSpecific(key: queueIDKey, value: 103) + Promise.value(42).map(on: .global(qos: .background), flags: .barrier) { (x: Int) -> Int in + let queueID = DispatchQueue.getSpecific(key: queueIDKey) + XCTAssertNotNil(queueID) + XCTAssertEqual(queueID!, 100) return x + 10 - }.then(on: .main, flags: []) { - XCTAssertEqual($0, 52) + }.then(on: .main, flags: []) { (x: Int) -> Promise in + XCTAssertEqual(x, 52) + let queueID = DispatchQueue.getSpecific(key: queueIDKey) + XCTAssertNotNil(queueID) + XCTAssertEqual(queueID!, 102) return Promise.value(50) - }.done(on: .global(qos: .userInitiated)) { - XCTAssertEqual($0, 50) + }.map(on: nil) { (x: Int) -> Int in + let queueID = DispatchQueue.getSpecific(key: queueIDKey) + XCTAssertNotNil(queueID) + XCTAssertEqual(queueID!, 102) + return x + 10 + }.map { (x: Int) -> Int in + XCTAssertEqual(x, 60) + let queueID = DispatchQueue.getSpecific(key: queueIDKey) + XCTAssertNotNil(queueID) + XCTAssertEqual(queueID!, 103) + return x + 10 + }.done(on: .global(qos: .background)) { + XCTAssertEqual($0, 70) + let queueID = DispatchQueue.getSpecific(key: queueIDKey) + XCTAssertNotNil(queueID) + XCTAssertEqual(queueID!, 100) ex.fulfill() }.cauterize() + waitForExpectations(timeout: 1) - XCTAssertEqual(self.dispatcher.dispatchCount, 0) PromiseKit.conf.D = oldConf + } func testDispatcherPromiseExtension() { @@ -86,7 +124,7 @@ class DispatcherTests: XCTestCase { XCTAssertEqual($0, 42) XCTAssertEqual(self.dispatcher.dispatchCount, 2) ex.fulfill() - }.cauterize() + }.cauterize() waitForExpectations(timeout: 1) } From 351651154142bcb068e6f74cb6e81a005e1294e2 Mon Sep 17 00:00:00 2001 From: Garth Snyder Date: Wed, 20 Jun 2018 15:12:01 -0700 Subject: [PATCH 07/26] Generalize DispatchQueues to Dispatcher-protocol objects --- Sources/Guarantee.swift | 2 +- Sources/Promise.swift | 2 +- Tests/CorePromise/DispatcherTests.swift | 37 +++++++++++++------------ 3 files changed, 22 insertions(+), 19 deletions(-) diff --git a/Sources/Guarantee.swift b/Sources/Guarantee.swift index b8200a569..65cdbcce5 100644 --- a/Sources/Guarantee.swift +++ b/Sources/Guarantee.swift @@ -202,7 +202,7 @@ public extension Dispatcher { - Returns: A new `Guarantee` resolved by the result of the provided closure. - Note: There is no Promise/Thenable version of this due to Swift compiler ambiguity issues. */ - func guarantee(execute body: @escaping () -> T) -> Guarantee { + func dispatch(_: PMKNamespacer, _ body: @escaping () -> T) -> Guarantee { let rg = Guarantee(.pending) dispatch { rg.box.seal(body()) diff --git a/Sources/Promise.swift b/Sources/Promise.swift index a66cee001..ee2d12d90 100644 --- a/Sources/Promise.swift +++ b/Sources/Promise.swift @@ -182,7 +182,7 @@ public extension Dispatcher { - Returns: A new `Promise` resolved by the result of the provided closure. - Note: There is no Promise/Thenable version of this due to Swift compiler ambiguity issues. */ - func promise(execute body: @escaping () throws -> T) -> Promise { + func dispatch(_: PMKNamespacer, _ body: @escaping () throws -> T) -> Promise { let promise = Promise(.pending) dispatch { do { diff --git a/Tests/CorePromise/DispatcherTests.swift b/Tests/CorePromise/DispatcherTests.swift index 6206ef10d..817ba9d3d 100644 --- a/Tests/CorePromise/DispatcherTests.swift +++ b/Tests/CorePromise/DispatcherTests.swift @@ -77,7 +77,8 @@ class DispatcherTests: XCTestCase { let oldConf = PromiseKit.conf.D PromiseKit.conf.D = (map: dispatcher, return: dispatcher) - DispatchQueue.global(qos: .background).setSpecific(key: queueIDKey, value: 100) + let background = DispatchQueue.global(qos: .background) + background.setSpecific(key: queueIDKey, value: 100) DispatchQueue.main.setSpecific(key: queueIDKey, value: 102) dispatcher.queue.setSpecific(key: queueIDKey, value: 103) @@ -103,7 +104,7 @@ class DispatcherTests: XCTestCase { XCTAssertNotNil(queueID) XCTAssertEqual(queueID!, 103) return x + 10 - }.done(on: .global(qos: .background)) { + }.done(on: background) { XCTAssertEqual($0, 70) let queueID = DispatchQueue.getSpecific(key: queueIDKey) XCTAssertNotNil(queueID) @@ -116,25 +117,27 @@ class DispatcherTests: XCTestCase { } - func testDispatcherPromiseExtension() { + @available(macOS 10.10, iOS 2.0, tvOS 10.0, watchOS 2.0, *) + func testDispatcherExtensionReturnsGuarantee() { let ex = expectation(description: "Dispatcher.promise") - dispatcher.promise { - return 42 - }.done(on: dispatcher) { - XCTAssertEqual($0, 42) - XCTAssertEqual(self.dispatcher.dispatchCount, 2) + dispatcher.dispatch(.promise) { () -> Int in + XCTAssertFalse(Thread.isMainThread) + return 1 + }.done { one in + XCTAssertEqual(one, 1) ex.fulfill() - }.cauterize() + } waitForExpectations(timeout: 1) } - - func testDispatcherGuaranteeExtension() { - let ex = expectation(description: "Dispatcher.guarantee") - dispatcher.guarantee { - return 42 - }.done(on: .main) { - XCTAssertEqual($0, 42) - XCTAssertEqual(self.dispatcher.dispatchCount, 1) + + @available(macOS 10.10, iOS 2.0, tvOS 10.0, watchOS 2.0, *) + func testDispatcherExtensionCanThrowInBody() { + let ex = expectation(description: "Dispatcher.promise") + dispatcher.dispatch(.promise) { () -> Int in + throw PMKError.badInput + }.done { _ in + XCTFail() + }.catch { _ in ex.fulfill() } waitForExpectations(timeout: 1) From 7c7835a33a1cb16ed41af1027e02cfa67ce41633 Mon Sep 17 00:00:00 2001 From: Garth Snyder Date: Wed, 8 Aug 2018 15:59:54 -0700 Subject: [PATCH 08/26] Port Thenable.tap and Guarantee.get to Dispatcher objects --- Sources/Dispatcher.swift | 24 +++++++++++++++++++++++- Sources/Guarantee.swift | 4 ++-- Sources/Thenable.swift | 6 +++--- 3 files changed, 28 insertions(+), 6 deletions(-) diff --git a/Sources/Dispatcher.swift b/Sources/Dispatcher.swift index 2d7a244b8..b6407ebf5 100644 --- a/Sources/Dispatcher.swift +++ b/Sources/Dispatcher.swift @@ -85,6 +85,11 @@ public extension Guarantee { 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) + return get(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) return map(on: dispatcher, body) @@ -213,11 +218,28 @@ public extension Thenable { }.done { foo in print(foo, " is Void") } - */ + */ func get(on: DispatchQueue? = .pmkDefault, flags: DispatchWorkItemFlags? = nil, _ body: @escaping (T) throws -> Void) -> Promise { let dispatcher = selectDispatcher(given: on, configured: conf.D.return, flags: flags) return get(on: dispatcher, body) } + + /** + The provided closure is executed with promise result. + + This is like `get` but provides the Result of the Promise so you can inspect the value of the chain at this point without causing any side effects. + + - Parameter on: The queue to which the provided closure dispatches. + - 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. For example: + + promise.tap{ print($0) }.then{ /*…*/ } + */ + func tap(on: DispatchQueue? = .pmkDefault, flags: DispatchWorkItemFlags? = nil, _ body: @escaping(Result) -> Void) -> Promise { + let dispatcher = selectDispatcher(given: on, configured: conf.D.map, flags: flags) + return tap(on: dispatcher, body) + } + } public extension Thenable where T: Sequence { diff --git a/Sources/Guarantee.swift b/Sources/Guarantee.swift index 65cdbcce5..2606f19d3 100644 --- a/Sources/Guarantee.swift +++ b/Sources/Guarantee.swift @@ -77,8 +77,8 @@ public extension Guarantee { return rg } - func get(on: DispatchQueue? = conf.Q.return, flags: DispatchWorkItemFlags? = nil, _ body: @escaping (T) -> Void) -> Guarantee { - return map(on: on, flags: flags) { + func get(on: Dispatcher = conf.D.return, _ body: @escaping (T) -> Void) -> Guarantee { + return map(on: on) { body($0) return $0 } diff --git a/Sources/Thenable.swift b/Sources/Thenable.swift index 0cb935bdf..b3a1f340e 100644 --- a/Sources/Thenable.swift +++ b/Sources/Thenable.swift @@ -193,16 +193,16 @@ public extension Thenable { This is like `get` but provides the Result of the Promise so you can inspect the value of the chain at this point without causing any side effects. - - Parameter on: The queue to which the provided closure dispatches. + - Parameter on: The dispatcher that executes the provided closure. - 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. For example: promise.tap{ print($0) }.then{ /*…*/ } */ - func tap(on: DispatchQueue? = conf.Q.map, flags: DispatchWorkItemFlags? = nil, _ body: @escaping(Result) -> Void) -> Promise { + func tap(on: Dispatcher = conf.D.map, _ body: @escaping(Result) -> Void) -> Promise { return Promise { seal in pipe { result in - on.async(flags: flags) { + on.dispatch { body(result) seal.resolve(result) } From 2cc021a3cab838cddc1177ee51907a6e7d36130b Mon Sep 17 00:00:00 2001 From: Garth Snyder Date: Wed, 8 Aug 2018 19:49:03 -0700 Subject: [PATCH 09/26] Travis-test Linux Swift 3 compatibility only with latest compiler --- .travis.yml | 141 ++++++++++++++++------------------------------------ 1 file changed, 43 insertions(+), 98 deletions(-) diff --git a/.travis.yml b/.travis.yml index f921a53b9..995a68e17 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,125 +1,70 @@ -# - `pod lib lint` weirdness explanation: https://github.com/CocoaPods/CocoaPods/issues/7123 -# - In Travis `cd foo` returns non-zero exit, hence `set +e` before changing directory -# - We need a newer version of npm in order to assemble our JS/A+ tests - -branches: - only: - - master matrix: include: - - {osx_image: xcode10, env: 'POD_LINT=1', os: osx, language: objective-c} - - - {osx_image: xcode8.3, env: 'SWFT=3.1 PLAT=iOS DST="OS=10.3.1,name=iPhone SE"', os: osx, language: objective-c} - - {osx_image: xcode8.3, env: 'SWFT=3.1 PLAT=tvOS DST="OS=10.2,name=Apple TV 1080p"', os: osx, language: objective-c} - - {osx_image: xcode8.3, env: 'SWFT=3.1 PLAT=macOS DST="arch=x86_64"', os: osx, language: objective-c} - - {osx_image: xcode8.3, env: 'SWFT=3.1 PLAT=watchOS DST="OS=3.2,name=Apple Watch - 38mm"', os: osx, language: objective-c} - - - {osx_image: xcode9.2, env: 'SWFT=3.2 PLAT=iOS DST="OS=11.2,name=iPhone SE"', os: osx, language: objective-c} - - {osx_image: xcode9.2, env: 'SWFT=3.2 PLAT=tvOS DST="OS=11.2,name=Apple TV"', os: osx, language: objective-c} - - {osx_image: xcode9.2, env: 'SWFT=3.2 PLAT=macOS DST="arch=x86_64"', os: osx, language: objective-c} - - {osx_image: xcode9.2, env: 'SWFT=3.2 PLAT=watchOS DST="OS=4.2,name=Apple Watch - 38mm"', os: osx, language: objective-c} - - - {osx_image: xcode9.4, env: 'SWFT=3.3 PLAT=iOS DST="OS=11.4,name=iPhone SE"', os: osx, language: objective-c} - - {osx_image: xcode9.4, env: 'SWFT=3.3 PLAT=tvOS DST="OS=11.4,name=Apple TV"', os: osx, language: objective-c} - - {osx_image: xcode9.4, env: 'SWFT=3.3 PLAT=macOS DST="arch=x86_64"', os: osx, language: objective-c} - - {osx_image: xcode9.4, env: 'SWFT=3.3 PLAT=watchOS DST="OS=4.3,name=Apple Watch - 38mm"', os: osx, language: objective-c} - - - {osx_image: xcode10, env: 'SWFT=3.4 PLAT=iOS DST="OS=12.0,name=iPhone SE"', os: osx, language: objective-c} - - {osx_image: xcode10, env: 'SWFT=3.4 PLAT=tvOS DST="OS=12.0,name=Apple TV"', os: osx, language: objective-c} - - {osx_image: xcode10, env: 'SWFT=3.4 PLAT=macOS DST="arch=x86_64"', os: osx, language: objective-c} - - {osx_image: xcode10, env: 'SWFT=3.4 PLAT=watchOS DST="OS=5.0,name=Apple Watch Series 3 - 42mm"', os: osx, language: objective-c} + - {osx_image: xcode8.3, env: 'SWFT=3.1 PLAT=iOS DST="OS=10.3.1,name=iPhone SE"', os: osx, language: objective-c} + - {osx_image: xcode8.3, env: 'SWFT=3.1 PLAT=tvOS DST="OS=10.2,name=Apple TV 1080p"', os: osx, language: objective-c} + - {osx_image: xcode8.3, env: 'SWFT=3.1 PLAT=macOS DST="arch=x86_64"', os: osx, language: objective-c} + - {osx_image: xcode8.3, env: 'SWFT=3.1 PLAT=watchOS DST="OS=3.2,name=Apple Watch - 38mm"', os: osx, language: objective-c} + + - {osx_image: xcode9.2, env: 'SWFT=3.2 PLAT=iOS DST="OS=11.2,name=iPhone SE"', os: osx, language: objective-c} + - {osx_image: xcode9.2, env: 'SWFT=3.2 PLAT=tvOS DST="OS=11.2,name=Apple TV"', os: osx, language: objective-c} + - {osx_image: xcode9.2, env: 'SWFT=3.2 PLAT=macOS DST="arch=x86_64"', os: osx, language: objective-c} + - {osx_image: xcode9.2, env: 'SWFT=3.2 PLAT=watchOS DST="OS=4.2,name=Apple Watch - 38mm"', os: osx, language: objective-c} + + - {osx_image: xcode9.4, env: 'SWFT=3.3 PLAT=iOS DST="OS=11.4,name=iPhone SE"', os: osx, language: objective-c} + - {osx_image: xcode9.4, env: 'SWFT=3.3 PLAT=tvOS DST="OS=11.4,name=Apple TV"', os: osx, language: objective-c} + - {osx_image: xcode9.4, env: 'SWFT=3.3 PLAT=macOS DST="arch=x86_64"', os: osx, language: objective-c} + - {osx_image: xcode9.4, env: 'SWFT=3.3 PLAT=watchOS DST="OS=4.3,name=Apple Watch - 38mm"', os: osx, language: objective-c} + + - {osx_image: xcode9.2, env: 'SWFT=4.0 PLAT=iOS DST="OS=11.2,name=iPhone SE"', os: osx, language: objective-c} + - {osx_image: xcode9.2, env: 'SWFT=4.0 PLAT=tvOS DST="OS=11.2,name=Apple TV"', os: osx, language: objective-c} + - {osx_image: xcode9.2, env: 'SWFT=4.0 PLAT=macOS DST="arch=x86_64"', os: osx, language: objective-c} + - {osx_image: xcode9.2, env: 'SWFT=4.0 PLAT=watchOS DST="OS=4.2,name=Apple Watch - 38mm"', os: osx, language: objective-c} + + - {osx_image: xcode9.4, env: 'SWFT=4.1 PLAT=iOS DST="OS=8.4,name=iPhone 4s"', os: osx, language: objective-c} + - {osx_image: xcode9.4, env: 'SWFT=4.1 PLAT=iOS DST="OS=9.3,name=iPhone 5s"', os: osx, language: objective-c} + - {osx_image: xcode9.4, env: 'SWFT=4.1 PLAT=iOS DST="OS=10.3.1,name=iPhone SE"', os: osx, language: objective-c} + - {osx_image: xcode9.4, env: 'SWFT=4.1 PLAT=iOS DST="OS=11.4,name=iPhone SE" TEST=1', os: osx, language: objective-c} + - {osx_image: xcode9.3, env: 'SWFT=4.1 PLAT=tvOS DST="OS=9.2,name=Apple TV 1080p"', os: osx, language: objective-c} + - {osx_image: xcode9.3, env: 'SWFT=4.1 PLAT=tvOS DST="OS=10.2,name=Apple TV 1080p"', os: osx, language: objective-c} + - {osx_image: xcode9.4, env: 'SWFT=4.1 PLAT=tvOS DST="OS=11.4,name=Apple TV" TEST=1', os: osx, language: objective-c} + - {osx_image: xcode9.4, env: 'SWFT=4.1 PLAT=macOS DST="arch=x86_64" TEST=1', os: osx, language: objective-c} + - {osx_image: xcode9.3, env: 'SWFT=4.1 PLAT=watchOS DST="OS=2.2,name=Apple Watch - 38mm"', os: osx, language: objective-c} + - {osx_image: xcode9.3, env: 'SWFT=4.1 PLAT=watchOS DST="OS=3.2,name=Apple Watch - 38mm"', os: osx, language: objective-c} + - {osx_image: xcode9.4, env: 'SWFT=4.1 PLAT=watchOS DST="OS=4.3,name=Apple Watch - 38mm"', os: osx, language: objective-c} - - {osx_image: xcode9.2, env: 'SWFT=4.0 PLAT=iOS DST="OS=11.2,name=iPhone SE"', os: osx, language: objective-c} - - {osx_image: xcode9.2, env: 'SWFT=4.0 PLAT=tvOS DST="OS=11.2,name=Apple TV"', os: osx, language: objective-c} - - {osx_image: xcode9.2, env: 'SWFT=4.0 PLAT=macOS DST="arch=x86_64"', os: osx, language: objective-c} - - {osx_image: xcode9.2, env: 'SWFT=4.0 PLAT=watchOS DST="OS=4.2,name=Apple Watch - 38mm"', os: osx, language: objective-c} + - {os: linux, dist: trusty, sudo: required, language: generic, env: 'SWIFT_BUILD_VERSION=4 SWIFT_VERSION=4.0 TEST=1'} + - {os: linux, dist: trusty, sudo: required, language: generic, env: 'SWIFT_BUILD_VERSION=3 SWIFT_VERSION=DEVELOPMENT-SNAPSHOT-2018-06-20-a'} + - {os: linux, dist: trusty, sudo: required, language: generic, env: 'SWIFT_BUILD_VERSION=4 SWIFT_VERSION=DEVELOPMENT-SNAPSHOT-2018-06-20-a'} - - {osx_image: xcode9.4, env: 'SWFT=4.1 PLAT=iOS DST="OS=8.4,name=iPhone 4s"', os: osx, language: objective-c} - - {osx_image: xcode9.4, env: 'SWFT=4.1 PLAT=iOS DST="OS=9.3,name=iPhone 5s"', os: osx, language: objective-c} - - {osx_image: xcode9.4, env: 'SWFT=4.1 PLAT=iOS DST="OS=10.3.1,name=iPhone SE"', os: osx, language: objective-c} - - {osx_image: xcode9.4, env: 'SWFT=4.1 PLAT=iOS DST="OS=11.4,name=iPhone SE" TEST=1', os: osx, language: objective-c} - - {osx_image: xcode9.3, env: 'SWFT=4.1 PLAT=tvOS DST="OS=9.2,name=Apple TV 1080p"', os: osx, language: objective-c} - - {osx_image: xcode9.3, env: 'SWFT=4.1 PLAT=tvOS DST="OS=10.2,name=Apple TV 1080p"', os: osx, language: objective-c} - - {osx_image: xcode9.4, env: 'SWFT=4.1 PLAT=tvOS DST="OS=11.4,name=Apple TV" TEST=1', os: osx, language: objective-c} - - {osx_image: xcode9.4, env: 'SWFT=4.1 PLAT=macOS DST="arch=x86_64" TEST=1', os: osx, language: objective-c} - - {osx_image: xcode9.3, env: 'SWFT=4.1 PLAT=watchOS DST="OS=2.2,name=Apple Watch - 38mm"', os: osx, language: objective-c} - - {osx_image: xcode9.3, env: 'SWFT=4.1 PLAT=watchOS DST="OS=3.2,name=Apple Watch - 38mm"', os: osx, language: objective-c} - - {osx_image: xcode9.4, env: 'SWFT=4.1 PLAT=watchOS DST="OS=4.3,name=Apple Watch - 38mm"', os: osx, language: objective-c} - - - {osx_image: xcode10, env: 'SWFT=4.2 PLAT=iOS DST="OS=12.0,name=iPhone SE"', os: osx, language: objective-c} - - {osx_image: xcode10, env: 'SWFT=4.2 PLAT=tvOS DST="OS=12.0,name=Apple TV"', os: osx, language: objective-c} - - {osx_image: xcode10, env: 'SWFT=4.2 PLAT=macOS DST="arch=x86_64"', os: osx, language: objective-c} - - {osx_image: xcode10, env: 'SWFT=4.2 PLAT=watchOS DST="OS=5.0,name=Apple Watch Series 3 - 42mm"', os: osx, language: objective-c} - - # Swift 3.2 - - {os: linux, dist: trusty, sudo: required, language: generic, env: 'SWIFT_BUILD_VERSION=3 SWIFT_VERSION=4.0.3'} - # Swift 3.3 - - {os: linux, dist: trusty, sudo: required, language: generic, env: 'SWIFT_BUILD_VERSION=3 SWIFT_VERSION=4.1.2 TEST=1'} - # Swift 3.4 - - {os: linux, dist: trusty, sudo: required, language: generic, env: 'SWIFT_BUILD_VERSION=3 SWIFT_VERSION=4.2 TEST=1'} - # Swift 4.0 - - {os: linux, dist: trusty, sudo: required, language: generic, env: 'SWIFT_BUILD_VERSION=4 SWIFT_VERSION=4.0.3'} - # Swift 4.1 - - {os: linux, dist: trusty, sudo: required, language: generic, env: 'SWIFT_BUILD_VERSION=4 SWIFT_VERSION=4.1.2 TEST=1'} - # Swift 4.2 - - {os: linux, dist: trusty, sudo: required, language: generic, env: 'SWIFT_BUILD_VERSION=4 SWIFT_VERSION=4.2 TEST=1'} -cache: - - Tests/JS-A+/build - - Tests/JS-A+/node_modules before_install: - set -e; if [ "$TRAVIS_OS_NAME" == "linux" ]; then eval "$(curl -sL https://swiftenv.fuller.li/install.sh)"; - else - if [[ $POD_LINT == "1" || $PLAT == "macOS" ]]; then - mv .github/PromiseKit.podspec .; - gem install cocoapods --prerelease --version 1.6.0.beta.1; - fi; - if [ "$TEST" == "1" ]; then - npm install -g npm; - fi; fi install: - set -e; case $PLAT in - iOS|tvOS|macOS|watchOS) + iOS|tvOS|macOS|watchOS) xcodebuild -scheme PromiseKit -target PromiseKit SWIFT_VERSION=$SWFT -quiet -destination "$DST" SWIFT_TREAT_WARNINGS_AS_ERRORS=YES build; - if [ "$TEST" == "1" ]; then - set +e; - cd Tests/JS-A+; - set -e; - npm ci; - npm run build; - set +e; - cd ../..; - set -e; + if [[ TEST == "1" ]]; then xcodebuild -scheme PromiseKit -quiet -destination "$DST" build; fi;; *) - if [[ $POD_LINT != "1" ]]; then - swift build -Xswiftc -swift-version -Xswiftc $SWIFT_BUILD_VERSION; - fi;; + swift --version; + swift build -Xswiftc -swift-version -Xswiftc $SWIFT_BUILD_VERSION;; esac script: - set -e; case $PLAT in iOS|tvOS|macOS) - if [[ $TEST == "1" ]]; then + if [[ TEST == "1" ]]; then xcodebuild -scheme PromiseKit -destination "$DST" test -enableCodeCoverage YES; - fi; - if [[ $PLAT == "macOS" ]]; then - pod lib lint --subspec=PromiseKit/CorePromise --fail-fast --swift-version=$SWFT; fi;; watchOS) ;; *) - if [[ $TEST == "1" ]]; then + if [[ TEST == "1" ]]; then swift test -Xswiftc -swift-version -Xswiftc 4; - elif [[ $POD_LINT == "1" ]]; then - pod lib lint --fail-fast --verbose --swift-version=4.2 | ruby -e 'ARGF.each{ print "." }'; fi;; esac after_success: - if [ "$TRAVIS_OS_NAME" == "osx" && $POD_LINT != "1" ]; then - bash <(curl -s https://codecov.io/bash); - fi + - if [ "$TRAVIS_OS_NAME" == "osx" ]; then + bash <(curl -s https://codecov.io/bash); + fi From 70b055149a2c48212589f6c13aebc6dda9d857b1 Mon Sep 17 00:00:00 2001 From: Garth Snyder Date: Sat, 1 Dec 2018 23:29:01 -0800 Subject: [PATCH 10/26] Use existing v7 Travis config file --- .travis.yml | 141 ++++++++++++++++++++++++++++++++++++---------------- 1 file changed, 98 insertions(+), 43 deletions(-) diff --git a/.travis.yml b/.travis.yml index 995a68e17..f921a53b9 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,70 +1,125 @@ +# - `pod lib lint` weirdness explanation: https://github.com/CocoaPods/CocoaPods/issues/7123 +# - In Travis `cd foo` returns non-zero exit, hence `set +e` before changing directory +# - We need a newer version of npm in order to assemble our JS/A+ tests + +branches: + only: + - master matrix: include: - - {osx_image: xcode8.3, env: 'SWFT=3.1 PLAT=iOS DST="OS=10.3.1,name=iPhone SE"', os: osx, language: objective-c} - - {osx_image: xcode8.3, env: 'SWFT=3.1 PLAT=tvOS DST="OS=10.2,name=Apple TV 1080p"', os: osx, language: objective-c} - - {osx_image: xcode8.3, env: 'SWFT=3.1 PLAT=macOS DST="arch=x86_64"', os: osx, language: objective-c} - - {osx_image: xcode8.3, env: 'SWFT=3.1 PLAT=watchOS DST="OS=3.2,name=Apple Watch - 38mm"', os: osx, language: objective-c} - - - {osx_image: xcode9.2, env: 'SWFT=3.2 PLAT=iOS DST="OS=11.2,name=iPhone SE"', os: osx, language: objective-c} - - {osx_image: xcode9.2, env: 'SWFT=3.2 PLAT=tvOS DST="OS=11.2,name=Apple TV"', os: osx, language: objective-c} - - {osx_image: xcode9.2, env: 'SWFT=3.2 PLAT=macOS DST="arch=x86_64"', os: osx, language: objective-c} - - {osx_image: xcode9.2, env: 'SWFT=3.2 PLAT=watchOS DST="OS=4.2,name=Apple Watch - 38mm"', os: osx, language: objective-c} - - - {osx_image: xcode9.4, env: 'SWFT=3.3 PLAT=iOS DST="OS=11.4,name=iPhone SE"', os: osx, language: objective-c} - - {osx_image: xcode9.4, env: 'SWFT=3.3 PLAT=tvOS DST="OS=11.4,name=Apple TV"', os: osx, language: objective-c} - - {osx_image: xcode9.4, env: 'SWFT=3.3 PLAT=macOS DST="arch=x86_64"', os: osx, language: objective-c} - - {osx_image: xcode9.4, env: 'SWFT=3.3 PLAT=watchOS DST="OS=4.3,name=Apple Watch - 38mm"', os: osx, language: objective-c} - - - {osx_image: xcode9.2, env: 'SWFT=4.0 PLAT=iOS DST="OS=11.2,name=iPhone SE"', os: osx, language: objective-c} - - {osx_image: xcode9.2, env: 'SWFT=4.0 PLAT=tvOS DST="OS=11.2,name=Apple TV"', os: osx, language: objective-c} - - {osx_image: xcode9.2, env: 'SWFT=4.0 PLAT=macOS DST="arch=x86_64"', os: osx, language: objective-c} - - {osx_image: xcode9.2, env: 'SWFT=4.0 PLAT=watchOS DST="OS=4.2,name=Apple Watch - 38mm"', os: osx, language: objective-c} - - - {osx_image: xcode9.4, env: 'SWFT=4.1 PLAT=iOS DST="OS=8.4,name=iPhone 4s"', os: osx, language: objective-c} - - {osx_image: xcode9.4, env: 'SWFT=4.1 PLAT=iOS DST="OS=9.3,name=iPhone 5s"', os: osx, language: objective-c} - - {osx_image: xcode9.4, env: 'SWFT=4.1 PLAT=iOS DST="OS=10.3.1,name=iPhone SE"', os: osx, language: objective-c} - - {osx_image: xcode9.4, env: 'SWFT=4.1 PLAT=iOS DST="OS=11.4,name=iPhone SE" TEST=1', os: osx, language: objective-c} - - {osx_image: xcode9.3, env: 'SWFT=4.1 PLAT=tvOS DST="OS=9.2,name=Apple TV 1080p"', os: osx, language: objective-c} - - {osx_image: xcode9.3, env: 'SWFT=4.1 PLAT=tvOS DST="OS=10.2,name=Apple TV 1080p"', os: osx, language: objective-c} - - {osx_image: xcode9.4, env: 'SWFT=4.1 PLAT=tvOS DST="OS=11.4,name=Apple TV" TEST=1', os: osx, language: objective-c} - - {osx_image: xcode9.4, env: 'SWFT=4.1 PLAT=macOS DST="arch=x86_64" TEST=1', os: osx, language: objective-c} - - {osx_image: xcode9.3, env: 'SWFT=4.1 PLAT=watchOS DST="OS=2.2,name=Apple Watch - 38mm"', os: osx, language: objective-c} - - {osx_image: xcode9.3, env: 'SWFT=4.1 PLAT=watchOS DST="OS=3.2,name=Apple Watch - 38mm"', os: osx, language: objective-c} - - {osx_image: xcode9.4, env: 'SWFT=4.1 PLAT=watchOS DST="OS=4.3,name=Apple Watch - 38mm"', os: osx, language: objective-c} + - {osx_image: xcode10, env: 'POD_LINT=1', os: osx, language: objective-c} + + - {osx_image: xcode8.3, env: 'SWFT=3.1 PLAT=iOS DST="OS=10.3.1,name=iPhone SE"', os: osx, language: objective-c} + - {osx_image: xcode8.3, env: 'SWFT=3.1 PLAT=tvOS DST="OS=10.2,name=Apple TV 1080p"', os: osx, language: objective-c} + - {osx_image: xcode8.3, env: 'SWFT=3.1 PLAT=macOS DST="arch=x86_64"', os: osx, language: objective-c} + - {osx_image: xcode8.3, env: 'SWFT=3.1 PLAT=watchOS DST="OS=3.2,name=Apple Watch - 38mm"', os: osx, language: objective-c} + + - {osx_image: xcode9.2, env: 'SWFT=3.2 PLAT=iOS DST="OS=11.2,name=iPhone SE"', os: osx, language: objective-c} + - {osx_image: xcode9.2, env: 'SWFT=3.2 PLAT=tvOS DST="OS=11.2,name=Apple TV"', os: osx, language: objective-c} + - {osx_image: xcode9.2, env: 'SWFT=3.2 PLAT=macOS DST="arch=x86_64"', os: osx, language: objective-c} + - {osx_image: xcode9.2, env: 'SWFT=3.2 PLAT=watchOS DST="OS=4.2,name=Apple Watch - 38mm"', os: osx, language: objective-c} + + - {osx_image: xcode9.4, env: 'SWFT=3.3 PLAT=iOS DST="OS=11.4,name=iPhone SE"', os: osx, language: objective-c} + - {osx_image: xcode9.4, env: 'SWFT=3.3 PLAT=tvOS DST="OS=11.4,name=Apple TV"', os: osx, language: objective-c} + - {osx_image: xcode9.4, env: 'SWFT=3.3 PLAT=macOS DST="arch=x86_64"', os: osx, language: objective-c} + - {osx_image: xcode9.4, env: 'SWFT=3.3 PLAT=watchOS DST="OS=4.3,name=Apple Watch - 38mm"', os: osx, language: objective-c} + + - {osx_image: xcode10, env: 'SWFT=3.4 PLAT=iOS DST="OS=12.0,name=iPhone SE"', os: osx, language: objective-c} + - {osx_image: xcode10, env: 'SWFT=3.4 PLAT=tvOS DST="OS=12.0,name=Apple TV"', os: osx, language: objective-c} + - {osx_image: xcode10, env: 'SWFT=3.4 PLAT=macOS DST="arch=x86_64"', os: osx, language: objective-c} + - {osx_image: xcode10, env: 'SWFT=3.4 PLAT=watchOS DST="OS=5.0,name=Apple Watch Series 3 - 42mm"', os: osx, language: objective-c} - - {os: linux, dist: trusty, sudo: required, language: generic, env: 'SWIFT_BUILD_VERSION=4 SWIFT_VERSION=4.0 TEST=1'} - - {os: linux, dist: trusty, sudo: required, language: generic, env: 'SWIFT_BUILD_VERSION=3 SWIFT_VERSION=DEVELOPMENT-SNAPSHOT-2018-06-20-a'} - - {os: linux, dist: trusty, sudo: required, language: generic, env: 'SWIFT_BUILD_VERSION=4 SWIFT_VERSION=DEVELOPMENT-SNAPSHOT-2018-06-20-a'} + - {osx_image: xcode9.2, env: 'SWFT=4.0 PLAT=iOS DST="OS=11.2,name=iPhone SE"', os: osx, language: objective-c} + - {osx_image: xcode9.2, env: 'SWFT=4.0 PLAT=tvOS DST="OS=11.2,name=Apple TV"', os: osx, language: objective-c} + - {osx_image: xcode9.2, env: 'SWFT=4.0 PLAT=macOS DST="arch=x86_64"', os: osx, language: objective-c} + - {osx_image: xcode9.2, env: 'SWFT=4.0 PLAT=watchOS DST="OS=4.2,name=Apple Watch - 38mm"', os: osx, language: objective-c} + - {osx_image: xcode9.4, env: 'SWFT=4.1 PLAT=iOS DST="OS=8.4,name=iPhone 4s"', os: osx, language: objective-c} + - {osx_image: xcode9.4, env: 'SWFT=4.1 PLAT=iOS DST="OS=9.3,name=iPhone 5s"', os: osx, language: objective-c} + - {osx_image: xcode9.4, env: 'SWFT=4.1 PLAT=iOS DST="OS=10.3.1,name=iPhone SE"', os: osx, language: objective-c} + - {osx_image: xcode9.4, env: 'SWFT=4.1 PLAT=iOS DST="OS=11.4,name=iPhone SE" TEST=1', os: osx, language: objective-c} + - {osx_image: xcode9.3, env: 'SWFT=4.1 PLAT=tvOS DST="OS=9.2,name=Apple TV 1080p"', os: osx, language: objective-c} + - {osx_image: xcode9.3, env: 'SWFT=4.1 PLAT=tvOS DST="OS=10.2,name=Apple TV 1080p"', os: osx, language: objective-c} + - {osx_image: xcode9.4, env: 'SWFT=4.1 PLAT=tvOS DST="OS=11.4,name=Apple TV" TEST=1', os: osx, language: objective-c} + - {osx_image: xcode9.4, env: 'SWFT=4.1 PLAT=macOS DST="arch=x86_64" TEST=1', os: osx, language: objective-c} + - {osx_image: xcode9.3, env: 'SWFT=4.1 PLAT=watchOS DST="OS=2.2,name=Apple Watch - 38mm"', os: osx, language: objective-c} + - {osx_image: xcode9.3, env: 'SWFT=4.1 PLAT=watchOS DST="OS=3.2,name=Apple Watch - 38mm"', os: osx, language: objective-c} + - {osx_image: xcode9.4, env: 'SWFT=4.1 PLAT=watchOS DST="OS=4.3,name=Apple Watch - 38mm"', os: osx, language: objective-c} + + - {osx_image: xcode10, env: 'SWFT=4.2 PLAT=iOS DST="OS=12.0,name=iPhone SE"', os: osx, language: objective-c} + - {osx_image: xcode10, env: 'SWFT=4.2 PLAT=tvOS DST="OS=12.0,name=Apple TV"', os: osx, language: objective-c} + - {osx_image: xcode10, env: 'SWFT=4.2 PLAT=macOS DST="arch=x86_64"', os: osx, language: objective-c} + - {osx_image: xcode10, env: 'SWFT=4.2 PLAT=watchOS DST="OS=5.0,name=Apple Watch Series 3 - 42mm"', os: osx, language: objective-c} + + # Swift 3.2 + - {os: linux, dist: trusty, sudo: required, language: generic, env: 'SWIFT_BUILD_VERSION=3 SWIFT_VERSION=4.0.3'} + # Swift 3.3 + - {os: linux, dist: trusty, sudo: required, language: generic, env: 'SWIFT_BUILD_VERSION=3 SWIFT_VERSION=4.1.2 TEST=1'} + # Swift 3.4 + - {os: linux, dist: trusty, sudo: required, language: generic, env: 'SWIFT_BUILD_VERSION=3 SWIFT_VERSION=4.2 TEST=1'} + # Swift 4.0 + - {os: linux, dist: trusty, sudo: required, language: generic, env: 'SWIFT_BUILD_VERSION=4 SWIFT_VERSION=4.0.3'} + # Swift 4.1 + - {os: linux, dist: trusty, sudo: required, language: generic, env: 'SWIFT_BUILD_VERSION=4 SWIFT_VERSION=4.1.2 TEST=1'} + # Swift 4.2 + - {os: linux, dist: trusty, sudo: required, language: generic, env: 'SWIFT_BUILD_VERSION=4 SWIFT_VERSION=4.2 TEST=1'} +cache: + - Tests/JS-A+/build + - Tests/JS-A+/node_modules before_install: + set -e; if [ "$TRAVIS_OS_NAME" == "linux" ]; then eval "$(curl -sL https://swiftenv.fuller.li/install.sh)"; + else + if [[ $POD_LINT == "1" || $PLAT == "macOS" ]]; then + mv .github/PromiseKit.podspec .; + gem install cocoapods --prerelease --version 1.6.0.beta.1; + fi; + if [ "$TEST" == "1" ]; then + npm install -g npm; + fi; fi install: + set -e; case $PLAT in - iOS|tvOS|macOS|watchOS) + iOS|tvOS|macOS|watchOS) xcodebuild -scheme PromiseKit -target PromiseKit SWIFT_VERSION=$SWFT -quiet -destination "$DST" SWIFT_TREAT_WARNINGS_AS_ERRORS=YES build; - if [[ TEST == "1" ]]; then + if [ "$TEST" == "1" ]; then + set +e; + cd Tests/JS-A+; + set -e; + npm ci; + npm run build; + set +e; + cd ../..; + set -e; xcodebuild -scheme PromiseKit -quiet -destination "$DST" build; fi;; *) - swift --version; - swift build -Xswiftc -swift-version -Xswiftc $SWIFT_BUILD_VERSION;; + if [[ $POD_LINT != "1" ]]; then + swift build -Xswiftc -swift-version -Xswiftc $SWIFT_BUILD_VERSION; + fi;; esac script: + set -e; case $PLAT in iOS|tvOS|macOS) - if [[ TEST == "1" ]]; then + if [[ $TEST == "1" ]]; then xcodebuild -scheme PromiseKit -destination "$DST" test -enableCodeCoverage YES; + fi; + if [[ $PLAT == "macOS" ]]; then + pod lib lint --subspec=PromiseKit/CorePromise --fail-fast --swift-version=$SWFT; fi;; watchOS) ;; *) - if [[ TEST == "1" ]]; then + if [[ $TEST == "1" ]]; then swift test -Xswiftc -swift-version -Xswiftc 4; + elif [[ $POD_LINT == "1" ]]; then + pod lib lint --fail-fast --verbose --swift-version=4.2 | ruby -e 'ARGF.each{ print "." }'; fi;; esac after_success: - - if [ "$TRAVIS_OS_NAME" == "osx" ]; then - bash <(curl -s https://codecov.io/bash); - fi + if [ "$TRAVIS_OS_NAME" == "osx" && $POD_LINT != "1" ]; then + bash <(curl -s https://codecov.io/bash); + fi From f8b0eadd302607ba4be97a846ca44f7968e3f28d Mon Sep 17 00:00:00 2001 From: Garth Snyder Date: Sun, 17 Jun 2018 23:39:52 -0700 Subject: [PATCH 11/26] First draft of Dispatcher conversion --- PromiseKit.xcodeproj/project.pbxproj | 4 + Sources/Box.swift | 12 +- Sources/Catchable.swift | 47 ++- Sources/Configuration.swift | 10 +- Sources/Deprecations.swift | 10 +- Sources/Dispatcher.swift | 475 +++++++++++++++++++++++++++ Sources/Guarantee.swift | 42 ++- Sources/Promise.swift | 27 ++ Sources/Thenable.swift | 60 ++-- 9 files changed, 608 insertions(+), 79 deletions(-) create mode 100644 Sources/Dispatcher.swift diff --git a/PromiseKit.xcodeproj/project.pbxproj b/PromiseKit.xcodeproj/project.pbxproj index bb625f682..aa532b751 100644 --- a/PromiseKit.xcodeproj/project.pbxproj +++ b/PromiseKit.xcodeproj/project.pbxproj @@ -80,6 +80,7 @@ 63CF6D80203CD19200EC8927 /* ThenableTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 63CF6D7F203CD19200EC8927 /* ThenableTests.swift */; }; 63D9B2EF203385FD0075C00B /* race.m in Sources */ = {isa = PBXBuildFile; fileRef = 63D9B2EE203385FD0075C00B /* race.m */; }; 63D9B2F120338D5D0075C00B /* Deprecations.swift in Sources */ = {isa = PBXBuildFile; fileRef = 63D9B2F020338D5D0075C00B /* Deprecations.swift */; }; + BB2524DE20D729A60010F7B0 /* Dispatcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB2524DD20D729A60010F7B0 /* Dispatcher.swift */; }; C013F7382048E3B6006B57B1 /* MockNodeEnvironment.swift in Sources */ = {isa = PBXBuildFile; fileRef = C013F7372048E3B6006B57B1 /* MockNodeEnvironment.swift */; }; C013F73A2049076A006B57B1 /* JSPromise.swift in Sources */ = {isa = PBXBuildFile; fileRef = C013F7392049076A006B57B1 /* JSPromise.swift */; }; C013F73C20494291006B57B1 /* JSAdapter.swift in Sources */ = {isa = PBXBuildFile; fileRef = C013F73B20494291006B57B1 /* JSAdapter.swift */; }; @@ -223,6 +224,7 @@ 63CF6D7F203CD19200EC8927 /* ThenableTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThenableTests.swift; sourceTree = ""; }; 63D9B2EE203385FD0075C00B /* race.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = race.m; path = Sources/race.m; sourceTree = ""; }; 63D9B2F020338D5D0075C00B /* Deprecations.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = Deprecations.swift; path = Sources/Deprecations.swift; sourceTree = ""; }; + BB2524DD20D729A60010F7B0 /* Dispatcher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = Dispatcher.swift; path = Sources/Dispatcher.swift; sourceTree = ""; }; C013F7372048E3B6006B57B1 /* MockNodeEnvironment.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = MockNodeEnvironment.swift; path = "Tests/JS-A+/MockNodeEnvironment.swift"; sourceTree = ""; }; C013F7392049076A006B57B1 /* JSPromise.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = JSPromise.swift; path = "Tests/JS-A+/JSPromise.swift"; sourceTree = ""; }; C013F73B20494291006B57B1 /* JSAdapter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = JSAdapter.swift; path = "Tests/JS-A+/JSAdapter.swift"; sourceTree = ""; }; @@ -443,6 +445,7 @@ 63B18AEB1F2D205C00B79E37 /* CustomStringConvertible.swift */, 63D9B2F020338D5D0075C00B /* Deprecations.swift */, 085B96BE21A9B37C00E5E22F /* LogEvent.swift */, + BB2524DD20D729A60010F7B0 /* Dispatcher.swift */, ); name = Sources.swift; sourceTree = ""; @@ -750,6 +753,7 @@ 085B96BF21A9B37C00E5E22F /* LogEvent.swift in Sources */, 6330B5E11F2E991200D60528 /* Configuration.swift in Sources */, 63B912AA1F1D7B1300D49110 /* firstly.swift in Sources */, + BB2524DE20D729A60010F7B0 /* Dispatcher.swift in Sources */, 636A29211F1C1716001229C2 /* Thenable.swift in Sources */, 632FBBE31F33B273008F8FBB /* Catchable.swift in Sources */, 63B0AC851D595E6300FA21D9 /* dispatch_promise.m in Sources */, diff --git a/Sources/Box.swift b/Sources/Box.swift index a0a80152b..f17caf5ae 100644 --- a/Sources/Box.swift +++ b/Sources/Box.swift @@ -84,18 +84,14 @@ final class EmptyBox: Box { } -extension Optional where Wrapped: DispatchQueue { +extension Optional: Dispatcher where Wrapped: Dispatcher { @inline(__always) - func async(flags: DispatchWorkItemFlags?, _ body: @escaping() -> Void) { + public func async(execute body: @escaping () -> Void) { switch self { case .none: body() - case .some(let q): - if let flags = flags { - q.async(flags: flags, execute: body) - } else { - q.async(execute: body) - } + case .some(let dispatcher): + dispatcher.async(execute: body) } } } diff --git a/Sources/Catchable.swift b/Sources/Catchable.swift index 1b640ff22..12491108d 100644 --- a/Sources/Catchable.swift +++ b/Sources/Catchable.swift @@ -14,14 +14,14 @@ public extension CatchMixin { of a chain. Often utility promises will not have a catch, instead delegating the error handling to the caller. - - Parameter on: The queue to which the provided closure dispatches. + - Parameter on: The dispatcher that executes the provided closure. - Parameter policy: The default policy does not execute your handler for cancellation errors. - Parameter execute: The handler to execute if this promise is rejected. - Returns: A promise finalizer. - SeeAlso: [Cancellation](http://promisekit.org/docs/) */ @discardableResult - func `catch`(on: DispatchQueue? = conf.Q.return, flags: DispatchWorkItemFlags? = nil, policy: CatchPolicy = conf.catchPolicy, _ body: @escaping(Error) -> Void) -> PMKFinalizer { + func `catch`(on: Dispatcher? = conf.D.return, policy: CatchPolicy = conf.catchPolicy, _ body: @escaping(Error) -> Void) -> PMKFinalizer { let finalizer = PMKFinalizer() pipe { switch $0 { @@ -29,7 +29,7 @@ public extension CatchMixin { guard policy == .allErrors || !error.isCancelled else { fallthrough } - on.async(flags: flags) { + on.async { body(error) finalizer.pending.resolve(()) } @@ -45,8 +45,8 @@ public class PMKFinalizer { let pending = Guarantee.pending() /// `finally` is the same as `ensure`, but it is not chainable - public func finally(on: DispatchQueue? = conf.Q.return, flags: DispatchWorkItemFlags? = nil, _ body: @escaping () -> Void) { - pending.guarantee.done(on: on, flags: flags) { + public func finally(on: Dispatcher? = conf.D.return, _ body: @escaping () -> Void) { + pending.guarantee.done(on: on) { body() } } @@ -68,11 +68,11 @@ public extension CatchMixin { return .value(CLLocation.chicago) } - - Parameter on: The queue to which the provided closure dispatches. + - Parameter on: The dispatcher that executes the provided closure. - Parameter body: The handler to execute if this promise is rejected. - SeeAlso: [Cancellation](http://promisekit.org/docs/) */ - func recover(on: DispatchQueue? = conf.Q.map, flags: DispatchWorkItemFlags? = nil, policy: CatchPolicy = conf.catchPolicy, _ body: @escaping(Error) throws -> U) -> Promise where U.T == T { + func recover(on: Dispatcher? = conf.D.map, policy: CatchPolicy = conf.catchPolicy, _ body: @escaping(Error) throws -> U) -> Promise where U.T == T { let rp = Promise(.pending) pipe { switch $0 { @@ -80,7 +80,7 @@ public extension CatchMixin { rp.box.seal(.fulfilled(value)) case .rejected(let error): if policy == .allErrors || !error.isCancelled { - on.async(flags: flags) { + on.async { do { let rv = try body(error) guard rv !== rp else { throw PMKError.returnedSelf } @@ -101,19 +101,19 @@ public extension CatchMixin { The provided closure executes when this promise rejects. This variant of `recover` requires the handler to return a Guarantee, thus it returns a Guarantee itself and your closure cannot `throw`. - Note it is logically impossible for this to take a `catchPolicy`, thus `allErrors` are handled. - - Parameter on: The queue to which the provided closure dispatches. + - Parameter on: The dispatcher that executes the provided closure. - Parameter body: The handler to execute if this promise is rejected. - SeeAlso: [Cancellation](http://promisekit.org/docs/) */ @discardableResult - func recover(on: DispatchQueue? = conf.Q.map, flags: DispatchWorkItemFlags? = nil, _ body: @escaping(Error) -> Guarantee) -> Guarantee { + func recover(on: Dispatcher? = conf.D.map, _ body: @escaping(Error) -> Guarantee) -> Guarantee { let rg = Guarantee(.pending) pipe { switch $0 { case .fulfilled(let value): rg.box.seal(value) case .rejected(let error): - on.async(flags: flags) { + on.async { body(error).pipe(to: rg.box.seal) } } @@ -134,14 +134,14 @@ public extension CatchMixin { //… } - - Parameter on: The queue to which the provided closure dispatches. + - Parameter on: The dispatcher that executes the provided closure. - Parameter body: The closure that executes when this promise resolves. - Returns: A new promise, resolved with this promise’s resolution. */ - func ensure(on: DispatchQueue? = conf.Q.return, flags: DispatchWorkItemFlags? = nil, _ body: @escaping () -> Void) -> Promise { + func ensure(on: Dispatcher? = conf.D.return, _ body: @escaping () -> Void) -> Promise { let rp = Promise(.pending) pipe { result in - on.async(flags: flags) { + on.async { body() rp.box.seal(result) } @@ -163,14 +163,14 @@ public extension CatchMixin { //… } - - Parameter on: The queue to which the provided closure dispatches. + - Parameter on: The dispatcher that executes the provided closure. - Parameter body: The closure that executes when this promise resolves. - Returns: A new promise, resolved with this promise’s resolution. */ - func ensureThen(on: DispatchQueue? = conf.Q.return, flags: DispatchWorkItemFlags? = nil, _ body: @escaping () -> Guarantee) -> Promise { + func ensureThen(on: Dispatcher? = conf.D.return, _ body: @escaping () -> Guarantee) -> Promise { let rp = Promise(.pending) pipe { result in - on.async(flags: flags) { + on.async { body().done { rp.box.seal(result) } @@ -180,7 +180,6 @@ public extension CatchMixin { } - /** 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. @@ -201,19 +200,19 @@ public extension CatchMixin where T == Void { This variant of `recover` is specialized for `Void` promises and de-errors your chain returning a `Guarantee`, thus you cannot `throw` and you must handle all errors including cancellation. - - Parameter on: The queue to which the provided closure dispatches. + - Parameter on: The dispatcher that executes the provided closure. - Parameter body: The handler to execute if this promise is rejected. - SeeAlso: [Cancellation](http://promisekit.org/docs/) */ @discardableResult - func recover(on: DispatchQueue? = conf.Q.map, flags: DispatchWorkItemFlags? = nil, _ body: @escaping(Error) -> Void) -> Guarantee { + func recover(on: Dispatcher? = conf.D.map, flags: DispatchWorkItemFlags? = nil, _ body: @escaping(Error) -> Void) -> Guarantee { let rg = Guarantee(.pending) pipe { switch $0 { case .fulfilled: rg.box.seal(()) case .rejected(let error): - on.async(flags: flags) { + on.async { body(error) rg.box.seal(()) } @@ -227,11 +226,11 @@ public extension CatchMixin where T == Void { This variant of `recover` ensures that no error is thrown from the handler and allows specifying a catch policy. - - Parameter on: The queue to which the provided closure dispatches. + - Parameter on: The dispatcher that executes the provided closure. - Parameter body: The handler to execute if this promise is rejected. - SeeAlso: [Cancellation](http://promisekit.org/docs/) */ - func recover(on: DispatchQueue? = conf.Q.map, flags: DispatchWorkItemFlags? = nil, policy: CatchPolicy = conf.catchPolicy, _ body: @escaping(Error) throws -> Void) -> Promise { + func recover(on: Dispatcher? = conf.D.map, policy: CatchPolicy = conf.catchPolicy, _ body: @escaping(Error) throws -> Void) -> Promise { let rg = Promise(.pending) pipe { switch $0 { @@ -239,7 +238,7 @@ public extension CatchMixin where T == Void { rg.box.seal(.fulfilled(())) case .rejected(let error): if policy == .allErrors || !error.isCancelled { - on.async(flags: flags) { + on.async { do { rg.box.seal(.fulfilled(try body(error))) } catch { diff --git a/Sources/Configuration.swift b/Sources/Configuration.swift index 503451266..b53c810be 100644 --- a/Sources/Configuration.swift +++ b/Sources/Configuration.swift @@ -8,8 +8,14 @@ import Dispatch 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 { - /// The default queues that promises handlers dispatch to - public var Q: (map: DispatchQueue?, return: DispatchQueue?) = (map: DispatchQueue.main, return: DispatchQueue.main) + /// Backward compatibility: default DispatchQueues that promise handlers dispatch to + public var Q: (map: DispatchQueue?, return: DispatchQueue?) { + get { return (map: D.map as? DispatchQueue, return: D.return as? DispatchQueue) } + set { D = (map: newValue.map, return: newValue.return) } + } + + /// The default Dispatchers that promise handlers dispatch to + public var D: (map: Dispatcher?, return: Dispatcher?) = (map: DispatchQueue.main, return: DispatchQueue.main) /// The default catch-policy for all `catch` and `resolve` public var catchPolicy = CatchPolicy.allErrorsExceptCancellation diff --git a/Sources/Deprecations.swift b/Sources/Deprecations.swift index ac4eb364b..27300a502 100644 --- a/Sources/Deprecations.swift +++ b/Sources/Deprecations.swift @@ -46,7 +46,7 @@ public extension Thenable { #if PMKFullDeprecations /// disabled due to ambiguity with the other `.flatMap` @available(*, deprecated: 6.1, message: "See: `compactMap`") - func flatMap(on: DispatchQueue? = conf.Q.map, _ transform: @escaping(T) throws -> U?) -> Promise { + func flatMap(on: DispatchQueue? = .pmkDefault, _ transform: @escaping(T) throws -> U?) -> Promise { return compactMap(on: on, transform) } #endif @@ -56,19 +56,19 @@ public extension Thenable where T: Sequence { #if PMKFullDeprecations /// disabled due to ambiguity with the other `.map` @available(*, deprecated, message: "See: `mapValues`") - func map(on: DispatchQueue? = conf.Q.map, _ transform: @escaping(T.Iterator.Element) throws -> U) -> Promise<[U]> { + func map(on: DispatchQueue? = .pmkDefault, _ transform: @escaping(T.Iterator.Element) throws -> U) -> Promise<[U]> { return mapValues(on: on, transform) } /// disabled due to ambiguity with the other `.flatMap` @available(*, deprecated, message: "See: `flatMapValues`") - func flatMap(on: DispatchQueue? = conf.Q.map, _ transform: @escaping(T.Iterator.Element) throws -> U) -> Promise<[U.Iterator.Element]> { + func flatMap(on: DispatchQueue? = .pmkDefault, _ transform: @escaping(T.Iterator.Element) throws -> U) -> Promise<[U.Iterator.Element]> { return flatMapValues(on: on, transform) } #endif @available(*, deprecated, message: "See: `filterValues`") - func filter(on: DispatchQueue? = conf.Q.map, test: @escaping (T.Iterator.Element) -> Bool) -> Promise<[T.Iterator.Element]> { + func filter(on: DispatchQueue? = .pmkDefault, test: @escaping (T.Iterator.Element) -> Bool) -> Promise<[T.Iterator.Element]> { return filterValues(on: on, test) } } @@ -87,7 +87,7 @@ public extension Thenable where T: Collection { public extension Thenable where T: Sequence, T.Iterator.Element: Comparable { @available(*, deprecated, message: "See: `sortedValues`") - func sorted(on: DispatchQueue? = conf.Q.map) -> Promise<[T.Iterator.Element]> { + func sorted(on: DispatchQueue? = .pmkDefault) -> Promise<[T.Iterator.Element]> { return sortedValues(on: on) } } diff --git a/Sources/Dispatcher.swift b/Sources/Dispatcher.swift new file mode 100644 index 000000000..0750f1e54 --- /dev/null +++ b/Sources/Dispatcher.swift @@ -0,0 +1,475 @@ +import Dispatch + +public protocol Dispatcher { + func async(execute work: @escaping () -> Void) +} + +public struct DispatchQueueDispatcher: Dispatcher { + + let queue: DispatchQueue + let flags: DispatchWorkItemFlags + + init(queue: DispatchQueue, flags: DispatchWorkItemFlags) { + self.queue = queue + self.flags = flags + } + + public func async(execute work: @escaping () -> Void) { + queue.async(flags: flags, execute: work) + } + +} + +extension DispatchQueue: Dispatcher { + + /// Explicit declaration required; actual function signature is not identical to protocol + public func async(execute work: @escaping () -> Void) { + async(execute: work) + } + +} + +/// 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. + +public extension DispatchQueue { + static var pmkDefault = DispatchQueue(label: "org.promisekit.sentinel") +} + +extension DispatchQueue { + + public func asDispatcher(withFlags flags: DispatchWorkItemFlags? = nil) -> Dispatcher { + if let flags = flags { + return DispatchQueueDispatcher(queue: self, flags: flags) + } + return self + } + +} + +/// 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. +/// +/// TODO: should conf.D = nil turn off dispatching even if explicit dispatch arguments are given? + +fileprivate func selectDispatcher(given: DispatchQueue?, configured: Dispatcher?, flags: DispatchWorkItemFlags?) -> Dispatcher? { + guard let given = given else { + if flags != nil { + print("PromiseKit: warning: nil DispatchQueue specified, but DispatchWorkItemFlags were also supplied (ignored)") + } + return nil + } + if given !== DispatchQueue.pmkDefault { + return given.asDispatcher(withFlags: flags) + } else if let flags = flags, let configured = configured as? DispatchQueue { + return configured.asDispatcher(withFlags: flags) + } else if flags != nil && configured != nil { + print("PromiseKit: warning: DispatchWorkItemFlags flags specified, but default dispatcher is not a DispatchQueue (ignored)") + } + return configured +} + +/// Backward compatibility for DispatchQueues in public API + +public extension Guarantee { + + @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) + return done(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) + return map(on: dispatcher, body) + } + + @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) + return then(on: dispatcher, body) + } + +} + +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) + return thenMap(on: dispatcher, transform) + } + +} + +public extension Thenable { + + /** + The provided closure executes when this promise resolves. + + This allows chaining promises. The promise returned by the provided closure is resolved before the promise returned by this closure resolves. + + - Parameter on: The queue to which the provided closure dispatches. + - 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. For example: + + firstly { + URLSession.shared.dataTask(.promise, with: url1) + }.then { response in + transform(data: response.data) + }.done { transformation in + //… + } + */ + 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) + return then(on: dispatcher, body) + } + + /** + The provided closure is executed when this promise is resolved. + + This is like `then` but it requires the closure to return a non-promise. + + - Parameter on: The queue to which the provided closure dispatches. + - Parameter transform: The closure that is executed when this Promise is fulfilled. It must return a non-promise. + - Returns: A new promise that is resolved with the value returned from the provided closure. For example: + + firstly { + URLSession.shared.dataTask(.promise, with: url1) + }.map { response in + response.data.length + }.done { length in + //… + } + */ + 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) + return map(on: dispatcher, transform) + } + + /** + The provided closure is executed when this promise is resolved. + + In your closure return an `Optional`, if you return `nil` the resulting promise is rejected with `PMKError.compactMap`, otherwise the promise is fulfilled with the unwrapped value. + + firstly { + URLSession.shared.dataTask(.promise, with: url) + }.compactMap { + try JSONSerialization.jsonObject(with: $0.data) as? [String: String] + }.done { dictionary in + //… + }.catch { + // 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) + return compactMap(on: dispatcher, transform) + } + + /** + The provided closure is executed when this promise is resolved. + + Equivalent to `map { x -> Void in`, but since we force the `Void` return Swift + is happier and gives you less hassle about your closure’s qualification. + + - Parameter on: The queue to which the provided closure dispatches. + - Parameter body: The closure that is executed when this Promise is fulfilled. + - Returns: A new promise fulfilled as `Void`. + + firstly { + URLSession.shared.dataTask(.promise, with: url) + }.done { response in + print(response.data) + } + */ + func done(on: DispatchQueue? = .pmkDefault, flags: DispatchWorkItemFlags? = nil, _ body: @escaping(T) throws -> Void) -> Promise { + let dispatcher = selectDispatcher(given: on, configured: conf.D.return, flags: flags) + return done(on: dispatcher, body) + } + + /** + The provided closure is executed when this promise is resolved. + + This is like `done` but it returns the same value that the handler is fed. + `get` immutably accesses the fulfilled value; the returned Promise maintains that value. + + - Parameter on: The queue to which the provided closure dispatches. + - 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. For example: + + firstly { + .value(1) + }.get { foo in + print(foo, " is 1") + }.done { foo in + print(foo, " is 1") + }.done { foo in + print(foo, " is Void") + } + */ + func get(on: DispatchQueue? = .pmkDefault, flags: DispatchWorkItemFlags? = nil, _ body: @escaping (T) throws -> Void) -> Promise { + let dispatcher = selectDispatcher(given: on, configured: conf.D.return, flags: flags) + return get(on: dispatcher, body) + } +} + +public extension Thenable where T: Sequence { + /** + `Promise<[T]>` => `T` -> `U` => `Promise<[U]>` + + firstly { + .value([1,2,3]) + }.mapValues { integer in + integer * 2 + }.done { + // $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) + return mapValues(on: dispatcher, transform) + } + + /** + `Promise<[T]>` => `T` -> `[U]` => `Promise<[U]>` + + firstly { + .value([1,2,3]) + }.flatMapValues { integer in + [integer, integer] + }.done { + // $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) + return flatMapValues(on: dispatcher, transform) + } + + /** + `Promise<[T]>` => `T` -> `U?` => `Promise<[U]>` + + firstly { + .value(["1","2","a","3"]) + }.compactMapValues { + Int($0) + }.done { + // $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) + return compactMapValues(on: dispatcher, transform) + } + + /** + `Promise<[T]>` => `T` -> `Promise` => `Promise<[U]>` + + firstly { + .value([1,2,3]) + }.thenMap { integer in + .value(integer * 2) + }.done { + // $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) + return thenMap(on: dispatcher, transform) + } + + /** + `Promise<[T]>` => `T` -> `Promise<[U]>` => `Promise<[U]>` + + firstly { + .value([1,2,3]) + }.thenFlatMap { integer in + .value([integer, integer]) + }.done { + // $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) + return thenFlatMap(on: dispatcher, transform) + } + + /** + `Promise<[T]>` => `T` -> Bool => `Promise<[U]>` + + firstly { + .value([1,2,3]) + }.filterValues { + $0 > 1 + }.done { + // $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) + 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) + 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) + return sortedValues(on: dispatcher) + } +} + +public extension CatchMixin { + /** + The provided closure executes when this promise rejects. + + Rejecting a promise cascades: rejecting all subsequent promises (unless + recover is invoked) thus you will typically place your catch at the end + of a chain. Often utility promises will not have a catch, instead + delegating the error handling to the caller. + + - Parameter on: The queue to which the provided closure dispatches. + - Parameter policy: The default policy does not execute your handler for cancellation errors. + - Parameter execute: The handler to execute if this promise is rejected. + - Returns: A promise finalizer. + - SeeAlso: [Cancellation](http://promisekit.org/docs/) + */ + @discardableResult + func `catch`(on: DispatchQueue? = .pmkDefault, flags: DispatchWorkItemFlags? = nil, policy: CatchPolicy = conf.catchPolicy, _ body: @escaping(Error) -> Void) -> PMKFinalizer { + let dispatcher = selectDispatcher(given: on, configured: conf.D.return, flags: flags) + return `catch`(on: dispatcher, policy: policy, body) + } + + /** + The provided closure executes when this promise rejects. + + Unlike `catch`, `recover` continues the chain. + Use `recover` in circumstances where recovering the chain from certain errors is a possibility. For example: + + firstly { + CLLocationManager.requestLocation() + }.recover { error in + guard error == CLError.unknownLocation else { throw error } + return .value(CLLocation.chicago) + } + + - Parameter on: The queue to which the provided closure dispatches. + - Parameter body: The handler to execute if this promise is rejected. + - SeeAlso: [Cancellation](http://promisekit.org/docs/) + */ + func recover(on: DispatchQueue? = conf.Q.map, flags: DispatchWorkItemFlags? = nil, policy: CatchPolicy = conf.catchPolicy, _ body: @escaping(Error) throws -> U) -> Promise where U.T == T { + let dispatcher = selectDispatcher(given: on, configured: conf.D.map, flags: flags) + return recover(on: dispatcher, policy: policy, body) + } + + /** + The provided closure executes when this promise rejects. + This variant of `recover` requires the handler to return a Guarantee, thus it returns a Guarantee itself and your closure cannot `throw`. + - Note it is logically impossible for this to take a `catchPolicy`, thus `allErrors` are handled. + - Parameter on: The queue to which the provided closure dispatches. + - Parameter body: The handler to execute if this promise is rejected. + - SeeAlso: [Cancellation](http://promisekit.org/docs/) + */ + @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) + return recover(on: dispatcher, body) + } + + /** + The provided closure executes when this promise resolves, whether it rejects or not. + + firstly { + UIApplication.shared.networkActivityIndicatorVisible = true + }.done { + //… + }.ensure { + UIApplication.shared.networkActivityIndicatorVisible = false + }.catch { + //… + } + + - Parameter on: The queue to which the provided closure dispatches. + - 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) -> Promise { + let dispatcher = selectDispatcher(given: on, configured: conf.D.return, flags: flags) + return ensure(on: dispatcher, body) + } + + /** + The provided closure executes when this promise resolves, whether it rejects or not. + The chain waits on the returned `Guarantee`. + + firstly { + setup() + }.done { + //… + }.ensureThen { + teardown() // -> Guarante + }.catch { + //… + } + + - Parameter on: The queue to which the provided closure dispatches. + - 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 () -> Guarantee) -> Promise { + let dispatcher = selectDispatcher(given: on, configured: conf.D.return, flags: flags) + return ensureThen(on: dispatcher, body) + } +} + +public extension PMKFinalizer { + /// `finally` is the same as `ensure`, but it is not chainable + public func finally(on: DispatchQueue? = .pmkDefault, flags: DispatchWorkItemFlags? = nil, _ body: @escaping () -> Void) { + let dispatcher = selectDispatcher(given: on, configured: conf.D.return, flags: flags) + return finally(on: dispatcher, body) + } +} + +public extension CatchMixin where T == Void { + + /** + The provided closure executes when this promise rejects. + + This variant of `recover` is specialized for `Void` promises and de-errors your chain returning a `Guarantee`, thus you cannot `throw` and you must handle all errors including cancellation. + + - Parameter on: The queue to which the provided closure dispatches. + - Parameter body: The handler to execute if this promise is rejected. + - SeeAlso: [Cancellation](http://promisekit.org/docs/) + */ + @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) + return recover(on: dispatcher, body) + } + + /** + The provided closure executes when this promise rejects. + + This variant of `recover` ensures that no error is thrown from the handler and allows specifying a catch policy. + + - Parameter on: The queue to which the provided closure dispatches. + - Parameter body: The handler to execute if this promise is rejected. + - SeeAlso: [Cancellation](http://promisekit.org/docs/) + */ + func recover(on: DispatchQueue? = .pmkDefault, flags: DispatchWorkItemFlags? = nil, policy: CatchPolicy = conf.catchPolicy, _ body: @escaping(Error) throws -> Void) -> Promise { + let dispatcher = selectDispatcher(given: on, configured: conf.D.map, flags: flags) + return recover(on: dispatcher, policy: policy, body) + } +} diff --git a/Sources/Guarantee.swift b/Sources/Guarantee.swift index 597e97ad2..d5482c732 100644 --- a/Sources/Guarantee.swift +++ b/Sources/Guarantee.swift @@ -66,10 +66,10 @@ public final class Guarantee: Thenable { public extension Guarantee { @discardableResult - func done(on: DispatchQueue? = conf.Q.return, flags: DispatchWorkItemFlags? = nil, _ body: @escaping(T) -> Void) -> Guarantee { + func done(on: Dispatcher? = conf.D.return, _ body: @escaping(T) -> Void) -> Guarantee { let rg = Guarantee(.pending) pipe { (value: T) in - on.async(flags: flags) { + on.async { body(value) rg.box.seal(()) } @@ -84,10 +84,10 @@ public extension Guarantee { } } - func map(on: DispatchQueue? = conf.Q.map, flags: DispatchWorkItemFlags? = nil, _ body: @escaping(T) -> U) -> Guarantee { + func map(on: Dispatcher? = conf.D.map, _ body: @escaping(T) -> U) -> Guarantee { let rg = Guarantee(.pending) pipe { value in - on.async(flags: flags) { + on.async { rg.box.seal(body(value)) } } @@ -95,10 +95,10 @@ public extension Guarantee { } @discardableResult - func then(on: DispatchQueue? = conf.Q.map, flags: DispatchWorkItemFlags? = nil, _ body: @escaping(T) -> Guarantee) -> Guarantee { + func then(on: Dispatcher? = conf.D.map, flags: DispatchWorkItemFlags? = nil, _ body: @escaping(T) -> Guarantee) -> Guarantee { let rg = Guarantee(.pending) pipe { value in - on.async(flags: flags) { + on.async { body(value).pipe(to: rg.box.seal) } } @@ -106,7 +106,7 @@ public extension Guarantee { } public func asVoid() -> Guarantee { - return map(on: nil) { _ in } + return map { _ in } } /** @@ -135,7 +135,7 @@ public extension Guarantee { public extension Guarantee where T: Sequence { /** - `Guarantee<[T]>` => `T` -> `Guarantee` => `Guaranetee<[U]>` + `Guarantee<[T]>` => `T` -> `Guarantee` => `Guarantee<[U]>` firstly { .value([1,2,3]) @@ -145,8 +145,8 @@ public extension Guarantee where T: Sequence { // $0 => [2,4,6] } */ - func thenMap(on: DispatchQueue? = conf.Q.map, flags: DispatchWorkItemFlags? = nil, _ transform: @escaping(T.Iterator.Element) -> Guarantee) -> Guarantee<[U]> { - return then(on: on, flags: flags) { + func thenMap(on: Dispatcher? = conf.D.map, _ transform: @escaping(T.Iterator.Element) -> Guarantee) -> Guarantee<[U]> { + return then(on: on) { when(fulfilled: $0.map(transform)) }.recover { // if happens then is bug inside PromiseKit @@ -188,6 +188,28 @@ public extension DispatchQueue { } } +public extension Dispatcher { + /** + Asynchronously executes the provided closure on a Dispatcher. + + dispatcher.guarantee { + md5(input) + }.done { md5 in + //… + } + + - Parameter body: The closure that resolves this promise. + - Returns: A new `Guarantee` resolved by the result of the provided closure. + - Note: There is no Promise/Thenable version of this due to Swift compiler ambiguity issues. + */ + func guarantee(execute body: @escaping () -> T) -> Guarantee { + let rg = Guarantee(.pending) + async { + rg.box.seal(body()) + } + return rg + } +} #if os(Linux) import func CoreFoundation._CFIsMainThread diff --git a/Sources/Promise.swift b/Sources/Promise.swift index da9f6aa2b..ca5357b88 100644 --- a/Sources/Promise.swift +++ b/Sources/Promise.swift @@ -168,6 +168,33 @@ public extension DispatchQueue { } } +public extension Dispatcher { + /** + Asynchronously executes the provided closure on a Dispatcher. + + dispatcher.promise { + try md5(input) + }.done { md5 in + //… + } + + - Parameter body: The closure that resolves this promise. + - Returns: A new `Promise` resolved by the result of the provided closure. + - Note: There is no Promise/Thenable version of this due to Swift compiler ambiguity issues. + */ + func promise(execute body: @escaping () throws -> T) -> Promise { + let promise = Promise(.pending) + async { + do { + promise.box.seal(.fulfilled(try body())) + } catch { + promise.box.seal(.rejected(error)) + } + } + return promise + } +} + /// used by our extensions to provide unambiguous functions with the same name as the original function public enum PMKNamespacer { diff --git a/Sources/Thenable.swift b/Sources/Thenable.swift index 776237207..c37e4db5b 100644 --- a/Sources/Thenable.swift +++ b/Sources/Thenable.swift @@ -18,7 +18,7 @@ public extension Thenable { This allows chaining promises. The promise returned by the provided closure is resolved before the promise returned by this closure resolves. - - Parameter on: The queue to which the provided closure dispatches. + - Parameter on: The dispatcher that executes the provided closure. - 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. For example: @@ -30,12 +30,12 @@ public extension Thenable { //… } */ - func then(on: DispatchQueue? = conf.Q.map, flags: DispatchWorkItemFlags? = nil, _ body: @escaping(T) throws -> U) -> Promise { + func then(on: Dispatcher? = conf.D.map, _ body: @escaping(T) throws -> U) -> Promise { let rp = Promise(.pending) pipe { switch $0 { case .fulfilled(let value): - on.async(flags: flags) { + on.async { do { let rv = try body(value) guard rv !== rp else { throw PMKError.returnedSelf } @@ -56,7 +56,7 @@ public extension Thenable { This is like `then` but it requires the closure to return a non-promise. - - Parameter on: The queue to which the provided closure dispatches. + - Parameter on: The dispatcher that executes the provided closure. - Parameter transform: The closure that is executed when this Promise is fulfilled. It must return a non-promise. - Returns: A new promise that is resolved with the value returned from the provided closure. For example: @@ -68,12 +68,12 @@ public extension Thenable { //… } */ - func map(on: DispatchQueue? = conf.Q.map, flags: DispatchWorkItemFlags? = nil, _ transform: @escaping(T) throws -> U) -> Promise { + func map(on: Dispatcher? = conf.D.map, _ transform: @escaping(T) throws -> U) -> Promise { let rp = Promise(.pending) pipe { switch $0 { case .fulfilled(let value): - on.async(flags: flags) { + on.async { do { rp.box.seal(.fulfilled(try transform(value))) } catch { @@ -102,12 +102,12 @@ public extension Thenable { // either `PMKError.compactMap` or a `JSONError` } */ - func compactMap(on: DispatchQueue? = conf.Q.map, flags: DispatchWorkItemFlags? = nil, _ transform: @escaping(T) throws -> U?) -> Promise { + func compactMap(on: Dispatcher? = conf.D.map, _ transform: @escaping(T) throws -> U?) -> Promise { let rp = Promise(.pending) pipe { switch $0 { case .fulfilled(let value): - on.async(flags: flags) { + on.async { do { if let rv = try transform(value) { rp.box.seal(.fulfilled(rv)) @@ -131,7 +131,7 @@ public extension Thenable { Equivalent to `map { x -> Void in`, but since we force the `Void` return Swift is happier and gives you less hassle about your closure’s qualification. - - Parameter on: The queue to which the provided closure dispatches. + - Parameter on: The dispatcher that executes the provided closure. - Parameter body: The closure that is executed when this Promise is fulfilled. - Returns: A new promise fulfilled as `Void`. @@ -141,12 +141,12 @@ public extension Thenable { print(response.data) } */ - func done(on: DispatchQueue? = conf.Q.return, flags: DispatchWorkItemFlags? = nil, _ body: @escaping(T) throws -> Void) -> Promise { + func done(on: Dispatcher? = conf.Q.return, _ body: @escaping(T) throws -> Void) -> Promise { let rp = Promise(.pending) pipe { switch $0 { case .fulfilled(let value): - on.async(flags: flags) { + on.async { do { try body(value) rp.box.seal(.fulfilled(())) @@ -167,7 +167,7 @@ public extension Thenable { This is like `done` but it returns the same value that the handler is fed. `get` immutably accesses the fulfilled value; the returned Promise maintains that value. - - Parameter on: The queue to which the provided closure dispatches. + - Parameter on: The dispatcher that executes the provided closure. - 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. For example: @@ -181,7 +181,7 @@ public extension Thenable { print(foo, " is Void") } */ - func get(on: DispatchQueue? = conf.Q.return, flags: DispatchWorkItemFlags? = nil, _ body: @escaping (T) throws -> Void) -> Promise { + func get(on: Dispatcher? = conf.D.return, _ body: @escaping (T) throws -> Void) -> Promise { return map(on: on, flags: flags) { try body($0) return $0 @@ -212,7 +212,7 @@ public extension Thenable { /// - Returns: a new promise chained off this promise but with its value discarded. func asVoid() -> Promise { - return map(on: nil) { _ in } + return map { _ in } } } @@ -286,8 +286,8 @@ public extension Thenable where T: Sequence { // $0 => [2,4,6] } */ - func mapValues(on: DispatchQueue? = conf.Q.map, flags: DispatchWorkItemFlags? = nil, _ transform: @escaping(T.Iterator.Element) throws -> U) -> Promise<[U]> { - return map(on: on, flags: flags){ try $0.map(transform) } + func mapValues(on: Dispatcher? = conf.D.map, _ transform: @escaping(T.Iterator.Element) throws -> U) -> Promise<[U]> { + return map(on: on) { try $0.map(transform) } } /** @@ -301,8 +301,8 @@ public extension Thenable where T: Sequence { // $0 => [1,1,2,2,3,3] } */ - func flatMapValues(on: DispatchQueue? = conf.Q.map, flags: DispatchWorkItemFlags? = nil, _ transform: @escaping(T.Iterator.Element) throws -> U) -> Promise<[U.Iterator.Element]> { - return map(on: on, flags: flags){ (foo: T) in + func flatMapValues(on: Dispatcher? = conf.D.map, _ transform: @escaping(T.Iterator.Element) throws -> U) -> Promise<[U.Iterator.Element]> { + return map(on: on){ (foo: T) in try foo.flatMap{ try transform($0) } } } @@ -318,8 +318,8 @@ public extension Thenable where T: Sequence { // $0 => [1,2,3] } */ - func compactMapValues(on: DispatchQueue? = conf.Q.map, flags: DispatchWorkItemFlags? = nil, _ transform: @escaping(T.Iterator.Element) throws -> U?) -> Promise<[U]> { - return map(on: on, flags: flags) { foo -> [U] in + func compactMapValues(on: Dispatcher? = conf.D.map, _ transform: @escaping(T.Iterator.Element) throws -> U?) -> Promise<[U]> { + return map(on: on) { foo -> [U] in #if !swift(>=3.3) || (swift(>=4) && !swift(>=4.1)) return try foo.flatMap(transform) #else @@ -339,8 +339,8 @@ public extension Thenable where T: Sequence { // $0 => [2,4,6] } */ - func thenMap(on: DispatchQueue? = conf.Q.map, flags: DispatchWorkItemFlags? = nil, _ transform: @escaping(T.Iterator.Element) throws -> U) -> Promise<[U.T]> { - return then(on: on, flags: flags) { + func thenMap(on: Dispatcher? = conf.D.map, _ transform: @escaping(T.Iterator.Element) throws -> U) -> Promise<[U.T]> { + return then(on: on) { when(fulfilled: try $0.map(transform)) } } @@ -356,8 +356,8 @@ public extension Thenable where T: Sequence { // $0 => [1,1,2,2,3,3] } */ - func thenFlatMap(on: DispatchQueue? = conf.Q.map, flags: DispatchWorkItemFlags? = nil, _ transform: @escaping(T.Iterator.Element) throws -> U) -> Promise<[U.T.Iterator.Element]> where U.T: Sequence { - return then(on: on, flags: flags) { + func thenFlatMap(on: Dispatcher? = conf.D.map, _ 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) { $0.flatMap{ $0 } @@ -375,8 +375,8 @@ public extension Thenable where T: Sequence { // $0 => [2,3] } */ - func filterValues(on: DispatchQueue? = conf.Q.map, flags: DispatchWorkItemFlags? = nil, _ isIncluded: @escaping (T.Iterator.Element) -> Bool) -> Promise<[T.Iterator.Element]> { - return map(on: on, flags: flags) { + func filterValues(on: Dispatcher? = conf.D.map, _ isIncluded: @escaping (T.Iterator.Element) -> Bool) -> Promise<[T.Iterator.Element]> { + return map(on: on) { $0.filter(isIncluded) } } @@ -394,8 +394,8 @@ public extension Thenable where T: Collection { } } - func firstValue(on: DispatchQueue? = conf.Q.map, flags: DispatchWorkItemFlags? = nil, where test: @escaping (T.Iterator.Element) -> Bool) -> Promise { - return map(on: on, flags: flags) { + func firstValue(on: Dispatcher? = conf.D.map, where test: @escaping (T.Iterator.Element) -> Bool) -> Promise { + return map(on: on) { for x in $0 where test(x) { return x } @@ -418,7 +418,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: DispatchQueue? = conf.Q.map, flags: DispatchWorkItemFlags? = nil) -> Promise<[T.Iterator.Element]> { - return map(on: on, flags: flags){ $0.sorted() } + func sortedValues(on: Dispatcher? = conf.D.map) -> Promise<[T.Iterator.Element]> { + return map(on: on){ $0.sorted() } } } From 4ba57edddcccb183146d813462d145ed4f60b79e Mon Sep 17 00:00:00 2001 From: Garth Snyder Date: Mon, 18 Jun 2018 00:43:50 -0700 Subject: [PATCH 12/26] Put back map(on: nil) --- Sources/Guarantee.swift | 2 +- Sources/Thenable.swift | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Sources/Guarantee.swift b/Sources/Guarantee.swift index d5482c732..fc7c16da2 100644 --- a/Sources/Guarantee.swift +++ b/Sources/Guarantee.swift @@ -106,7 +106,7 @@ public extension Guarantee { } public func asVoid() -> Guarantee { - return map { _ in } + return map(on: nil) { _ in } } /** diff --git a/Sources/Thenable.swift b/Sources/Thenable.swift index c37e4db5b..a56f53f19 100644 --- a/Sources/Thenable.swift +++ b/Sources/Thenable.swift @@ -212,7 +212,7 @@ public extension Thenable { /// - Returns: a new promise chained off this promise but with its value discarded. func asVoid() -> Promise { - return map { _ in } + return map(on: nil) { _ in } } } From 262b4dd5784da097849a1e6b99b6bfd9bdb5b613 Mon Sep 17 00:00:00 2001 From: Garth Snyder Date: Mon, 18 Jun 2018 01:25:24 -0700 Subject: [PATCH 13/26] Rename async() method args to forestall ambiguity; compiles --- Sources/Box.swift | 6 +++--- Sources/Dispatcher.swift | 10 +++++----- Sources/Thenable.swift | 2 +- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/Sources/Box.swift b/Sources/Box.swift index f17caf5ae..fc8b3e4e0 100644 --- a/Sources/Box.swift +++ b/Sources/Box.swift @@ -84,14 +84,14 @@ final class EmptyBox: Box { } -extension Optional: Dispatcher where Wrapped: Dispatcher { +extension Optional: Dispatcher where Wrapped == Dispatcher { @inline(__always) - public func async(execute body: @escaping () -> Void) { + public func async(_ body: @escaping () -> Void) { switch self { case .none: body() case .some(let dispatcher): - dispatcher.async(execute: body) + dispatcher.async(body) } } } diff --git a/Sources/Dispatcher.swift b/Sources/Dispatcher.swift index 0750f1e54..d5e0807b2 100644 --- a/Sources/Dispatcher.swift +++ b/Sources/Dispatcher.swift @@ -1,7 +1,7 @@ import Dispatch public protocol Dispatcher { - func async(execute work: @escaping () -> Void) + func async(_ body: @escaping () -> Void) } public struct DispatchQueueDispatcher: Dispatcher { @@ -14,8 +14,8 @@ public struct DispatchQueueDispatcher: Dispatcher { self.flags = flags } - public func async(execute work: @escaping () -> Void) { - queue.async(flags: flags, execute: work) + public func async(_ body: @escaping () -> Void) { + queue.async(flags: flags, execute: body) } } @@ -23,8 +23,8 @@ public struct DispatchQueueDispatcher: Dispatcher { extension DispatchQueue: Dispatcher { /// Explicit declaration required; actual function signature is not identical to protocol - public func async(execute work: @escaping () -> Void) { - async(execute: work) + public func async(_ body: @escaping () -> Void) { + async(execute: body) } } diff --git a/Sources/Thenable.swift b/Sources/Thenable.swift index a56f53f19..b944a3896 100644 --- a/Sources/Thenable.swift +++ b/Sources/Thenable.swift @@ -182,7 +182,7 @@ public extension Thenable { } */ func get(on: Dispatcher? = conf.D.return, _ body: @escaping (T) throws -> Void) -> Promise { - return map(on: on, flags: flags) { + return map(on: on) { try body($0) return $0 } From 044fe7841716fd7312f52c1a2ca5878e57d8ee56 Mon Sep 17 00:00:00 2001 From: Garth Snyder Date: Tue, 19 Jun 2018 18:36:08 -0700 Subject: [PATCH 14/26] Working on Dispatcher tests --- PromiseKit.xcodeproj/project.pbxproj | 4 + Sources/Dispatcher.swift | 2 +- Sources/Thenable.swift | 2 +- Tests/CorePromise/DispatcherTests.swift | 105 ++++++++++++++++++++++++ 4 files changed, 111 insertions(+), 2 deletions(-) create mode 100644 Tests/CorePromise/DispatcherTests.swift diff --git a/PromiseKit.xcodeproj/project.pbxproj b/PromiseKit.xcodeproj/project.pbxproj index aa532b751..3d9101e87 100644 --- a/PromiseKit.xcodeproj/project.pbxproj +++ b/PromiseKit.xcodeproj/project.pbxproj @@ -81,6 +81,7 @@ 63D9B2EF203385FD0075C00B /* race.m in Sources */ = {isa = PBXBuildFile; fileRef = 63D9B2EE203385FD0075C00B /* race.m */; }; 63D9B2F120338D5D0075C00B /* Deprecations.swift in Sources */ = {isa = PBXBuildFile; fileRef = 63D9B2F020338D5D0075C00B /* Deprecations.swift */; }; BB2524DE20D729A60010F7B0 /* Dispatcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB2524DD20D729A60010F7B0 /* Dispatcher.swift */; }; + BB4AF7C520D820700008333D /* DispatcherTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB4AF7C320D819360008333D /* DispatcherTests.swift */; }; C013F7382048E3B6006B57B1 /* MockNodeEnvironment.swift in Sources */ = {isa = PBXBuildFile; fileRef = C013F7372048E3B6006B57B1 /* MockNodeEnvironment.swift */; }; C013F73A2049076A006B57B1 /* JSPromise.swift in Sources */ = {isa = PBXBuildFile; fileRef = C013F7392049076A006B57B1 /* JSPromise.swift */; }; C013F73C20494291006B57B1 /* JSAdapter.swift in Sources */ = {isa = PBXBuildFile; fileRef = C013F73B20494291006B57B1 /* JSAdapter.swift */; }; @@ -225,6 +226,7 @@ 63D9B2EE203385FD0075C00B /* race.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = race.m; path = Sources/race.m; sourceTree = ""; }; 63D9B2F020338D5D0075C00B /* Deprecations.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = Deprecations.swift; path = Sources/Deprecations.swift; sourceTree = ""; }; BB2524DD20D729A60010F7B0 /* Dispatcher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = Dispatcher.swift; path = Sources/Dispatcher.swift; sourceTree = ""; }; + BB4AF7C320D819360008333D /* DispatcherTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DispatcherTests.swift; sourceTree = ""; }; C013F7372048E3B6006B57B1 /* MockNodeEnvironment.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = MockNodeEnvironment.swift; path = "Tests/JS-A+/MockNodeEnvironment.swift"; sourceTree = ""; }; C013F7392049076A006B57B1 /* JSPromise.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = JSPromise.swift; path = "Tests/JS-A+/JSPromise.swift"; sourceTree = ""; }; C013F73B20494291006B57B1 /* JSAdapter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = JSAdapter.swift; path = "Tests/JS-A+/JSAdapter.swift"; sourceTree = ""; }; @@ -354,6 +356,7 @@ 635D640D1D59635300BC0AF5 /* ZalgoTests.swift */, 639BF755203DF02C00FA577B /* Utilities.swift */, 085B96B121A6358900E5E22F /* LoggingTests.swift */, + BB4AF7C320D819360008333D /* DispatcherTests.swift */, ); name = Core; path = Tests/CorePromise; @@ -680,6 +683,7 @@ 635D64221D59635300BC0AF5 /* ZalgoTests.swift in Sources */, 635D64271D59635300BC0AF5 /* RaceTests.swift in Sources */, 632FBBE51F33B338008F8FBB /* CatchableTests.swift in Sources */, + BB4AF7C520D820700008333D /* DispatcherTests.swift in Sources */, 63CF6D80203CD19200EC8927 /* ThenableTests.swift in Sources */, 635D642B1D59635300BC0AF5 /* StressTests.swift in Sources */, 630A805A203CEF6800D25F23 /* WhenTests.m in Sources */, diff --git a/Sources/Dispatcher.swift b/Sources/Dispatcher.swift index d5e0807b2..bff518f39 100644 --- a/Sources/Dispatcher.swift +++ b/Sources/Dispatcher.swift @@ -369,7 +369,7 @@ public extension CatchMixin { - Parameter body: The handler to execute if this promise is rejected. - SeeAlso: [Cancellation](http://promisekit.org/docs/) */ - func recover(on: DispatchQueue? = conf.Q.map, flags: DispatchWorkItemFlags? = nil, policy: CatchPolicy = conf.catchPolicy, _ body: @escaping(Error) throws -> U) -> Promise where U.T == T { + func recover(on: DispatchQueue? = .pmkDefault, flags: DispatchWorkItemFlags? = nil, policy: CatchPolicy = conf.catchPolicy, _ body: @escaping(Error) throws -> U) -> Promise where U.T == T { let dispatcher = selectDispatcher(given: on, configured: conf.D.map, flags: flags) return recover(on: dispatcher, policy: policy, body) } diff --git a/Sources/Thenable.swift b/Sources/Thenable.swift index b944a3896..7516d6535 100644 --- a/Sources/Thenable.swift +++ b/Sources/Thenable.swift @@ -141,7 +141,7 @@ public extension Thenable { print(response.data) } */ - func done(on: Dispatcher? = conf.Q.return, _ body: @escaping(T) throws -> Void) -> Promise { + func done(on: Dispatcher? = conf.D.return, _ body: @escaping(T) throws -> Void) -> Promise { let rp = Promise(.pending) pipe { switch $0 { diff --git a/Tests/CorePromise/DispatcherTests.swift b/Tests/CorePromise/DispatcherTests.swift new file mode 100644 index 000000000..64ae85178 --- /dev/null +++ b/Tests/CorePromise/DispatcherTests.swift @@ -0,0 +1,105 @@ +import PromiseKit +import XCTest + +class RecordingDispatcher: Dispatcher { + + var dispatchCount = 0 + + func async(_ body: @escaping () -> Void) { + dispatchCount += 1 + DispatchQueue.global(qos: .background).async(execute: body) + } + +} + +class DispatcherTests: XCTestCase { + + var dispatcher = RecordingDispatcher() + var dispatcherB = RecordingDispatcher() + + override func setUp() { + dispatcher = RecordingDispatcher() + 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) // Not representable as DispatchQueues + XCTAssertNil(PromiseKit.conf.Q.return) + Promise { seal in + seal.fulfill(42) + }.map { + $0 + 10 + }.done() { + XCTAssertEqual($0, 52) + XCTAssertEqual(self.dispatcher.dispatchCount, 1) + XCTAssertEqual(self.dispatcherB.dispatchCount, 1) + ex.fulfill() + }.cauterize() + waitForExpectations(timeout: 1) + PromiseKit.conf.D.map = DispatchQueue.main + PromiseKit.conf.Q.return = .main + XCTAssert(PromiseKit.conf.Q.map === DispatchQueue.main) + XCTAssert((PromiseKit.conf.D.return as? DispatchQueue)! === DispatchQueue.main) + PromiseKit.conf.D = oldConf + } + + func testDispatcherWithThrow() { + let ex = expectation(description: "Dispatcher with throw") + Promise { seal in + seal.fulfill(42) + }.map(on: dispatcher) { _ in + throw PMKError.badInput + }.catch(on: dispatcher) { _ in + ex.fulfill() + } + waitForExpectations(timeout: 1) + XCTAssertEqual(self.dispatcher.dispatchCount, 2) + } + + func testDispatchQueueBackwardCompatibility() { + let ex = expectation(description: "DispatchQueue compatibility") + let oldConf = PromiseKit.conf.D + PromiseKit.conf.D = (map: dispatcher, return: dispatcher) + Promise.value(42).map(on: .global(qos: .background), flags: .barrier) { (x: Int) -> Int in + return x + 10 + }.then(on: .main, flags: []) { + XCTAssertEqual($0, 52) + return Promise.value(50) + }.done(on: .global(qos: .userInitiated)) { + XCTAssertEqual($0, 50) + ex.fulfill() + }.cauterize() + waitForExpectations(timeout: 1) + XCTAssertEqual(self.dispatcher.dispatchCount, 0) + PromiseKit.conf.D = oldConf + } + + func testDispatcherPromiseExtension() { + let ex = expectation(description: "Dispatcher.promise") + dispatcher.promise { + return 42 + }.done(on: dispatcher) { + XCTAssertEqual($0, 42) + XCTAssertEqual(self.dispatcher.dispatchCount, 2) + ex.fulfill() + }.cauterize() + waitForExpectations(timeout: 1) + } + + func testDispatcherGuaranteeExtension() { + let ex = expectation(description: "Dispatcher.guarantee") + dispatcher.guarantee { + return 42 + }.done(on: .main) { + XCTAssertEqual($0, 42) + XCTAssertEqual(self.dispatcher.dispatchCount, 1) + ex.fulfill() + } + waitForExpectations(timeout: 1) + } + +} From 0a027c8b0ce18a9efa9cce5d1e6406bcdda36238 Mon Sep 17 00:00:00 2001 From: Garth Snyder Date: Wed, 20 Jun 2018 12:54:41 -0700 Subject: [PATCH 15/26] Make Dispatcher arguments nonoptional --- Sources/Box.swift | 13 ------------- Sources/Catchable.swift | 30 +++++++++++++++--------------- Sources/Configuration.swift | 4 ++-- Sources/Dispatcher.swift | 28 +++++++++++++++------------- Sources/Guarantee.swift | 16 ++++++++-------- Sources/Promise.swift | 2 +- Sources/Thenable.swift | 34 +++++++++++++++++----------------- 7 files changed, 58 insertions(+), 69 deletions(-) diff --git a/Sources/Box.swift b/Sources/Box.swift index fc8b3e4e0..8571999f0 100644 --- a/Sources/Box.swift +++ b/Sources/Box.swift @@ -82,16 +82,3 @@ final class EmptyBox: Box { } } } - - -extension Optional: Dispatcher where Wrapped == Dispatcher { - @inline(__always) - public func async(_ body: @escaping () -> Void) { - switch self { - case .none: - body() - case .some(let dispatcher): - dispatcher.async(body) - } - } -} diff --git a/Sources/Catchable.swift b/Sources/Catchable.swift index 12491108d..020298d9c 100644 --- a/Sources/Catchable.swift +++ b/Sources/Catchable.swift @@ -21,7 +21,7 @@ public extension CatchMixin { - SeeAlso: [Cancellation](http://promisekit.org/docs/) */ @discardableResult - func `catch`(on: Dispatcher? = conf.D.return, policy: CatchPolicy = conf.catchPolicy, _ body: @escaping(Error) -> Void) -> PMKFinalizer { + func `catch`(on: Dispatcher = conf.D.return, policy: CatchPolicy = conf.catchPolicy, _ body: @escaping(Error) -> Void) -> PMKFinalizer { let finalizer = PMKFinalizer() pipe { switch $0 { @@ -29,7 +29,7 @@ public extension CatchMixin { guard policy == .allErrors || !error.isCancelled else { fallthrough } - on.async { + on.dispatch { body(error) finalizer.pending.resolve(()) } @@ -45,7 +45,7 @@ public class PMKFinalizer { let pending = Guarantee.pending() /// `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.D.return, _ body: @escaping () -> Void) { pending.guarantee.done(on: on) { body() } @@ -72,7 +72,7 @@ public extension CatchMixin { - Parameter body: The handler to execute if this promise is rejected. - SeeAlso: [Cancellation](http://promisekit.org/docs/) */ - 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.D.map, policy: CatchPolicy = conf.catchPolicy, _ body: @escaping(Error) throws -> U) -> Promise where U.T == T { let rp = Promise(.pending) pipe { switch $0 { @@ -80,7 +80,7 @@ public extension CatchMixin { rp.box.seal(.fulfilled(value)) case .rejected(let error): if policy == .allErrors || !error.isCancelled { - on.async { + on.dispatch { do { let rv = try body(error) guard rv !== rp else { throw PMKError.returnedSelf } @@ -106,14 +106,14 @@ public extension CatchMixin { - SeeAlso: [Cancellation](http://promisekit.org/docs/) */ @discardableResult - func recover(on: Dispatcher? = conf.D.map, _ body: @escaping(Error) -> Guarantee) -> Guarantee { + func recover(on: Dispatcher = conf.D.map, _ body: @escaping(Error) -> Guarantee) -> Guarantee { let rg = Guarantee(.pending) pipe { switch $0 { case .fulfilled(let value): rg.box.seal(value) case .rejected(let error): - on.async { + on.dispatch { body(error).pipe(to: rg.box.seal) } } @@ -138,10 +138,10 @@ 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.D.return, _ body: @escaping () -> Void) -> Promise { let rp = Promise(.pending) pipe { result in - on.async { + on.dispatch { body() rp.box.seal(result) } @@ -167,10 +167,10 @@ 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.D.return, _ body: @escaping () -> Guarantee) -> Promise { let rp = Promise(.pending) pipe { result in - on.async { + on.dispatch { body().done { rp.box.seal(result) } @@ -205,14 +205,14 @@ public extension CatchMixin where T == Void { - SeeAlso: [Cancellation](http://promisekit.org/docs/) */ @discardableResult - func recover(on: Dispatcher? = conf.D.map, flags: DispatchWorkItemFlags? = nil, _ body: @escaping(Error) -> Void) -> Guarantee { + func recover(on: Dispatcher = conf.D.map, _ body: @escaping(Error) -> Void) -> Guarantee { let rg = Guarantee(.pending) pipe { switch $0 { case .fulfilled: rg.box.seal(()) case .rejected(let error): - on.async { + on.dispatch { body(error) rg.box.seal(()) } @@ -230,7 +230,7 @@ public extension CatchMixin where T == Void { - Parameter body: The handler to execute if this promise is rejected. - SeeAlso: [Cancellation](http://promisekit.org/docs/) */ - func recover(on: Dispatcher? = conf.D.map, policy: CatchPolicy = conf.catchPolicy, _ body: @escaping(Error) throws -> Void) -> Promise { + func recover(on: Dispatcher = conf.D.map, policy: CatchPolicy = conf.catchPolicy, _ body: @escaping(Error) throws -> Void) -> Promise { let rg = Promise(.pending) pipe { switch $0 { @@ -238,7 +238,7 @@ public extension CatchMixin where T == Void { rg.box.seal(.fulfilled(())) case .rejected(let error): if policy == .allErrors || !error.isCancelled { - on.async { + on.dispatch { do { rg.box.seal(.fulfilled(try body(error))) } catch { diff --git a/Sources/Configuration.swift b/Sources/Configuration.swift index b53c810be..94d0a606b 100644 --- a/Sources/Configuration.swift +++ b/Sources/Configuration.swift @@ -11,11 +11,11 @@ public struct PMKConfiguration { /// Backward compatibility: default DispatchQueues that promise handlers dispatch to public var Q: (map: DispatchQueue?, return: DispatchQueue?) { get { return (map: D.map as? DispatchQueue, return: D.return as? DispatchQueue) } - set { D = (map: newValue.map, return: newValue.return) } + set { D = (map: newValue.map ?? CurrentThreadDispatcher(), return: newValue.return ?? CurrentThreadDispatcher()) } } /// The default Dispatchers that promise handlers dispatch to - public var D: (map: Dispatcher?, return: Dispatcher?) = (map: DispatchQueue.main, return: DispatchQueue.main) + public var D: (map: Dispatcher, return: Dispatcher) = (map: DispatchQueue.main, return: DispatchQueue.main) /// The default catch-policy for all `catch` and `resolve` public var catchPolicy = CatchPolicy.allErrorsExceptCancellation diff --git a/Sources/Dispatcher.swift b/Sources/Dispatcher.swift index bff518f39..2d7a244b8 100644 --- a/Sources/Dispatcher.swift +++ b/Sources/Dispatcher.swift @@ -1,10 +1,10 @@ import Dispatch public protocol Dispatcher { - func async(_ body: @escaping () -> Void) + func dispatch(_ body: @escaping () -> Void) } -public struct DispatchQueueDispatcher: Dispatcher { +public class DispatchQueueDispatcher: Dispatcher { let queue: DispatchQueue let flags: DispatchWorkItemFlags @@ -14,19 +14,23 @@ public struct DispatchQueueDispatcher: Dispatcher { self.flags = flags } - public func async(_ body: @escaping () -> Void) { + public func dispatch(_ body: @escaping () -> Void) { queue.async(flags: flags, execute: body) } } +public struct CurrentThreadDispatcher: Dispatcher { + public func dispatch(_ body: @escaping () -> Void) { + body() + } +} + extension DispatchQueue: Dispatcher { - /// Explicit declaration required; actual function signature is not identical to protocol - public func async(_ body: @escaping () -> Void) { + public func dispatch(_ body: @escaping () -> Void) { async(execute: body) } - } /// Used as default parameter for backward compatibility since clients may explicitly @@ -38,15 +42,13 @@ public extension DispatchQueue { static var pmkDefault = DispatchQueue(label: "org.promisekit.sentinel") } -extension DispatchQueue { - - public func asDispatcher(withFlags flags: DispatchWorkItemFlags? = nil) -> Dispatcher { +public extension DispatchQueue { + func asDispatcher(withFlags flags: DispatchWorkItemFlags? = nil) -> Dispatcher { if let flags = flags { return DispatchQueueDispatcher(queue: self, flags: flags) } return self } - } /// This hairball disambiguates all the various combinations of explicit arguments, default @@ -56,18 +58,18 @@ extension DispatchQueue { /// /// TODO: should conf.D = nil turn off dispatching even if explicit dispatch arguments are given? -fileprivate func selectDispatcher(given: DispatchQueue?, configured: Dispatcher?, flags: DispatchWorkItemFlags?) -> Dispatcher? { +fileprivate func selectDispatcher(given: DispatchQueue?, configured: Dispatcher, flags: DispatchWorkItemFlags?) -> Dispatcher { guard let given = given else { if flags != nil { print("PromiseKit: warning: nil DispatchQueue specified, but DispatchWorkItemFlags were also supplied (ignored)") } - return nil + return CurrentThreadDispatcher() } if given !== DispatchQueue.pmkDefault { return given.asDispatcher(withFlags: flags) } else if let flags = flags, let configured = configured as? DispatchQueue { return configured.asDispatcher(withFlags: flags) - } else if flags != nil && configured != nil { + } else if flags != nil { print("PromiseKit: warning: DispatchWorkItemFlags flags specified, but default dispatcher is not a DispatchQueue (ignored)") } return configured diff --git a/Sources/Guarantee.swift b/Sources/Guarantee.swift index fc7c16da2..fd6ee689a 100644 --- a/Sources/Guarantee.swift +++ b/Sources/Guarantee.swift @@ -66,10 +66,10 @@ 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.D.return, _ body: @escaping(T) -> Void) -> Guarantee { let rg = Guarantee(.pending) pipe { (value: T) in - on.async { + on.dispatch { body(value) rg.box.seal(()) } @@ -84,10 +84,10 @@ public extension Guarantee { } } - func map(on: Dispatcher? = conf.D.map, _ body: @escaping(T) -> U) -> Guarantee { + func map(on: Dispatcher = conf.D.map, _ body: @escaping(T) -> U) -> Guarantee { let rg = Guarantee(.pending) pipe { value in - on.async { + on.dispatch { rg.box.seal(body(value)) } } @@ -95,10 +95,10 @@ public extension Guarantee { } @discardableResult - func then(on: Dispatcher? = conf.D.map, flags: DispatchWorkItemFlags? = nil, _ body: @escaping(T) -> Guarantee) -> Guarantee { + func then(on: Dispatcher = conf.D.map, flags: DispatchWorkItemFlags? = nil, _ body: @escaping(T) -> Guarantee) -> Guarantee { let rg = Guarantee(.pending) pipe { value in - on.async { + on.dispatch { body(value).pipe(to: rg.box.seal) } } @@ -145,7 +145,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.D.map, _ transform: @escaping(T.Iterator.Element) -> Guarantee) -> Guarantee<[U]> { return then(on: on) { when(fulfilled: $0.map(transform)) }.recover { @@ -204,7 +204,7 @@ public extension Dispatcher { */ func guarantee(execute body: @escaping () -> T) -> Guarantee { let rg = Guarantee(.pending) - async { + dispatch { rg.box.seal(body()) } return rg diff --git a/Sources/Promise.swift b/Sources/Promise.swift index ca5357b88..1daf1192b 100644 --- a/Sources/Promise.swift +++ b/Sources/Promise.swift @@ -184,7 +184,7 @@ public extension Dispatcher { */ func promise(execute body: @escaping () throws -> T) -> Promise { let promise = Promise(.pending) - async { + dispatch { do { promise.box.seal(.fulfilled(try body())) } catch { diff --git a/Sources/Thenable.swift b/Sources/Thenable.swift index 7516d6535..0cb935bdf 100644 --- a/Sources/Thenable.swift +++ b/Sources/Thenable.swift @@ -30,12 +30,12 @@ public extension Thenable { //… } */ - func then(on: Dispatcher? = conf.D.map, _ body: @escaping(T) throws -> U) -> Promise { + func then(on: Dispatcher = conf.D.map, _ body: @escaping(T) throws -> U) -> Promise { let rp = Promise(.pending) pipe { switch $0 { case .fulfilled(let value): - on.async { + on.dispatch { do { let rv = try body(value) guard rv !== rp else { throw PMKError.returnedSelf } @@ -68,12 +68,12 @@ public extension Thenable { //… } */ - func map(on: Dispatcher? = conf.D.map, _ transform: @escaping(T) throws -> U) -> Promise { + func map(on: Dispatcher = conf.D.map, _ transform: @escaping(T) throws -> U) -> Promise { let rp = Promise(.pending) pipe { switch $0 { case .fulfilled(let value): - on.async { + on.dispatch { do { rp.box.seal(.fulfilled(try transform(value))) } catch { @@ -102,12 +102,12 @@ 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.D.map, _ transform: @escaping(T) throws -> U?) -> Promise { let rp = Promise(.pending) pipe { switch $0 { case .fulfilled(let value): - on.async { + on.dispatch { do { if let rv = try transform(value) { rp.box.seal(.fulfilled(rv)) @@ -141,12 +141,12 @@ public extension Thenable { print(response.data) } */ - func done(on: Dispatcher? = conf.D.return, _ body: @escaping(T) throws -> Void) -> Promise { + func done(on: Dispatcher = conf.D.return, _ body: @escaping(T) throws -> Void) -> Promise { let rp = Promise(.pending) pipe { switch $0 { case .fulfilled(let value): - on.async { + on.dispatch { do { try body(value) rp.box.seal(.fulfilled(())) @@ -181,7 +181,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.D.return, _ body: @escaping (T) throws -> Void) -> Promise { return map(on: on) { try body($0) return $0 @@ -286,7 +286,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.D.map, _ transform: @escaping(T.Iterator.Element) throws -> U) -> Promise<[U]> { return map(on: on) { try $0.map(transform) } } @@ -301,7 +301,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.D.map, _ transform: @escaping(T.Iterator.Element) throws -> U) -> Promise<[U.Iterator.Element]> { return map(on: on){ (foo: T) in try foo.flatMap{ try transform($0) } } @@ -318,7 +318,7 @@ public extension Thenable where T: Sequence { // $0 => [1,2,3] } */ - func compactMapValues(on: Dispatcher? = conf.D.map, _ transform: @escaping(T.Iterator.Element) throws -> U?) -> Promise<[U]> { + func compactMapValues(on: Dispatcher = conf.D.map, _ transform: @escaping(T.Iterator.Element) throws -> U?) -> Promise<[U]> { return map(on: on) { foo -> [U] in #if !swift(>=3.3) || (swift(>=4) && !swift(>=4.1)) return try foo.flatMap(transform) @@ -339,7 +339,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.D.map, _ transform: @escaping(T.Iterator.Element) throws -> U) -> Promise<[U.T]> { return then(on: on) { when(fulfilled: try $0.map(transform)) } @@ -356,7 +356,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.D.map, _ 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) { @@ -375,7 +375,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.D.map, _ isIncluded: @escaping (T.Iterator.Element) -> Bool) -> Promise<[T.Iterator.Element]> { return map(on: on) { $0.filter(isIncluded) } @@ -394,7 +394,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.D.map, where test: @escaping (T.Iterator.Element) -> Bool) -> Promise { return map(on: on) { for x in $0 where test(x) { return x @@ -418,7 +418,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.D.map) -> Promise<[T.Iterator.Element]> { return map(on: on){ $0.sorted() } } } From 855476acbe3e77fb7b4feb7109cd7ac957984c0a Mon Sep 17 00:00:00 2001 From: Garth Snyder Date: Wed, 20 Jun 2018 14:24:59 -0700 Subject: [PATCH 16/26] Finish tests --- Sources/Guarantee.swift | 2 +- Tests/CorePromise/DispatcherTests.swift | 56 +++++++++++++++++++++---- 2 files changed, 48 insertions(+), 10 deletions(-) diff --git a/Sources/Guarantee.swift b/Sources/Guarantee.swift index fd6ee689a..5f387aee7 100644 --- a/Sources/Guarantee.swift +++ b/Sources/Guarantee.swift @@ -95,7 +95,7 @@ public extension Guarantee { } @discardableResult - func then(on: Dispatcher = conf.D.map, flags: DispatchWorkItemFlags? = nil, _ body: @escaping(T) -> Guarantee) -> Guarantee { + func then(on: Dispatcher = conf.D.map, _ body: @escaping(T) -> Guarantee) -> Guarantee { let rg = Guarantee(.pending) pipe { value in on.dispatch { diff --git a/Tests/CorePromise/DispatcherTests.swift b/Tests/CorePromise/DispatcherTests.swift index 64ae85178..6206ef10d 100644 --- a/Tests/CorePromise/DispatcherTests.swift +++ b/Tests/CorePromise/DispatcherTests.swift @@ -1,13 +1,23 @@ import PromiseKit import XCTest +fileprivate let queueIDKey = DispatchSpecificKey() + class RecordingDispatcher: Dispatcher { + static var queueIndex = 1 + var dispatchCount = 0 + let queue: DispatchQueue + + init() { + queue = DispatchQueue(label: "org.promisekit.testqueue \(RecordingDispatcher.queueIndex)") + RecordingDispatcher.queueIndex += 1 + } - func async(_ body: @escaping () -> Void) { + func dispatch(_ body: @escaping () -> Void) { dispatchCount += 1 - DispatchQueue.global(qos: .background).async(execute: body) + queue.async(execute: body) } } @@ -60,22 +70,50 @@ class DispatcherTests: XCTestCase { XCTAssertEqual(self.dispatcher.dispatchCount, 2) } - func testDispatchQueueBackwardCompatibility() { + func testDispatchQueueSelection() { + let ex = expectation(description: "DispatchQueue compatibility") + let oldConf = PromiseKit.conf.D PromiseKit.conf.D = (map: dispatcher, return: dispatcher) + + DispatchQueue.global(qos: .background).setSpecific(key: queueIDKey, value: 100) + DispatchQueue.main.setSpecific(key: queueIDKey, value: 102) + dispatcher.queue.setSpecific(key: queueIDKey, value: 103) + Promise.value(42).map(on: .global(qos: .background), flags: .barrier) { (x: Int) -> Int in + let queueID = DispatchQueue.getSpecific(key: queueIDKey) + XCTAssertNotNil(queueID) + XCTAssertEqual(queueID!, 100) return x + 10 - }.then(on: .main, flags: []) { - XCTAssertEqual($0, 52) + }.then(on: .main, flags: []) { (x: Int) -> Promise in + XCTAssertEqual(x, 52) + let queueID = DispatchQueue.getSpecific(key: queueIDKey) + XCTAssertNotNil(queueID) + XCTAssertEqual(queueID!, 102) return Promise.value(50) - }.done(on: .global(qos: .userInitiated)) { - XCTAssertEqual($0, 50) + }.map(on: nil) { (x: Int) -> Int in + let queueID = DispatchQueue.getSpecific(key: queueIDKey) + XCTAssertNotNil(queueID) + XCTAssertEqual(queueID!, 102) + return x + 10 + }.map { (x: Int) -> Int in + XCTAssertEqual(x, 60) + let queueID = DispatchQueue.getSpecific(key: queueIDKey) + XCTAssertNotNil(queueID) + XCTAssertEqual(queueID!, 103) + return x + 10 + }.done(on: .global(qos: .background)) { + XCTAssertEqual($0, 70) + let queueID = DispatchQueue.getSpecific(key: queueIDKey) + XCTAssertNotNil(queueID) + XCTAssertEqual(queueID!, 100) ex.fulfill() }.cauterize() + waitForExpectations(timeout: 1) - XCTAssertEqual(self.dispatcher.dispatchCount, 0) PromiseKit.conf.D = oldConf + } func testDispatcherPromiseExtension() { @@ -86,7 +124,7 @@ class DispatcherTests: XCTestCase { XCTAssertEqual($0, 42) XCTAssertEqual(self.dispatcher.dispatchCount, 2) ex.fulfill() - }.cauterize() + }.cauterize() waitForExpectations(timeout: 1) } From 9e17e7d6ae21bc8d94a71a4d06aea32f29355ee8 Mon Sep 17 00:00:00 2001 From: Garth Snyder Date: Wed, 20 Jun 2018 15:12:01 -0700 Subject: [PATCH 17/26] Generalize DispatchQueues to Dispatcher-protocol objects --- Sources/Guarantee.swift | 2 +- Sources/Promise.swift | 2 +- Tests/CorePromise/DispatcherTests.swift | 37 +++++++++++++------------ 3 files changed, 22 insertions(+), 19 deletions(-) diff --git a/Sources/Guarantee.swift b/Sources/Guarantee.swift index 5f387aee7..6518e577a 100644 --- a/Sources/Guarantee.swift +++ b/Sources/Guarantee.swift @@ -202,7 +202,7 @@ public extension Dispatcher { - Returns: A new `Guarantee` resolved by the result of the provided closure. - Note: There is no Promise/Thenable version of this due to Swift compiler ambiguity issues. */ - func guarantee(execute body: @escaping () -> T) -> Guarantee { + func dispatch(_: PMKNamespacer, _ body: @escaping () -> T) -> Guarantee { let rg = Guarantee(.pending) dispatch { rg.box.seal(body()) diff --git a/Sources/Promise.swift b/Sources/Promise.swift index 1daf1192b..3d4e375be 100644 --- a/Sources/Promise.swift +++ b/Sources/Promise.swift @@ -182,7 +182,7 @@ public extension Dispatcher { - Returns: A new `Promise` resolved by the result of the provided closure. - Note: There is no Promise/Thenable version of this due to Swift compiler ambiguity issues. */ - func promise(execute body: @escaping () throws -> T) -> Promise { + func dispatch(_: PMKNamespacer, _ body: @escaping () throws -> T) -> Promise { let promise = Promise(.pending) dispatch { do { diff --git a/Tests/CorePromise/DispatcherTests.swift b/Tests/CorePromise/DispatcherTests.swift index 6206ef10d..817ba9d3d 100644 --- a/Tests/CorePromise/DispatcherTests.swift +++ b/Tests/CorePromise/DispatcherTests.swift @@ -77,7 +77,8 @@ class DispatcherTests: XCTestCase { let oldConf = PromiseKit.conf.D PromiseKit.conf.D = (map: dispatcher, return: dispatcher) - DispatchQueue.global(qos: .background).setSpecific(key: queueIDKey, value: 100) + let background = DispatchQueue.global(qos: .background) + background.setSpecific(key: queueIDKey, value: 100) DispatchQueue.main.setSpecific(key: queueIDKey, value: 102) dispatcher.queue.setSpecific(key: queueIDKey, value: 103) @@ -103,7 +104,7 @@ class DispatcherTests: XCTestCase { XCTAssertNotNil(queueID) XCTAssertEqual(queueID!, 103) return x + 10 - }.done(on: .global(qos: .background)) { + }.done(on: background) { XCTAssertEqual($0, 70) let queueID = DispatchQueue.getSpecific(key: queueIDKey) XCTAssertNotNil(queueID) @@ -116,25 +117,27 @@ class DispatcherTests: XCTestCase { } - func testDispatcherPromiseExtension() { + @available(macOS 10.10, iOS 2.0, tvOS 10.0, watchOS 2.0, *) + func testDispatcherExtensionReturnsGuarantee() { let ex = expectation(description: "Dispatcher.promise") - dispatcher.promise { - return 42 - }.done(on: dispatcher) { - XCTAssertEqual($0, 42) - XCTAssertEqual(self.dispatcher.dispatchCount, 2) + dispatcher.dispatch(.promise) { () -> Int in + XCTAssertFalse(Thread.isMainThread) + return 1 + }.done { one in + XCTAssertEqual(one, 1) ex.fulfill() - }.cauterize() + } waitForExpectations(timeout: 1) } - - func testDispatcherGuaranteeExtension() { - let ex = expectation(description: "Dispatcher.guarantee") - dispatcher.guarantee { - return 42 - }.done(on: .main) { - XCTAssertEqual($0, 42) - XCTAssertEqual(self.dispatcher.dispatchCount, 1) + + @available(macOS 10.10, iOS 2.0, tvOS 10.0, watchOS 2.0, *) + func testDispatcherExtensionCanThrowInBody() { + let ex = expectation(description: "Dispatcher.promise") + dispatcher.dispatch(.promise) { () -> Int in + throw PMKError.badInput + }.done { _ in + XCTFail() + }.catch { _ in ex.fulfill() } waitForExpectations(timeout: 1) From 686a1e63a4d5000016029989bb44aae8727ecb74 Mon Sep 17 00:00:00 2001 From: Garth Snyder Date: Wed, 8 Aug 2018 15:59:54 -0700 Subject: [PATCH 18/26] Port Thenable.tap and Guarantee.get to Dispatcher objects --- Sources/Dispatcher.swift | 24 +++++++++++++++++++++++- Sources/Guarantee.swift | 4 ++-- Sources/Thenable.swift | 6 +++--- 3 files changed, 28 insertions(+), 6 deletions(-) diff --git a/Sources/Dispatcher.swift b/Sources/Dispatcher.swift index 2d7a244b8..b6407ebf5 100644 --- a/Sources/Dispatcher.swift +++ b/Sources/Dispatcher.swift @@ -85,6 +85,11 @@ public extension Guarantee { 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) + return get(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) return map(on: dispatcher, body) @@ -213,11 +218,28 @@ public extension Thenable { }.done { foo in print(foo, " is Void") } - */ + */ func get(on: DispatchQueue? = .pmkDefault, flags: DispatchWorkItemFlags? = nil, _ body: @escaping (T) throws -> Void) -> Promise { let dispatcher = selectDispatcher(given: on, configured: conf.D.return, flags: flags) return get(on: dispatcher, body) } + + /** + The provided closure is executed with promise result. + + This is like `get` but provides the Result of the Promise so you can inspect the value of the chain at this point without causing any side effects. + + - Parameter on: The queue to which the provided closure dispatches. + - 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. For example: + + promise.tap{ print($0) }.then{ /*…*/ } + */ + func tap(on: DispatchQueue? = .pmkDefault, flags: DispatchWorkItemFlags? = nil, _ body: @escaping(Result) -> Void) -> Promise { + let dispatcher = selectDispatcher(given: on, configured: conf.D.map, flags: flags) + return tap(on: dispatcher, body) + } + } public extension Thenable where T: Sequence { diff --git a/Sources/Guarantee.swift b/Sources/Guarantee.swift index 6518e577a..6b771f652 100644 --- a/Sources/Guarantee.swift +++ b/Sources/Guarantee.swift @@ -77,8 +77,8 @@ public extension Guarantee { return rg } - func get(on: DispatchQueue? = conf.Q.return, flags: DispatchWorkItemFlags? = nil, _ body: @escaping (T) -> Void) -> Guarantee { - return map(on: on, flags: flags) { + func get(on: Dispatcher = conf.D.return, _ body: @escaping (T) -> Void) -> Guarantee { + return map(on: on) { body($0) return $0 } diff --git a/Sources/Thenable.swift b/Sources/Thenable.swift index 0cb935bdf..b3a1f340e 100644 --- a/Sources/Thenable.swift +++ b/Sources/Thenable.swift @@ -193,16 +193,16 @@ public extension Thenable { This is like `get` but provides the Result of the Promise so you can inspect the value of the chain at this point without causing any side effects. - - Parameter on: The queue to which the provided closure dispatches. + - Parameter on: The dispatcher that executes the provided closure. - 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. For example: promise.tap{ print($0) }.then{ /*…*/ } */ - func tap(on: DispatchQueue? = conf.Q.map, flags: DispatchWorkItemFlags? = nil, _ body: @escaping(Result) -> Void) -> Promise { + func tap(on: Dispatcher = conf.D.map, _ body: @escaping(Result) -> Void) -> Promise { return Promise { seal in pipe { result in - on.async(flags: flags) { + on.dispatch { body(result) seal.resolve(result) } From d1a38a56717e0ca633739f80025e6ad411aabbee Mon Sep 17 00:00:00 2001 From: Neal Lester Date: Fri, 14 Dec 2018 08:47:17 -0800 Subject: [PATCH 19/26] =?UTF-8?q?Fixed=20bug=20where=20pendingPromiseDeall?= =?UTF-8?q?ocated=20event=20was=20incorrectly=20repor=E2=80=A6=20(#972)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Fixed bug where pendingPromiseDeallocated event was incorrectly reported as a waitOnMainThread event. Added test which should have caught this bug in the first place. * Kick Travis CI --- Sources/Resolver.swift | 2 +- Tests/CorePromise/LoggingTests.swift | 22 ++++++++++++++++++++++ 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/Sources/Resolver.swift b/Sources/Resolver.swift index 953ece2eb..297de0325 100644 --- a/Sources/Resolver.swift +++ b/Sources/Resolver.swift @@ -8,7 +8,7 @@ public final class Resolver { deinit { if case .pending = box.inspect() { - conf.logHandler(.waitOnMainThread) + conf.logHandler(.pendingPromiseDeallocated) } } } diff --git a/Tests/CorePromise/LoggingTests.swift b/Tests/CorePromise/LoggingTests.swift index 742e4a75c..e0b3b06a3 100644 --- a/Tests/CorePromise/LoggingTests.swift +++ b/Tests/CorePromise/LoggingTests.swift @@ -153,5 +153,27 @@ class LoggingTests: XCTestCase { XCTAssertEqual(logOutput!, "waitOnMainThread") } + // Verify pendingPromiseDeallocated is logged + func testPendingPromiseDeallocatedIsLogged() { + + var logOutput: String? = nil + conf.logHandler = { event in + switch event { + case .waitOnMainThread: + logOutput = "\(event)" + case .pendingPromiseDeallocated: + logOutput = "\(event)" + case .cauterized: + // Using an enum with associated value does not convert to a string properly in + // earlier versions of swift + logOutput = "cauterized" + } + } + do { + let _ = Promise.pending() + } + XCTAssertEqual ("pendingPromiseDeallocated", logOutput!) + } + //TODO Verify pending promise deallocation is logged } From 0e90203d6cd748b043cedd0772502181d2710f5c Mon Sep 17 00:00:00 2001 From: Max Howell Date: Fri, 14 Dec 2018 22:18:26 +0000 Subject: [PATCH 20/26] Update ObjectiveC.md --- Documentation/ObjectiveC.md | 84 ++++++++++++++++++++++--------------- 1 file changed, 51 insertions(+), 33 deletions(-) diff --git a/Documentation/ObjectiveC.md b/Documentation/ObjectiveC.md index bbc8aa165..2e5f6e85b 100644 --- a/Documentation/ObjectiveC.md +++ b/Documentation/ObjectiveC.md @@ -49,39 +49,6 @@ myPromise.then(^{ //… }); ``` -#### :warning: Caution: -ARC in Objective-C, unlike in Objective-C++, is not exception-safe by default. -So, throwing an error will result in keeping a strong reference to the closure -that contains the throw statement. -This pattern will consequently result in memory leaks if you're not careful. - -> *Note:* Only having a strong reference to the closure would result in memory leaks. -> In our case, PromisKit automatically keeps a strong reference to the closure until it's released. - -__Workarounds:__ -1. Return a Promise with value NSError\ -Instead of throwing a normal error, you can return a Promise with value NSError instead. - -```objc -myPromise.then(^{ - return [AnyPromise promiseWithValue:[NSError myCustomError]]; -}).catch(^(NSError *error){ - if ([error isEqual:[NSError myCustomError]]) { - // In case, same error as the one we thrown - return; - } - //… -}); -``` -2. Enable ARC for exceptions in Objective-C (not recomended)\ -You can add this ```-fobjc-arc-exceptions to your``` to your compiler flags to enable ARC for exceptions. -This is not recommended unless you've read the Apple documentation and are comfortable with the caveats. - -For more details on ARC and exceptions: -https://clang.llvm.org/docs/AutomaticReferenceCounting.html#exceptions - - ---- One important feature is the syntactic flexability of your handlers: @@ -199,3 +166,54 @@ Perfect. Note that AnyPromise can only bridge objects that conform to `AnyObject` or derive from `NSObject`. This is a limitation of Objective-C. +# Using ObjC AnyPromises from Swift + +Simply use them, the type of your handler parameter is `Any`: + +```objective-c +- (AnyPromise *)fetchThings { + return [AnyPromise promiseWithValue:@[@"a", @"b", @"c"]]; +} +``` + +Since ObjC is not type-safe and Swift is, you will (probably) need to cast the `Any` to whatever it is you actually are feeding: + +```swift +Foo.fetchThings().done { any in + let bar = any as! [String] +} +``` + +## :warning: Caution: + +ARC in Objective-C, unlike in Objective-C++, is not exception-safe by default. +So, throwing an error will result in keeping a strong reference to the closure +that contains the throw statement. +This pattern will consequently result in memory leaks if you're not careful. + +> *Note:* Only having a strong reference to the closure would result in memory leaks. +> In our case, PromisKit automatically keeps a strong reference to the closure until it's released. + +__Workarounds:__ + +1. Return a Promise with value NSError\ +Instead of throwing a normal error, you can return a Promise with value NSError instead. + +```objc +myPromise.then(^{ + return [AnyPromise promiseWithValue:[NSError myCustomError]]; +}).catch(^(NSError *error){ + if ([error isEqual:[NSError myCustomError]]) { + // In case, same error as the one we thrown + return; + } + //… +}); +``` +2. Enable ARC for exceptions in Objective-C (not recomended)\ +You can add this ```-fobjc-arc-exceptions to your``` to your compiler flags to enable ARC for exceptions. +This is not recommended unless you've read the Apple documentation and are comfortable with the caveats. + +For more details on ARC and exceptions: +https://clang.llvm.org/docs/AutomaticReferenceCounting.html#exceptions + From 84f35e5bd5b0addc5cdda893156d9fa462eff54c Mon Sep 17 00:00:00 2001 From: Garth Snyder Date: Fri, 14 Dec 2018 21:45:43 -0800 Subject: [PATCH 21/26] Add explicit import for Dispatch in tests (needed for Travis builds) --- Tests/CorePromise/DispatcherTests.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/Tests/CorePromise/DispatcherTests.swift b/Tests/CorePromise/DispatcherTests.swift index 817ba9d3d..55c44141d 100644 --- a/Tests/CorePromise/DispatcherTests.swift +++ b/Tests/CorePromise/DispatcherTests.swift @@ -1,3 +1,4 @@ +import Dispatch import PromiseKit import XCTest From 1136163e1ff04f0d8a0fdad6ff780eeb368c4ead Mon Sep 17 00:00:00 2001 From: Garth Snyder Date: Sun, 16 Dec 2018 14:53:35 -0800 Subject: [PATCH 22/26] Travis-build all branches in Travis TRAVIS_BRANCHES env variable --- .travis.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index 1c2d57925..24cd001ba 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,10 +1,10 @@ stages: - name: lint - if: branch = master OR branch =~ ^\d+\.\d+\.\d+$ + if: branch = master OR branch =~ ^\d+\.\d+\.\d+$ OR branch =~ concat("^(",env(TRAVIS_BRANCHES),")$") - name: compile - if: branch = master OR branch =~ ^\d+\.\d+\.\d+$ + if: branch = master OR branch =~ ^\d+\.\d+\.\d+$ OR branch =~ concat("^(",env(TRAVIS_BRANCHES),")$") - name: test - if: branch = master OR branch =~ ^\d+\.\d+\.\d+$ + if: branch = master OR branch =~ ^\d+\.\d+\.\d+$ OR branch =~ concat("^(",env(TRAVIS_BRANCHES),")$") - name: deploy if: branch =~ ^\d+\.\d+\.\d+$ jobs: From 8ae5bfa30491b2df9c4850ae81816f9e6282bc22 Mon Sep 17 00:00:00 2001 From: Garth Snyder Date: Sun, 16 Dec 2018 14:55:19 -0800 Subject: [PATCH 23/26] v7 Travis: Swift 3 only on latest compiler, sync Linux test/build --- .travis.yml | 132 ++++++++++++++++++++++++---------------------------- 1 file changed, 61 insertions(+), 71 deletions(-) diff --git a/.travis.yml b/.travis.yml index 24cd001ba..7ac877c03 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,48 +7,22 @@ stages: if: branch = master OR branch =~ ^\d+\.\d+\.\d+$ OR branch =~ concat("^(",env(TRAVIS_BRANCHES),")$") - name: deploy if: branch =~ ^\d+\.\d+\.\d+$ + jobs: include: - - &carthage - stage: compile - osx_image: xcode8.3 - name: Carthage / Xcode 8.3 - os: osx - language: objective-c - script: carthage build --no-skip-current --configuration Release - - <<: *carthage - osx_image: xcode9.2 - name: Carthage / Xcode 9.2 - - <<: *carthage - osx_image: xcode9.4 - name: Carthage / Xcode 9.4 - - <<: *carthage - osx_image: xcode10.1 - name: Carthage / Xcode 10.0 + # Lint stage - &pod stage: lint - osx_image: xcode8.3 - env: SWIFT=3.1 - name: pod lib lint --swift-version=3.1 + osx_image: xcode10.1 + env: SWIFT=3.4 + name: pod lib lint --swift-version=3.4 os: osx cache: cocoapods language: objective-c before_install: mv .github/PromiseKit.podspec . install: gem install cocoapods --prerelease --version 1.6.0.beta.2 script: pod lib lint --subspec=PromiseKit/CorePromise --fail-fast --swift-version=$SWIFT - - <<: *pod - osx_image: xcode9.2 - env: SWIFT=3.2 - name: pod lib lint --swift-version=3.2 - - <<: *pod - osx_image: xcode9.4 - env: SWIFT=3.3 - name: pod lib lint --swift-version=3.3 - - <<: *pod - osx_image: xcode10.1 - env: SWIFT=3.4 - name: pod lib lint --swift-version=3.4 - <<: *pod osx_image: xcode9.2 env: SWIFT=4.0 @@ -62,10 +36,25 @@ jobs: env: SWIFT=4.2 name: pod lib lint --swift-version=4.2 + # Compile stage - Carthage macOS builds + - &carthage + stage: compile + osx_image: xcode10.1 + name: Carthage / Xcode 10.1 (Swift 3.4) + env: SWIFT_VERSION=3.4 + os: osx + language: objective-c + script: carthage build --no-skip-current --configuration Release + - <<: *carthage + osx_image: xcode10.1 + env: SWIFT_VERSION=4.2 + name: Carthage / Xcode 10.1 (Swift 4.2) + + # Compile and test stages - Linux builds - &linux stage: compile - env: SWIFT_BUILD_VERSION=3 SWIFT_VERSION=4.0.3 - name: Linux / Swift 3.2 + env: SWIFT_BUILD_VERSION=4 SWIFT_VERSION=4.2.1 + name: Linux / Swift 4.2 (compile) os: linux dist: trusty sudo: required @@ -74,29 +63,50 @@ jobs: install: swift build -Xswiftc -swift-version -Xswiftc $SWIFT_BUILD_VERSION script: "true" - <<: *linux - env: SWIFT_BUILD_VERSION=3 SWIFT_VERSION=4.1.2 - name: Linux / Swift 3.3 + name: Linux / Swift 4.2 (test) + stage: test + script: swift test -Xswiftc -swift-version -Xswiftc $SWIFT_BUILD_VERSION - <<: *linux - env: SWIFT_BUILD_VERSION=3 SWIFT_VERSION=4.2.1 - name: Linux / Swift 3.4 + name: Linux / Swift 4.1 (compile) + env: SWIFT_BUILD_VERSION=4 SWIFT_VERSION=4.1.2 + - <<: *linux + stage: test + name: Linux / Swift 4.1 (test) + env: SWIFT_BUILD_VERSION=4 SWIFT_VERSION=4.1.2 + script: swift test -Xswiftc -swift-version -Xswiftc $SWIFT_BUILD_VERSION - <<: *linux + name: Linux / Swift 4.0 (compile) env: SWIFT_BUILD_VERSION=4 SWIFT_VERSION=4.0.3 - name: Linux / Swift 4.0 + - <<: *linux stage: test - script: swift test -Xswiftc -swift-version -Xswiftc 4 + name: Linux / Swift 4.0 (test) + env: SWIFT_BUILD_VERSION=4 SWIFT_VERSION=4.0.3 + script: swift test -Xswiftc -swift-version -Xswiftc $SWIFT_BUILD_VERSION - <<: *linux - env: SWIFT_BUILD_VERSION=4 SWIFT_VERSION=4.1.2 - name: Linux / Swift 4.1 + name: Linux / Swift 3 on tools 4.2.1 (compile) + env: SWIFT_BUILD_VERSION=3 SWIFT_VERSION=4.2.1 - <<: *linux - env: SWIFT_BUILD_VERSION=4 SWIFT_VERSION=4.2.1 - name: Linux / Swift 4.2 stage: test - script: swift test -Xswiftc -swift-version -Xswiftc 4 + name: Linux / Swift 3 on tools 4.2.1 (test) + env: SWIFT_BUILD_VERSION=3 SWIFT_VERSION=4.2.1 + script: swift test -Xswiftc -swift-version -Xswiftc $SWIFT_BUILD_VERSION + # Compile stage - SwiftPM builds + - &swiftpm + stage: compile + name: SwiftPM / macOS / Xcode 9.4 + os: osx + osx_image: xcode9.4 + script: swift build + - <<: *swiftpm + osx_image: xcode10.1 + name: SwiftPM / macOS / Xcode 10.1 + + # Test stage - macOS, iOS, and tvOS - &test stage: test - osx_image: xcode8.3 - name: macOS / Xcode 8.3 + osx_image: xcode10.1 + name: macOS / Xcode 10.1 (Swift 3) os: osx language: objective-c env: DST='platform=OS X,arch=x86_64' @@ -110,31 +120,20 @@ jobs: after_success: bash <(curl -s https://codecov.io/bash) - <<: *test - name: macOS / Xcode 9.2 - osx_image: xcode9.2 - script: - xcodebuild -scheme PromiseKit -destination="$DST" -enableCodeCoverage YES SWIFT_VERSION=4 test | xcpretty - - <<: *test - name: macOS / Xcode 9.4 - osx_image: xcode9.4 - script: - xcodebuild -scheme PromiseKit -destination="$DST" -enableCodeCoverage YES SWIFT_VERSION=4 test | xcpretty - - <<: *test - name: macOS / Xcode 10.0 + name: macOS / Xcode 10.1 (Swift 4) osx_image: xcode10.1 script: xcodebuild -scheme PromiseKit -destination="$DST" -enableCodeCoverage YES SWIFT_VERSION=4 test | xcpretty - - <<: *test - name: iOS / Xcode 10.0 + name: iOS / Xcode 10.1 osx_image: xcode10.1 env: DST='OS=12.0,name=iPhone SE' - - <<: *test - name: tvOS / Xcode 10.0 + name: tvOS / Xcode 10.1 env: DST='OS=12.0,name=Apple TV' osx_image: xcode10.1 + # Test stage - Promises/A+ - stage: test name: Promises/A+ (via WebKit JavaScript Bridge) language: objective-c @@ -155,16 +154,7 @@ jobs: - Tests/JS-A+/build - Tests/JS-A+/node_modules - - &swiftpm - stage: compile - name: SwiftPM / macOS / Xcode 9.4 - os: osx - osx_image: xcode9.4 - script: swift build - - <<: *swiftpm - osx_image: xcode10.1 - name: SwiftPM / macOS / Xcode 10.0 - + # Deployment stage - stage: deploy script: | set -eo pipefail From 46894156bcbaab03316680ddda95c6c3cccadc44 Mon Sep 17 00:00:00 2001 From: Garth Snyder Date: Sun, 16 Dec 2018 14:52:44 -0800 Subject: [PATCH 24/26] Save artifacts (env must define ARTIFACTS_* for KEY, SECRET, REGION, BUCKET) --- .travis.yml | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/.travis.yml b/.travis.yml index 7ac877c03..365bf8d0e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,3 +1,12 @@ +addons: + artifacts: + # To collect artifacts, define ARTIFACTS_KEY, ARTIFACTS_SECRET, ARTIFACTS_REGION, and + # ARTIFACTS_BUCKET in the Travis environment. (AWS S3 is currently the only option.) + paths: + # Collect build directory contents and logs if configured to do so in the environment, + # but skip the Promises/A+ products because there are a lot. Also omit some binaries. + - $(find /var/folders -name '*carthage-xcodebuild*' -print 2>/dev/null | tr "\n" ":") + - $(if [ `git ls-files -o | grep build.js | wc -l` -eq 0 ]; then git ls-files -o | grep -v x86_64- | tr "\n" ":" ; fi) stages: - name: lint if: branch = master OR branch =~ ^\d+\.\d+\.\d+$ OR branch =~ concat("^(",env(TRAVIS_BRANCHES),")$") From f877a396fb5de6c561c9602e84f67438718e4ba9 Mon Sep 17 00:00:00 2001 From: Garth Snyder Date: Mon, 17 Dec 2018 20:52:25 -0800 Subject: [PATCH 25/26] Disable artifact collection by default --- .travis.yml | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/.travis.yml b/.travis.yml index 365bf8d0e..e6c20cc43 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,10 +1,12 @@ addons: - artifacts: - # To collect artifacts, define ARTIFACTS_KEY, ARTIFACTS_SECRET, ARTIFACTS_REGION, and - # ARTIFACTS_BUCKET in the Travis environment. (AWS S3 is currently the only option.) + # To enable build artifact collection on S3, change "artifacts_disabled" below to "artifacts". + # Then define ARTIFACTS_KEY, ARTIFACTS_SECRET, ARTIFACTS_REGION, and ARTIFACTS_BUCKET in the + # Travis environment. If you enable artifacts here but do not define the configuration + # parameters, some jobs will fail. + artifacts_disabled: paths: # Collect build directory contents and logs if configured to do so in the environment, - # but skip the Promises/A+ products because there are a lot. Also omit some binaries. + # but skip the Promises/A+ products because there are a lot. Also omit some larger binaries. - $(find /var/folders -name '*carthage-xcodebuild*' -print 2>/dev/null | tr "\n" ":") - $(if [ `git ls-files -o | grep build.js | wc -l` -eq 0 ]; then git ls-files -o | grep -v x86_64- | tr "\n" ":" ; fi) stages: From 68763cf30161fb0a5e254de867488f13ca7cc5ef Mon Sep 17 00:00:00 2001 From: Garth Snyder Date: Mon, 17 Dec 2018 23:18:50 -0800 Subject: [PATCH 26/26] Revert commits 0e902 and d1a38; these were rebased in from master --- Documentation/ObjectiveC.md | 84 +++++++++++----------------- Sources/Resolver.swift | 2 +- Tests/CorePromise/LoggingTests.swift | 22 -------- 3 files changed, 34 insertions(+), 74 deletions(-) diff --git a/Documentation/ObjectiveC.md b/Documentation/ObjectiveC.md index 2e5f6e85b..bbc8aa165 100644 --- a/Documentation/ObjectiveC.md +++ b/Documentation/ObjectiveC.md @@ -49,6 +49,39 @@ myPromise.then(^{ //… }); ``` +#### :warning: Caution: +ARC in Objective-C, unlike in Objective-C++, is not exception-safe by default. +So, throwing an error will result in keeping a strong reference to the closure +that contains the throw statement. +This pattern will consequently result in memory leaks if you're not careful. + +> *Note:* Only having a strong reference to the closure would result in memory leaks. +> In our case, PromisKit automatically keeps a strong reference to the closure until it's released. + +__Workarounds:__ +1. Return a Promise with value NSError\ +Instead of throwing a normal error, you can return a Promise with value NSError instead. + +```objc +myPromise.then(^{ + return [AnyPromise promiseWithValue:[NSError myCustomError]]; +}).catch(^(NSError *error){ + if ([error isEqual:[NSError myCustomError]]) { + // In case, same error as the one we thrown + return; + } + //… +}); +``` +2. Enable ARC for exceptions in Objective-C (not recomended)\ +You can add this ```-fobjc-arc-exceptions to your``` to your compiler flags to enable ARC for exceptions. +This is not recommended unless you've read the Apple documentation and are comfortable with the caveats. + +For more details on ARC and exceptions: +https://clang.llvm.org/docs/AutomaticReferenceCounting.html#exceptions + + +--- One important feature is the syntactic flexability of your handlers: @@ -166,54 +199,3 @@ Perfect. Note that AnyPromise can only bridge objects that conform to `AnyObject` or derive from `NSObject`. This is a limitation of Objective-C. -# Using ObjC AnyPromises from Swift - -Simply use them, the type of your handler parameter is `Any`: - -```objective-c -- (AnyPromise *)fetchThings { - return [AnyPromise promiseWithValue:@[@"a", @"b", @"c"]]; -} -``` - -Since ObjC is not type-safe and Swift is, you will (probably) need to cast the `Any` to whatever it is you actually are feeding: - -```swift -Foo.fetchThings().done { any in - let bar = any as! [String] -} -``` - -## :warning: Caution: - -ARC in Objective-C, unlike in Objective-C++, is not exception-safe by default. -So, throwing an error will result in keeping a strong reference to the closure -that contains the throw statement. -This pattern will consequently result in memory leaks if you're not careful. - -> *Note:* Only having a strong reference to the closure would result in memory leaks. -> In our case, PromisKit automatically keeps a strong reference to the closure until it's released. - -__Workarounds:__ - -1. Return a Promise with value NSError\ -Instead of throwing a normal error, you can return a Promise with value NSError instead. - -```objc -myPromise.then(^{ - return [AnyPromise promiseWithValue:[NSError myCustomError]]; -}).catch(^(NSError *error){ - if ([error isEqual:[NSError myCustomError]]) { - // In case, same error as the one we thrown - return; - } - //… -}); -``` -2. Enable ARC for exceptions in Objective-C (not recomended)\ -You can add this ```-fobjc-arc-exceptions to your``` to your compiler flags to enable ARC for exceptions. -This is not recommended unless you've read the Apple documentation and are comfortable with the caveats. - -For more details on ARC and exceptions: -https://clang.llvm.org/docs/AutomaticReferenceCounting.html#exceptions - diff --git a/Sources/Resolver.swift b/Sources/Resolver.swift index 297de0325..953ece2eb 100644 --- a/Sources/Resolver.swift +++ b/Sources/Resolver.swift @@ -8,7 +8,7 @@ public final class Resolver { deinit { if case .pending = box.inspect() { - conf.logHandler(.pendingPromiseDeallocated) + conf.logHandler(.waitOnMainThread) } } } diff --git a/Tests/CorePromise/LoggingTests.swift b/Tests/CorePromise/LoggingTests.swift index e0b3b06a3..742e4a75c 100644 --- a/Tests/CorePromise/LoggingTests.swift +++ b/Tests/CorePromise/LoggingTests.swift @@ -153,27 +153,5 @@ class LoggingTests: XCTestCase { XCTAssertEqual(logOutput!, "waitOnMainThread") } - // Verify pendingPromiseDeallocated is logged - func testPendingPromiseDeallocatedIsLogged() { - - var logOutput: String? = nil - conf.logHandler = { event in - switch event { - case .waitOnMainThread: - logOutput = "\(event)" - case .pendingPromiseDeallocated: - logOutput = "\(event)" - case .cauterized: - // Using an enum with associated value does not convert to a string properly in - // earlier versions of swift - logOutput = "cauterized" - } - } - do { - let _ = Promise.pending() - } - XCTAssertEqual ("pendingPromiseDeallocated", logOutput!) - } - //TODO Verify pending promise deallocation is logged }