├── .gitignore ├── CancellablePromiseKit.podspec ├── CancellablePromiseKit └── Classes │ ├── .gitkeep │ ├── CancellablePromise.swift │ ├── CancellablePromiseError.swift │ ├── Promise+CancellablePromise.swift │ ├── map.swift │ ├── race.swift │ ├── then.swift │ └── when.swift ├── Example ├── CancellablePromiseKit.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ └── contents.xcworkspacedata │ └── xcshareddata │ │ └── xcschemes │ │ └── CancellablePromiseKit-Example.xcscheme ├── CancellablePromiseKit.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist ├── Podfile ├── Podfile.lock └── Tests │ ├── Info.plist │ ├── InitializerTests.swift │ ├── MapTests.swift │ ├── RaceTests.swift │ ├── ThenTests.swift │ ├── ThenableTests.swift │ ├── WhenFulfilledTests.swift │ ├── WhenResolvedTests.swift │ └── WhenTests.swift ├── LICENSE ├── README.md └── _Pods.xcodeproj /.gitignore: -------------------------------------------------------------------------------- 1 | Pods/ 2 | 3 | xcuserdata 4 | 5 | *.xccheckout 6 | *.moved-aside 7 | *.xcuserstate 8 | *.xcscmblueprint -------------------------------------------------------------------------------- /CancellablePromiseKit.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = 'CancellablePromiseKit' 3 | s.version = '0.3.0' 4 | s.swift_version = '5.0' 5 | s.summary = 'Extends the amazing PromiseKit to cover cancellable tasks' 6 | 7 | s.description = <<-DESC 8 | CancellablePromiseKit is an extension for PromiseKit. A Promise is an abstraction of an asynchonous 9 | operation that can succeed or fail. A `CancellablePromise`, provided by this library, extends this 10 | concept to represent tasks that can be cancelled/aborted. 11 | DESC 12 | 13 | s.homepage = 'https://github.com/johannesd/CancellablePromiseKit' 14 | # s.screenshots = 'www.example.com/screenshots_1', 'www.example.com/screenshots_2' 15 | s.license = { :type => 'MIT', :file => 'LICENSE' } 16 | s.author = { 'Johannes Dörr' => 'mail@johannesdoerr.de' } 17 | s.source = { :git => 'https://github.com/johannesd/CancellablePromiseKit.git', :tag => s.version.to_s } 18 | s.social_media_url = 'https://twitter.com/johdoerr' 19 | 20 | s.ios.deployment_target = '8.0' 21 | s.tvos.deployment_target = '9.0' 22 | 23 | s.source_files = 'CancellablePromiseKit/Classes/**/*' 24 | 25 | # s.resource_bundles = { 26 | # 'CancellablePromiseKit' => ['CancellablePromiseKit/Assets/*.png'] 27 | # } 28 | 29 | # s.public_header_files = 'Pod/Classes/**/*.h' 30 | s.dependency 'PromiseKit', '~> 6' 31 | end 32 | -------------------------------------------------------------------------------- /CancellablePromiseKit/Classes/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johannesd/CancellablePromiseKit/30de5b26aea209c511b6a45a14978fb447d38e5a/CancellablePromiseKit/Classes/.gitkeep -------------------------------------------------------------------------------- /CancellablePromiseKit/Classes/CancellablePromise.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CancellablePromise.swift 3 | // CancellablePromiseKit 4 | // 5 | // Created by Johannes Dörr on 11.05.18. 6 | // Copyright © 2018 Johannes Dörr. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import PromiseKit 11 | 12 | public class CancellablePromise { 13 | 14 | /** 15 | Returns: True if this promise has been cancelled 16 | */ 17 | public private(set) var isCancelled: Bool = false 18 | 19 | internal var subsequentCancels = [() -> ()]() 20 | 21 | private let promise: Promise 22 | 23 | /** 24 | Returns the undelying promise 25 | */ 26 | public func asPromise() -> Promise { 27 | return promise 28 | } 29 | 30 | private let cancelPromise: Promise 31 | private let cancelResolver: Resolver 32 | private let cancelFunction: (() -> ()) 33 | 34 | /** 35 | Aborts the execution of the underlying task 36 | */ 37 | public func cancel() { 38 | isCancelled = true 39 | subsequentCancels.forEach { $0() } 40 | if isPending { 41 | cancelFunction() 42 | // Let already scheduled promise blocks (like `then`) execute first, before rejecting: 43 | (conf.Q.map ?? DispatchQueue.main).async { 44 | self.cancelResolver.reject(CancellablePromiseError.cancelled) 45 | } 46 | } 47 | } 48 | 49 | internal init(_ body: (_ cancelPromise: Promise) -> Promise, cancel: @escaping () -> ()) { 50 | (self.cancelPromise, self.cancelResolver) = Promise.pending() 51 | self.promise = when(body(cancelPromise), while: cancelPromise) 52 | cancelFunction = cancel 53 | } 54 | 55 | public convenience init(using promise: Promise, cancel: @escaping () -> ()) { 56 | self.init({ _ in promise }, cancel: cancel) 57 | } 58 | 59 | public convenience init(resolver body: (Resolver) throws -> (() -> ())) { 60 | let (promise, resolver) = Promise.pending() 61 | do { 62 | let cancel = try body(resolver) 63 | self.init(using: promise, cancel: cancel) 64 | } catch let error { 65 | resolver.reject(error) 66 | self.init(using: promise, cancel: { }) 67 | } 68 | } 69 | 70 | public convenience init(wrapper body: (_ cancelPromise: Promise) -> Promise) { 71 | self.init(body, cancel: { }) 72 | } 73 | 74 | deinit { 75 | // Prevent PromiseKit's warning that a pending promise has been deinited: 76 | let resolver = cancelResolver 77 | (conf.Q.map ?? DispatchQueue.main).async { 78 | resolver.fulfill(Void()) 79 | } 80 | } 81 | 82 | } 83 | 84 | extension CancellablePromise: Thenable, CatchMixin { 85 | 86 | public func pipe(to: @escaping (Result) -> Void) { 87 | asPromise().pipe(to: to) 88 | } 89 | 90 | public var result: Result? { 91 | return asPromise().result 92 | } 93 | 94 | } 95 | 96 | internal func cancelAll(`in` array: [CancellablePromise]) { 97 | array.forEach { (cancellablePromise) in 98 | cancellablePromise.cancel() 99 | } 100 | } 101 | 102 | -------------------------------------------------------------------------------- /CancellablePromiseKit/Classes/CancellablePromiseError.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CancellablePromiseError.swift 3 | // CancellablePromiseKit 4 | // 5 | // Created by Johannes Dörr on 18.05.18. 6 | // 7 | 8 | import PromiseKit 9 | 10 | public enum CancellablePromiseError: Swift.Error, CancellableError { 11 | case cancelled 12 | } 13 | 14 | extension CancellablePromiseError { 15 | 16 | public var isCancelled: Bool { 17 | switch self { 18 | case .cancelled: 19 | return true 20 | } 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /CancellablePromiseKit/Classes/Promise+CancellablePromise.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Promise+CancellablePromise.swift 3 | // CancellablePromiseKit 4 | // 5 | // Created by Johannes Dörr on 18.05.18. 6 | // 7 | 8 | import PromiseKit 9 | 10 | extension Promise { 11 | 12 | /** 13 | Returns a CancellablePromise. 14 | */ 15 | public func asCancellable() -> CancellablePromise { 16 | return CancellablePromise(wrapper: { cancelPromise in 17 | return when(self, while: cancelPromise) 18 | }) 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /CancellablePromiseKit/Classes/map.swift: -------------------------------------------------------------------------------- 1 | // 2 | // map.swift 3 | // CancellablePromiseKit 4 | // 5 | // Created by Johannes Dörr on 04.07.18. 6 | // 7 | 8 | import Foundation 9 | import PromiseKit 10 | 11 | extension CancellablePromise { 12 | 13 | public func map(on: DispatchQueue? = conf.Q.map, _ transform: @escaping(T) throws -> U) -> CancellablePromise { 14 | return CancellablePromise(using: self.asPromise().map(on: on, transform), cancel: cancel) 15 | } 16 | 17 | public func asVoid() -> CancellablePromise { 18 | return map(on: nil) { _ in } 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /CancellablePromiseKit/Classes/race.swift: -------------------------------------------------------------------------------- 1 | // 2 | // race.swift 3 | // CancellablePromiseKit 4 | // 5 | // Created by Johannes Dörr on 13.05.18. 6 | // Copyright © 2018 Johannes Dörr. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import PromiseKit 11 | 12 | /** 13 | Parameter cancellablePromises: The promises to wait for 14 | Parameter autoCancel: Specifies if the other provided promises should be cancelled when one of them fulfills, or when the returned promise is cancelled 15 | */ 16 | public func race(_ cancellablePromises: [CancellablePromise], autoCancel: Bool) -> CancellablePromise { 17 | return CancellablePromise { (cancelPromise) -> Promise in 18 | let promise = race(cancellablePromises.map{ $0.asPromise() }) 19 | return when(promise, while: cancelPromise).ensure { 20 | if autoCancel { 21 | cancelAll(in: cancellablePromises) 22 | } 23 | } 24 | } 25 | } 26 | 27 | public func race(_ cancellablePromises: [CancellablePromise]) -> CancellablePromise { 28 | return race(cancellablePromises, autoCancel: false) 29 | } 30 | -------------------------------------------------------------------------------- /CancellablePromiseKit/Classes/then.swift: -------------------------------------------------------------------------------- 1 | // 2 | // then.swift 3 | // CancellablePromiseKit 4 | // 5 | // Created by Johannes Dörr on 17.05.18. 6 | // Copyright © 2018 Johannes Dörr. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import PromiseKit 11 | 12 | extension CancellablePromise { 13 | 14 | public func then(on: DispatchQueue? = conf.Q.map, file: StaticString = #file, line: UInt = #line, _ body: @escaping(T) throws -> CancellablePromise) -> CancellablePromise { 15 | let promise: Promise = then { (value) -> Promise in 16 | let cancellablePromise = try body(value) 17 | if self.isCancelled { 18 | cancellablePromise.cancel() 19 | } 20 | self.subsequentCancels.append(cancellablePromise.cancel) 21 | return cancellablePromise.asPromise() 22 | } 23 | let cancellablePromise = CancellablePromise(using: promise, cancel: cancel) 24 | return cancellablePromise 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /CancellablePromiseKit/Classes/when.swift: -------------------------------------------------------------------------------- 1 | // 2 | // when.swift 3 | // CancellablePromiseKit 4 | // 5 | // Created by Johannes Dörr on 13.05.18. 6 | // Copyright © 2018 Johannes Dörr. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import PromiseKit 11 | 12 | /** 13 | Wait for promise, but abort if conditionPromise fails 14 | - Parameter promise: The promise to expect 15 | - Parameter conditionPromise: If rejected, the waiting will abort 16 | - Parameter ensured: If true, conditionPromise must not be rejected at the time the returned promise is evaluated 17 | */ 18 | public func when(_ promise: Promise, while conditionPromise: Promise, isEnsured: Bool = false) -> Promise { 19 | return when(fulfilled: [promise.asVoid(), race([promise.asVoid(), conditionPromise.asVoid()])]).map({ _ -> T in 20 | if isEnsured && conditionPromise.isRejected { 21 | throw CancellablePromiseError.cancelled 22 | } 23 | return promise.value! 24 | }) 25 | } 26 | 27 | /** 28 | Wait for promise, but abort if conditionPromise fails 29 | - Parameter promise: The promise to expect 30 | - Parameter conditionPromise: If rejected, the waiting will abort 31 | - Parameter ensured: If true, conditionPromise must not be rejected at the time the returned promise is evaluated 32 | - Parameter autoCancel: Specifies if `cancellablePromise` should be cancelled when `conditionPromise` is rejected or when the returned promise is cancelled 33 | */ 34 | public func when(_ cancellablePromise: CancellablePromise, while conditionPromise: Promise, isEnsured: Bool = false, autoCancel: Bool = false) -> Promise { 35 | return when(fulfilled: [cancellablePromise.asVoid(), race([cancellablePromise.asVoid(), conditionPromise.asVoid()])]).map({ _ -> T in 36 | if isEnsured && conditionPromise.isRejected { 37 | throw CancellablePromiseError.cancelled 38 | } 39 | return cancellablePromise.value! 40 | }).ensure { 41 | if autoCancel { 42 | cancellablePromise.cancel() 43 | } 44 | } 45 | } 46 | 47 | /** 48 | Parameter cancellablePromises: The promises to wait for 49 | Parameter autoCancel: Specifies if the provided promises should be cancelled when one of them rejects, or when the returned promise is cancelled 50 | */ 51 | public func when(fulfilled cancellablePromises: [CancellablePromise], autoCancel: Bool) -> CancellablePromise<[T]> { 52 | return CancellablePromise { (cancelPromise) -> Promise<[T]> in 53 | let promise = when(fulfilled: cancellablePromises.map{ $0.asPromise() }) 54 | return when(promise, while: cancelPromise).ensure { 55 | if autoCancel { 56 | cancelAll(in: cancellablePromises) 57 | } 58 | } 59 | } 60 | } 61 | 62 | public func when(fulfilled cancellablePromises: [CancellablePromise]) -> CancellablePromise<[T]> { 63 | return when(fulfilled: cancellablePromises, autoCancel: false) 64 | } 65 | 66 | /** 67 | Parameter cancellablePromises: The promises to wait for 68 | Parameter autoCancel: Specifies if the provided promises should be cancelled when the returned promise is cancelled 69 | */ 70 | public func when(resolved cancellablePromises: [CancellablePromise], autoCancel: Bool) -> CancellablePromise<[Result]> { 71 | return CancellablePromise { (cancelPromise) -> Promise<[Result]> in 72 | let guarantee = when(resolved: cancellablePromises.map{ $0.asPromise() }) 73 | let promise = Promise { guarantee.done($0.fulfill) } 74 | return when(promise, while: cancelPromise).ensure { 75 | if autoCancel { 76 | cancelAll(in: cancellablePromises) 77 | } 78 | } 79 | } 80 | } 81 | 82 | public func when(resolved cancellablePromises: [CancellablePromise]) -> CancellablePromise<[Result]> { 83 | return when(resolved: cancellablePromises, autoCancel: false) 84 | } 85 | -------------------------------------------------------------------------------- /Example/CancellablePromiseKit.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 8AA710B40AFAC01AFBA40B96 /* Pods_CancellablePromiseKit_Tests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D1F3D68B8145FA0E4261A7F4 /* Pods_CancellablePromiseKit_Tests.framework */; }; 11 | 8F0FDFB920AF790800012FD0 /* RaceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8F0FDFB420AF790800012FD0 /* RaceTests.swift */; }; 12 | 8F0FDFBA20AF790800012FD0 /* WhenTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8F0FDFB520AF790800012FD0 /* WhenTests.swift */; }; 13 | 8F0FDFBB20AF790800012FD0 /* WhenResolvedTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8F0FDFB620AF790800012FD0 /* WhenResolvedTests.swift */; }; 14 | 8F0FDFBC20AF790800012FD0 /* WhenFulfilledTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8F0FDFB720AF790800012FD0 /* WhenFulfilledTests.swift */; }; 15 | 8F0FDFBD20AF790800012FD0 /* InitializerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8F0FDFB820AF790800012FD0 /* InitializerTests.swift */; }; 16 | 8F0FDFBF20AF7D9F00012FD0 /* ThenableTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8F0FDFBE20AF7D9F00012FD0 /* ThenableTests.swift */; }; 17 | 8F0FDFC120AF7DE500012FD0 /* ThenTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8F0FDFC020AF7DE500012FD0 /* ThenTests.swift */; }; 18 | 8F8F7FEE20ED1DE700FDAFF2 /* MapTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8F8F7FED20ED1DE700FDAFF2 /* MapTests.swift */; }; 19 | /* End PBXBuildFile section */ 20 | 21 | /* Begin PBXFileReference section */ 22 | 5F666A081B7A30EB7F52982D /* Pods-CancellablePromiseKit_Tests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-CancellablePromiseKit_Tests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-CancellablePromiseKit_Tests/Pods-CancellablePromiseKit_Tests.debug.xcconfig"; sourceTree = ""; }; 23 | 607FACE51AFB9204008FA782 /* CancellablePromiseKit_Tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = CancellablePromiseKit_Tests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 24 | 607FACEA1AFB9204008FA782 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 25 | 8F0FDFB420AF790800012FD0 /* RaceTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RaceTests.swift; sourceTree = ""; }; 26 | 8F0FDFB520AF790800012FD0 /* WhenTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WhenTests.swift; sourceTree = ""; }; 27 | 8F0FDFB620AF790800012FD0 /* WhenResolvedTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WhenResolvedTests.swift; sourceTree = ""; }; 28 | 8F0FDFB720AF790800012FD0 /* WhenFulfilledTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WhenFulfilledTests.swift; sourceTree = ""; }; 29 | 8F0FDFB820AF790800012FD0 /* InitializerTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InitializerTests.swift; sourceTree = ""; }; 30 | 8F0FDFBE20AF7D9F00012FD0 /* ThenableTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThenableTests.swift; sourceTree = ""; }; 31 | 8F0FDFC020AF7DE500012FD0 /* ThenTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThenTests.swift; sourceTree = ""; }; 32 | 8F8F7FED20ED1DE700FDAFF2 /* MapTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapTests.swift; sourceTree = ""; }; 33 | BEE08EDB204A47FA0CEB77F3 /* README.md */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = net.daringfireball.markdown; name = README.md; path = ../README.md; sourceTree = ""; }; 34 | CAB1A0B1395BF75D0E71F6C7 /* LICENSE */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text; name = LICENSE; path = ../LICENSE; sourceTree = ""; }; 35 | D1F3D68B8145FA0E4261A7F4 /* Pods_CancellablePromiseKit_Tests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_CancellablePromiseKit_Tests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 36 | D6930DB2A43E4B199760CAE1 /* Pods-CancellablePromiseKit_Tests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-CancellablePromiseKit_Tests.release.xcconfig"; path = "Pods/Target Support Files/Pods-CancellablePromiseKit_Tests/Pods-CancellablePromiseKit_Tests.release.xcconfig"; sourceTree = ""; }; 37 | E8B015231F53EC898E94B7E6 /* CancellablePromiseKit.podspec */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text; name = CancellablePromiseKit.podspec; path = ../CancellablePromiseKit.podspec; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.ruby; }; 38 | /* End PBXFileReference section */ 39 | 40 | /* Begin PBXFrameworksBuildPhase section */ 41 | 607FACE21AFB9204008FA782 /* Frameworks */ = { 42 | isa = PBXFrameworksBuildPhase; 43 | buildActionMask = 2147483647; 44 | files = ( 45 | 8AA710B40AFAC01AFBA40B96 /* Pods_CancellablePromiseKit_Tests.framework in Frameworks */, 46 | ); 47 | runOnlyForDeploymentPostprocessing = 0; 48 | }; 49 | /* End PBXFrameworksBuildPhase section */ 50 | 51 | /* Begin PBXGroup section */ 52 | 607FACC71AFB9204008FA782 = { 53 | isa = PBXGroup; 54 | children = ( 55 | 607FACF51AFB993E008FA782 /* Podspec Metadata */, 56 | 607FACE81AFB9204008FA782 /* Tests */, 57 | 607FACD11AFB9204008FA782 /* Products */, 58 | F90B96EC916CCB1DBA0DBFB5 /* Pods */, 59 | 7DF22CBB87095AFDDC73CCBB /* Frameworks */, 60 | ); 61 | sourceTree = ""; 62 | }; 63 | 607FACD11AFB9204008FA782 /* Products */ = { 64 | isa = PBXGroup; 65 | children = ( 66 | 607FACE51AFB9204008FA782 /* CancellablePromiseKit_Tests.xctest */, 67 | ); 68 | name = Products; 69 | sourceTree = ""; 70 | }; 71 | 607FACE81AFB9204008FA782 /* Tests */ = { 72 | isa = PBXGroup; 73 | children = ( 74 | 8F8F7FED20ED1DE700FDAFF2 /* MapTests.swift */, 75 | 8F0FDFB820AF790800012FD0 /* InitializerTests.swift */, 76 | 8F0FDFBE20AF7D9F00012FD0 /* ThenableTests.swift */, 77 | 8F0FDFB420AF790800012FD0 /* RaceTests.swift */, 78 | 8F0FDFB720AF790800012FD0 /* WhenFulfilledTests.swift */, 79 | 8F0FDFB620AF790800012FD0 /* WhenResolvedTests.swift */, 80 | 8F0FDFB520AF790800012FD0 /* WhenTests.swift */, 81 | 8F0FDFC020AF7DE500012FD0 /* ThenTests.swift */, 82 | 607FACE91AFB9204008FA782 /* Supporting Files */, 83 | ); 84 | path = Tests; 85 | sourceTree = ""; 86 | }; 87 | 607FACE91AFB9204008FA782 /* Supporting Files */ = { 88 | isa = PBXGroup; 89 | children = ( 90 | 607FACEA1AFB9204008FA782 /* Info.plist */, 91 | ); 92 | name = "Supporting Files"; 93 | sourceTree = ""; 94 | }; 95 | 607FACF51AFB993E008FA782 /* Podspec Metadata */ = { 96 | isa = PBXGroup; 97 | children = ( 98 | E8B015231F53EC898E94B7E6 /* CancellablePromiseKit.podspec */, 99 | BEE08EDB204A47FA0CEB77F3 /* README.md */, 100 | CAB1A0B1395BF75D0E71F6C7 /* LICENSE */, 101 | ); 102 | name = "Podspec Metadata"; 103 | sourceTree = ""; 104 | }; 105 | 7DF22CBB87095AFDDC73CCBB /* Frameworks */ = { 106 | isa = PBXGroup; 107 | children = ( 108 | D1F3D68B8145FA0E4261A7F4 /* Pods_CancellablePromiseKit_Tests.framework */, 109 | ); 110 | name = Frameworks; 111 | sourceTree = ""; 112 | }; 113 | F90B96EC916CCB1DBA0DBFB5 /* Pods */ = { 114 | isa = PBXGroup; 115 | children = ( 116 | 5F666A081B7A30EB7F52982D /* Pods-CancellablePromiseKit_Tests.debug.xcconfig */, 117 | D6930DB2A43E4B199760CAE1 /* Pods-CancellablePromiseKit_Tests.release.xcconfig */, 118 | ); 119 | name = Pods; 120 | sourceTree = ""; 121 | }; 122 | /* End PBXGroup section */ 123 | 124 | /* Begin PBXNativeTarget section */ 125 | 607FACE41AFB9204008FA782 /* CancellablePromiseKit_Tests */ = { 126 | isa = PBXNativeTarget; 127 | buildConfigurationList = 607FACF21AFB9204008FA782 /* Build configuration list for PBXNativeTarget "CancellablePromiseKit_Tests" */; 128 | buildPhases = ( 129 | F1699D65114280FBE850534E /* [CP] Check Pods Manifest.lock */, 130 | 607FACE11AFB9204008FA782 /* Sources */, 131 | 607FACE21AFB9204008FA782 /* Frameworks */, 132 | 607FACE31AFB9204008FA782 /* Resources */, 133 | 619958D9BA8E03AE4CD9321F /* [CP] Embed Pods Frameworks */, 134 | ); 135 | buildRules = ( 136 | ); 137 | dependencies = ( 138 | ); 139 | name = CancellablePromiseKit_Tests; 140 | productName = Tests; 141 | productReference = 607FACE51AFB9204008FA782 /* CancellablePromiseKit_Tests.xctest */; 142 | productType = "com.apple.product-type.bundle.unit-test"; 143 | }; 144 | /* End PBXNativeTarget section */ 145 | 146 | /* Begin PBXProject section */ 147 | 607FACC81AFB9204008FA782 /* Project object */ = { 148 | isa = PBXProject; 149 | attributes = { 150 | LastSwiftUpdateCheck = 0830; 151 | LastUpgradeCheck = 1020; 152 | ORGANIZATIONNAME = CocoaPods; 153 | TargetAttributes = { 154 | 607FACE41AFB9204008FA782 = { 155 | CreatedOnToolsVersion = 6.3.1; 156 | DevelopmentTeam = BHX8R35CB6; 157 | LastSwiftMigration = 1010; 158 | TestTargetID = 607FACCF1AFB9204008FA782; 159 | }; 160 | }; 161 | }; 162 | buildConfigurationList = 607FACCB1AFB9204008FA782 /* Build configuration list for PBXProject "CancellablePromiseKit" */; 163 | compatibilityVersion = "Xcode 3.2"; 164 | developmentRegion = en; 165 | hasScannedForEncodings = 0; 166 | knownRegions = ( 167 | en, 168 | Base, 169 | ); 170 | mainGroup = 607FACC71AFB9204008FA782; 171 | productRefGroup = 607FACD11AFB9204008FA782 /* Products */; 172 | projectDirPath = ""; 173 | projectRoot = ""; 174 | targets = ( 175 | 607FACE41AFB9204008FA782 /* CancellablePromiseKit_Tests */, 176 | ); 177 | }; 178 | /* End PBXProject section */ 179 | 180 | /* Begin PBXResourcesBuildPhase section */ 181 | 607FACE31AFB9204008FA782 /* Resources */ = { 182 | isa = PBXResourcesBuildPhase; 183 | buildActionMask = 2147483647; 184 | files = ( 185 | ); 186 | runOnlyForDeploymentPostprocessing = 0; 187 | }; 188 | /* End PBXResourcesBuildPhase section */ 189 | 190 | /* Begin PBXShellScriptBuildPhase section */ 191 | 619958D9BA8E03AE4CD9321F /* [CP] Embed Pods Frameworks */ = { 192 | isa = PBXShellScriptBuildPhase; 193 | buildActionMask = 2147483647; 194 | files = ( 195 | ); 196 | inputPaths = ( 197 | "${SRCROOT}/Pods/Target Support Files/Pods-CancellablePromiseKit_Tests/Pods-CancellablePromiseKit_Tests-frameworks.sh", 198 | "${BUILT_PRODUCTS_DIR}/CancellablePromiseKit/CancellablePromiseKit.framework", 199 | "${BUILT_PRODUCTS_DIR}/PromiseKit/PromiseKit.framework", 200 | ); 201 | name = "[CP] Embed Pods Frameworks"; 202 | outputPaths = ( 203 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/CancellablePromiseKit.framework", 204 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/PromiseKit.framework", 205 | ); 206 | runOnlyForDeploymentPostprocessing = 0; 207 | shellPath = /bin/sh; 208 | shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-CancellablePromiseKit_Tests/Pods-CancellablePromiseKit_Tests-frameworks.sh\"\n"; 209 | showEnvVarsInLog = 0; 210 | }; 211 | F1699D65114280FBE850534E /* [CP] Check Pods Manifest.lock */ = { 212 | isa = PBXShellScriptBuildPhase; 213 | buildActionMask = 2147483647; 214 | files = ( 215 | ); 216 | inputPaths = ( 217 | "${PODS_PODFILE_DIR_PATH}/Podfile.lock", 218 | "${PODS_ROOT}/Manifest.lock", 219 | ); 220 | name = "[CP] Check Pods Manifest.lock"; 221 | outputPaths = ( 222 | "$(DERIVED_FILE_DIR)/Pods-CancellablePromiseKit_Tests-checkManifestLockResult.txt", 223 | ); 224 | runOnlyForDeploymentPostprocessing = 0; 225 | shellPath = /bin/sh; 226 | shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; 227 | showEnvVarsInLog = 0; 228 | }; 229 | /* End PBXShellScriptBuildPhase section */ 230 | 231 | /* Begin PBXSourcesBuildPhase section */ 232 | 607FACE11AFB9204008FA782 /* Sources */ = { 233 | isa = PBXSourcesBuildPhase; 234 | buildActionMask = 2147483647; 235 | files = ( 236 | 8F0FDFB920AF790800012FD0 /* RaceTests.swift in Sources */, 237 | 8F0FDFBA20AF790800012FD0 /* WhenTests.swift in Sources */, 238 | 8F8F7FEE20ED1DE700FDAFF2 /* MapTests.swift in Sources */, 239 | 8F0FDFBF20AF7D9F00012FD0 /* ThenableTests.swift in Sources */, 240 | 8F0FDFBD20AF790800012FD0 /* InitializerTests.swift in Sources */, 241 | 8F0FDFBC20AF790800012FD0 /* WhenFulfilledTests.swift in Sources */, 242 | 8F0FDFC120AF7DE500012FD0 /* ThenTests.swift in Sources */, 243 | 8F0FDFBB20AF790800012FD0 /* WhenResolvedTests.swift in Sources */, 244 | ); 245 | runOnlyForDeploymentPostprocessing = 0; 246 | }; 247 | /* End PBXSourcesBuildPhase section */ 248 | 249 | /* Begin XCBuildConfiguration section */ 250 | 607FACED1AFB9204008FA782 /* Debug */ = { 251 | isa = XCBuildConfiguration; 252 | buildSettings = { 253 | ALWAYS_SEARCH_USER_PATHS = NO; 254 | CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; 255 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 256 | CLANG_CXX_LIBRARY = "libc++"; 257 | CLANG_ENABLE_MODULES = YES; 258 | CLANG_ENABLE_OBJC_ARC = YES; 259 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 260 | CLANG_WARN_BOOL_CONVERSION = YES; 261 | CLANG_WARN_COMMA = YES; 262 | CLANG_WARN_CONSTANT_CONVERSION = YES; 263 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 264 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 265 | CLANG_WARN_EMPTY_BODY = YES; 266 | CLANG_WARN_ENUM_CONVERSION = YES; 267 | CLANG_WARN_INFINITE_RECURSION = YES; 268 | CLANG_WARN_INT_CONVERSION = YES; 269 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 270 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 271 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 272 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 273 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 274 | CLANG_WARN_STRICT_PROTOTYPES = YES; 275 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 276 | CLANG_WARN_UNREACHABLE_CODE = YES; 277 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 278 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 279 | COPY_PHASE_STRIP = NO; 280 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 281 | ENABLE_STRICT_OBJC_MSGSEND = YES; 282 | ENABLE_TESTABILITY = YES; 283 | GCC_C_LANGUAGE_STANDARD = gnu99; 284 | GCC_DYNAMIC_NO_PIC = NO; 285 | GCC_NO_COMMON_BLOCKS = YES; 286 | GCC_OPTIMIZATION_LEVEL = 0; 287 | GCC_PREPROCESSOR_DEFINITIONS = ( 288 | "DEBUG=1", 289 | "$(inherited)", 290 | ); 291 | GCC_SYMBOLS_PRIVATE_EXTERN = NO; 292 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 293 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 294 | GCC_WARN_UNDECLARED_SELECTOR = YES; 295 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 296 | GCC_WARN_UNUSED_FUNCTION = YES; 297 | GCC_WARN_UNUSED_VARIABLE = YES; 298 | IPHONEOS_DEPLOYMENT_TARGET = 9.3; 299 | MTL_ENABLE_DEBUG_INFO = YES; 300 | ONLY_ACTIVE_ARCH = YES; 301 | SDKROOT = iphoneos; 302 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 303 | }; 304 | name = Debug; 305 | }; 306 | 607FACEE1AFB9204008FA782 /* Release */ = { 307 | isa = XCBuildConfiguration; 308 | buildSettings = { 309 | ALWAYS_SEARCH_USER_PATHS = NO; 310 | CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; 311 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 312 | CLANG_CXX_LIBRARY = "libc++"; 313 | CLANG_ENABLE_MODULES = YES; 314 | CLANG_ENABLE_OBJC_ARC = YES; 315 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 316 | CLANG_WARN_BOOL_CONVERSION = YES; 317 | CLANG_WARN_COMMA = YES; 318 | CLANG_WARN_CONSTANT_CONVERSION = YES; 319 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 320 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 321 | CLANG_WARN_EMPTY_BODY = YES; 322 | CLANG_WARN_ENUM_CONVERSION = YES; 323 | CLANG_WARN_INFINITE_RECURSION = YES; 324 | CLANG_WARN_INT_CONVERSION = YES; 325 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 326 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 327 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 328 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 329 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 330 | CLANG_WARN_STRICT_PROTOTYPES = YES; 331 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 332 | CLANG_WARN_UNREACHABLE_CODE = YES; 333 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 334 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 335 | COPY_PHASE_STRIP = NO; 336 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 337 | ENABLE_NS_ASSERTIONS = NO; 338 | ENABLE_STRICT_OBJC_MSGSEND = YES; 339 | GCC_C_LANGUAGE_STANDARD = gnu99; 340 | GCC_NO_COMMON_BLOCKS = YES; 341 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 342 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 343 | GCC_WARN_UNDECLARED_SELECTOR = YES; 344 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 345 | GCC_WARN_UNUSED_FUNCTION = YES; 346 | GCC_WARN_UNUSED_VARIABLE = YES; 347 | IPHONEOS_DEPLOYMENT_TARGET = 9.3; 348 | MTL_ENABLE_DEBUG_INFO = NO; 349 | SDKROOT = iphoneos; 350 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 351 | VALIDATE_PRODUCT = YES; 352 | }; 353 | name = Release; 354 | }; 355 | 607FACF31AFB9204008FA782 /* Debug */ = { 356 | isa = XCBuildConfiguration; 357 | baseConfigurationReference = 5F666A081B7A30EB7F52982D /* Pods-CancellablePromiseKit_Tests.debug.xcconfig */; 358 | buildSettings = { 359 | DEVELOPMENT_TEAM = BHX8R35CB6; 360 | FRAMEWORK_SEARCH_PATHS = ( 361 | "$(SDKROOT)/Developer/Library/Frameworks", 362 | "$(inherited)", 363 | ); 364 | GCC_PREPROCESSOR_DEFINITIONS = ( 365 | "DEBUG=1", 366 | "$(inherited)", 367 | ); 368 | INFOPLIST_FILE = Tests/Info.plist; 369 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 370 | PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.$(PRODUCT_NAME:rfc1034identifier)"; 371 | PRODUCT_NAME = "$(TARGET_NAME)"; 372 | SWIFT_VERSION = 5.0; 373 | }; 374 | name = Debug; 375 | }; 376 | 607FACF41AFB9204008FA782 /* Release */ = { 377 | isa = XCBuildConfiguration; 378 | baseConfigurationReference = D6930DB2A43E4B199760CAE1 /* Pods-CancellablePromiseKit_Tests.release.xcconfig */; 379 | buildSettings = { 380 | DEVELOPMENT_TEAM = BHX8R35CB6; 381 | FRAMEWORK_SEARCH_PATHS = ( 382 | "$(SDKROOT)/Developer/Library/Frameworks", 383 | "$(inherited)", 384 | ); 385 | INFOPLIST_FILE = Tests/Info.plist; 386 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 387 | PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.$(PRODUCT_NAME:rfc1034identifier)"; 388 | PRODUCT_NAME = "$(TARGET_NAME)"; 389 | SWIFT_VERSION = 5.0; 390 | }; 391 | name = Release; 392 | }; 393 | /* End XCBuildConfiguration section */ 394 | 395 | /* Begin XCConfigurationList section */ 396 | 607FACCB1AFB9204008FA782 /* Build configuration list for PBXProject "CancellablePromiseKit" */ = { 397 | isa = XCConfigurationList; 398 | buildConfigurations = ( 399 | 607FACED1AFB9204008FA782 /* Debug */, 400 | 607FACEE1AFB9204008FA782 /* Release */, 401 | ); 402 | defaultConfigurationIsVisible = 0; 403 | defaultConfigurationName = Release; 404 | }; 405 | 607FACF21AFB9204008FA782 /* Build configuration list for PBXNativeTarget "CancellablePromiseKit_Tests" */ = { 406 | isa = XCConfigurationList; 407 | buildConfigurations = ( 408 | 607FACF31AFB9204008FA782 /* Debug */, 409 | 607FACF41AFB9204008FA782 /* Release */, 410 | ); 411 | defaultConfigurationIsVisible = 0; 412 | defaultConfigurationName = Release; 413 | }; 414 | /* End XCConfigurationList section */ 415 | }; 416 | rootObject = 607FACC81AFB9204008FA782 /* Project object */; 417 | } 418 | -------------------------------------------------------------------------------- /Example/CancellablePromiseKit.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Example/CancellablePromiseKit.xcodeproj/xcshareddata/xcschemes/CancellablePromiseKit-Example.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 29 | 35 | 36 | 37 | 38 | 39 | 44 | 45 | 47 | 53 | 54 | 55 | 56 | 57 | 63 | 64 | 65 | 66 | 67 | 68 | 78 | 80 | 86 | 87 | 88 | 89 | 90 | 91 | 97 | 99 | 105 | 106 | 107 | 108 | 110 | 111 | 114 | 115 | 116 | -------------------------------------------------------------------------------- /Example/CancellablePromiseKit.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /Example/CancellablePromiseKit.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Example/Podfile: -------------------------------------------------------------------------------- 1 | use_frameworks! 2 | target 'CancellablePromiseKit_Tests' do 3 | pod 'CancellablePromiseKit', :path => '../' 4 | 5 | 6 | end 7 | -------------------------------------------------------------------------------- /Example/Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - CancellablePromiseKit (0.3.0): 3 | - PromiseKit (~> 6) 4 | - PromiseKit (6.10.0): 5 | - PromiseKit/CorePromise (= 6.10.0) 6 | - PromiseKit/Foundation (= 6.10.0) 7 | - PromiseKit/UIKit (= 6.10.0) 8 | - PromiseKit/CorePromise (6.10.0) 9 | - PromiseKit/Foundation (6.10.0): 10 | - PromiseKit/CorePromise 11 | - PromiseKit/UIKit (6.10.0): 12 | - PromiseKit/CorePromise 13 | 14 | DEPENDENCIES: 15 | - CancellablePromiseKit (from `../`) 16 | 17 | SPEC REPOS: 18 | https://github.com/cocoapods/specs.git: 19 | - PromiseKit 20 | 21 | EXTERNAL SOURCES: 22 | CancellablePromiseKit: 23 | :path: "../" 24 | 25 | SPEC CHECKSUMS: 26 | CancellablePromiseKit: 869c2408bc7db8b7dddb4a8c8f3924249d9f83a0 27 | PromiseKit: 1fdaeb6c0a94a5114fcb814ff3d772b86886ad4e 28 | 29 | PODFILE CHECKSUM: 83a80d9070039d3b9f62f1f5a633c2a2549f43fe 30 | 31 | COCOAPODS: 1.5.3 32 | -------------------------------------------------------------------------------- /Example/Tests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | 24 | 25 | -------------------------------------------------------------------------------- /Example/Tests/InitializerTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CancellablePromiseTests.swift 3 | // CancellablePromiseKit 4 | // 5 | // Created by Johannes Dörr on 12.05.18. 6 | // Copyright © 2018 Johannes Dörr. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | import PromiseKit 11 | import CancellablePromiseKit 12 | 13 | class InitializerTests: XCTestCase { 14 | 15 | var testPromise: CancellablePromise! 16 | 17 | override func tearDown() { 18 | super.tearDown() 19 | testPromise = nil 20 | } 21 | 22 | func testCancelFunc() { 23 | let expectation = self.expectation(description: "Cancel function was not called") 24 | 25 | let promise = Promise.pending().promise 26 | func cancelFunc() { 27 | expectation.fulfill() 28 | } 29 | testPromise = CancellablePromise(using: promise, cancel: cancelFunc) 30 | testPromise.cancel() 31 | 32 | wait(for: [expectation], timeout: 0.1) 33 | } 34 | 35 | func testCancelPromise() { 36 | let rejectionExpectation = expectation(description: "Cancel promise was not rejected") 37 | let fulfillmentExpectation = expectation(description: "CancelPromise was fulfilled") 38 | fulfillmentExpectation.isInverted = true 39 | 40 | testPromise = CancellablePromise { (cancelPromise) -> Promise in 41 | cancelPromise.done { 42 | fulfillmentExpectation.fulfill() 43 | }.catch(policy: .allErrors) { (error) in 44 | rejectionExpectation.fulfill() 45 | } 46 | return Promise.pending().promise 47 | } 48 | testPromise.cancel() 49 | 50 | wait(for: [rejectionExpectation, fulfillmentExpectation], timeout: 0.1) 51 | } 52 | 53 | func testDeinitPromise() { 54 | let rejectionExpectation = expectation(description: "Cancel promise was rejected") 55 | rejectionExpectation.isInverted = true 56 | let fulfillmentExpectation = expectation(description: "CancelPromise was not fulfilled") 57 | 58 | testPromise = CancellablePromise { (cancelPromise) -> Promise in 59 | cancelPromise.done { 60 | fulfillmentExpectation.fulfill() 61 | }.catch(policy: .allErrors) { (error) in 62 | rejectionExpectation.fulfill() 63 | } 64 | return Promise.pending().promise 65 | } 66 | testPromise = nil 67 | 68 | wait(for: [rejectionExpectation, fulfillmentExpectation], timeout: 0.1) 69 | } 70 | 71 | } 72 | -------------------------------------------------------------------------------- /Example/Tests/MapTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MapTests.swift 3 | // CancellablePromiseKit_Tests 4 | // 5 | // Created by Johannes Dörr on 04.07.18. 6 | // Copyright © 2018 CocoaPods. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | import PromiseKit 11 | import CancellablePromiseKit 12 | 13 | class MapTests: XCTestCase { 14 | 15 | var testPromise: CancellablePromise! 16 | var testResolver: Resolver! 17 | 18 | override func setUp() { 19 | super.setUp() 20 | let (basePromise, resolver) = Promise.pending() 21 | testPromise = CancellablePromise(using: basePromise, cancel: { resolver.reject(CancellablePromiseError.cancelled) }) 22 | testResolver = resolver 23 | } 24 | 25 | override func tearDown() { 26 | super.tearDown() 27 | testPromise = nil 28 | testResolver = nil 29 | } 30 | 31 | func testMap() { 32 | let expectation = self.expectation(description: "Value was not mapped correctly") 33 | let testValue = "test" 34 | 35 | _ = testPromise.map({ (value) -> String in 36 | return value + value 37 | }).done { (mappedValue) in 38 | if mappedValue == testValue + testValue { 39 | expectation.fulfill() 40 | } 41 | } 42 | 43 | testResolver.fulfill(testValue) 44 | 45 | wait(for: [expectation], timeout: 0.1) 46 | } 47 | 48 | func testCancel() { 49 | let rejectionExpectation = expectation(description: "Cancel promise was not rejected") 50 | let fulfillmentExpectation = expectation(description: "CancelPromise was fulfilled") 51 | fulfillmentExpectation.isInverted = true 52 | 53 | _ = testPromise.map({ (value) -> String in 54 | return value + value 55 | }).done({ (mappedValue) in 56 | fulfillmentExpectation.fulfill() 57 | }).catch(policy: .allErrors) { (error) in 58 | rejectionExpectation.fulfill() 59 | } 60 | 61 | testPromise.cancel() 62 | 63 | wait(for: [rejectionExpectation, fulfillmentExpectation], timeout: 0.1) 64 | } 65 | 66 | } 67 | -------------------------------------------------------------------------------- /Example/Tests/RaceTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RaceTests.swift 3 | // CancellablePromiseKit 4 | // 5 | // Created by Johannes Dörr on 13.05.18. 6 | // Copyright © 2018 Johannes Dörr. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | import PromiseKit 11 | import CancellablePromiseKit 12 | 13 | class RaceTests: XCTestCase { 14 | 15 | var testPromise1: PromiseAndResolver! 16 | var testPromise2: PromiseAndResolver! 17 | 18 | class Error: Swift.Error { } 19 | 20 | override func setUp() { 21 | super.setUp() 22 | testPromise1 = createPromise() 23 | testPromise2 = createPromise() 24 | } 25 | 26 | override func tearDown() { 27 | super.tearDown() 28 | testPromise1 = nil 29 | testPromise2 = nil 30 | } 31 | 32 | func testFulfilled() { 33 | let expectation = self.expectation(description: "Promise was not fulfilled") 34 | 35 | let testValue1 = "test1" 36 | let cancellablePromise = race([testPromise1.promise, testPromise2.promise]) 37 | _ = cancellablePromise.done { (string) in 38 | if string == testValue1 { 39 | expectation.fulfill() 40 | } 41 | } 42 | testPromise1.resolver.fulfill(testValue1) 43 | 44 | wait(for: [expectation], timeout: 0.1) 45 | } 46 | 47 | func testRejected() { 48 | let expectation = self.expectation(description: "Promise was not rejected") 49 | 50 | let cancellablePromise = race([testPromise1.promise, testPromise2.promise]) 51 | _ = cancellablePromise.catch { (error) in 52 | expectation.fulfill() 53 | } 54 | testPromise2.resolver.reject(Error()) 55 | 56 | wait(for: [expectation], timeout: 0.1) 57 | } 58 | 59 | func testRejectedAutoCancelOther() { 60 | let expectation = self.expectation(description: "Promise was not rejected") 61 | let cancelExpectation = self.expectation(description: "Other Promise was not cancelled") 62 | 63 | self.testPromise1.promise.catch(policy: .allErrors) { error in 64 | switch error { 65 | case CancellablePromiseError.cancelled: 66 | cancelExpectation.fulfill() 67 | default: 68 | break 69 | } 70 | } 71 | 72 | let cancellablePromise = race([testPromise1.promise, testPromise2.promise], autoCancel: true) 73 | _ = cancellablePromise.catch { (error) in 74 | expectation.fulfill() 75 | } 76 | testPromise2.resolver.reject(Error()) 77 | 78 | wait(for: [expectation, cancelExpectation], timeout: 0.1) 79 | } 80 | 81 | func testCancel() { 82 | let expectation = self.expectation(description: "Promise was not cancelled") 83 | let cancelExpectation1 = self.expectation(description: "Promise 1 was cancelled") 84 | cancelExpectation1.isInverted = true 85 | let cancelExpectation2 = self.expectation(description: "Promise 1 was cancelled") 86 | cancelExpectation2.isInverted = true 87 | 88 | self.testPromise1.promise.catch(policy: .allErrors) { error in 89 | cancelExpectation1.fulfill() 90 | } 91 | self.testPromise2.promise.catch(policy: .allErrors) { error in 92 | cancelExpectation2.fulfill() 93 | } 94 | 95 | let cancellablePromise = race([testPromise1.promise, testPromise2.promise]) 96 | _ = cancellablePromise.catch(policy: .allErrors) { (error) in 97 | switch error { 98 | case CancellablePromiseError.cancelled: 99 | expectation.fulfill() 100 | default: 101 | break 102 | } 103 | } 104 | cancellablePromise.cancel() 105 | 106 | wait(for: [expectation, cancelExpectation1, cancelExpectation2], timeout: 0.1) 107 | } 108 | 109 | func testCancelAutoCancelOther() { 110 | let expectation = self.expectation(description: "Promise was not cancelled") 111 | let cancelExpectation1 = self.expectation(description: "Promise 1 was not cancelled") 112 | let cancelExpectation2 = self.expectation(description: "Promise 1 was not cancelled") 113 | 114 | self.testPromise1.promise.catch(policy: .allErrors) { error in 115 | switch error { 116 | case CancellablePromiseError.cancelled: 117 | cancelExpectation1.fulfill() 118 | default: 119 | break 120 | } 121 | } 122 | self.testPromise2.promise.catch(policy: .allErrors) { error in 123 | switch error { 124 | case CancellablePromiseError.cancelled: 125 | cancelExpectation2.fulfill() 126 | default: 127 | break 128 | } 129 | } 130 | 131 | let cancellablePromise = when(fulfilled: [testPromise1.promise, testPromise2.promise], autoCancel: true) 132 | _ = cancellablePromise.catch(policy: .allErrors) { (error) in 133 | switch error { 134 | case CancellablePromiseError.cancelled: 135 | expectation.fulfill() 136 | default: 137 | break 138 | } 139 | } 140 | cancellablePromise.cancel() 141 | 142 | wait(for: [expectation, cancelExpectation1, cancelExpectation2], timeout: 0.1) 143 | } 144 | 145 | } 146 | -------------------------------------------------------------------------------- /Example/Tests/ThenTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ThenTests.swift 3 | // CancellablePromiseKit_Tests 4 | // 5 | // Created by Johannes Dörr on 18.05.18. 6 | // Copyright © 2018 CocoaPods. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | import PromiseKit 11 | import CancellablePromiseKit 12 | 13 | class ThenTests: XCTestCase { 14 | 15 | var testPromise1: CancellablePromise! 16 | var testResolver1: Resolver! 17 | var testPromise2: CancellablePromise! 18 | var testResolver2: Resolver! 19 | var thenPromise: CancellablePromise! 20 | 21 | override func setUp() { 22 | super.setUp() 23 | let (basePromise1, resolver1) = Promise.pending() 24 | testPromise1 = CancellablePromise(using: basePromise1, cancel: { resolver1.reject(CancellablePromiseError.cancelled) }) 25 | testResolver1 = resolver1 26 | let (basePromise2, resolver2) = Promise.pending() 27 | testPromise2 = CancellablePromise(using: basePromise2, cancel: { resolver2.reject(CancellablePromiseError.cancelled) }) 28 | testResolver2 = resolver2 29 | thenPromise = testPromise1.then { value in 30 | return self.testPromise2 31 | } 32 | } 33 | 34 | override func tearDown() { 35 | super.tearDown() 36 | testPromise1 = nil 37 | testResolver1 = nil 38 | testPromise2 = nil 39 | testResolver2 = nil 40 | thenPromise = nil 41 | } 42 | 43 | func testFulfill() { 44 | let rejectionExpectation = expectation(description: "Promise was rejected") 45 | rejectionExpectation.isInverted = true 46 | let fulfillmentExpectation = expectation(description: "Promise was not fulfilled") 47 | 48 | thenPromise.done { _ in 49 | fulfillmentExpectation.fulfill() 50 | }.catch(policy: .allErrors) { (error) in 51 | rejectionExpectation.fulfill() 52 | } 53 | 54 | testResolver1.fulfill("") 55 | testResolver2.fulfill("") 56 | 57 | wait(for: [rejectionExpectation, fulfillmentExpectation], timeout: 0.1) 58 | } 59 | 60 | func testCancelFirst() { 61 | let cancellationExpectation = expectation(description: "Promises were not cancelled") 62 | let fulfillmentExpectation = expectation(description: "Promise was fulfilled") 63 | fulfillmentExpectation.isInverted = true 64 | 65 | thenPromise.done { _ in 66 | fulfillmentExpectation.fulfill() 67 | }.catch(policy: .allErrors) { (error) in 68 | if self.testPromise1.isCancelled && self.thenPromise.isCancelled { 69 | cancellationExpectation.fulfill() 70 | } 71 | } 72 | 73 | thenPromise.cancel() 74 | 75 | wait(for: [cancellationExpectation, fulfillmentExpectation], timeout: 0.1) 76 | } 77 | 78 | func testCancelSecond() { 79 | let cancellationExpectation = expectation(description: "Promises were not cancelled") 80 | let fulfillmentExpectation = expectation(description: "Promise was fulfilled") 81 | fulfillmentExpectation.isInverted = true 82 | 83 | thenPromise.done { value in 84 | fulfillmentExpectation.fulfill() 85 | }.catch(policy: .allErrors) { (error) in 86 | if self.testPromise1.isCancelled && self.testPromise2.isCancelled && self.thenPromise.isCancelled { 87 | cancellationExpectation.fulfill() 88 | } 89 | } 90 | testResolver1.fulfill("") 91 | thenPromise.cancel() 92 | 93 | wait(for: [cancellationExpectation, fulfillmentExpectation], timeout: 0.1) 94 | } 95 | 96 | func testCancelSecondWithDelay() { 97 | let cancellationExpectation = expectation(description: "Promises were not cancelled") 98 | let fulfillmentExpectation = expectation(description: "Promise was fulfilled") 99 | fulfillmentExpectation.isInverted = true 100 | 101 | thenPromise.done { value in 102 | fulfillmentExpectation.fulfill() 103 | }.catch(policy: .allErrors) { (error) in 104 | if self.testPromise1.isCancelled && self.testPromise2.isCancelled && self.thenPromise.isCancelled { 105 | cancellationExpectation.fulfill() 106 | } 107 | } 108 | testResolver1.fulfill("") 109 | DispatchQueue.main.async { 110 | self.thenPromise.cancel() 111 | } 112 | 113 | wait(for: [cancellationExpectation, fulfillmentExpectation], timeout: 0.1) 114 | } 115 | 116 | } 117 | -------------------------------------------------------------------------------- /Example/Tests/ThenableTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CancellablePromiseUsageTests.swift 3 | // CancellablePromiseKit 4 | // 5 | // Created by Johannes Dörr on 18.05.18. 6 | // Copyright © 2018 CocoaPods. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | import PromiseKit 11 | import CancellablePromiseKit 12 | 13 | class ThenableTests: XCTestCase { 14 | 15 | var testPromise: CancellablePromise! 16 | var testResolver: Resolver! 17 | 18 | override func setUp() { 19 | super.setUp() 20 | let (basePromise, resolver) = Promise.pending() 21 | testPromise = CancellablePromise(using: basePromise, cancel: { resolver.reject(CancellablePromiseError.cancelled) }) 22 | testResolver = resolver 23 | } 24 | 25 | override func tearDown() { 26 | super.tearDown() 27 | testPromise = nil 28 | testResolver = nil 29 | } 30 | 31 | func testFulfill() { 32 | let rejectionExpectation = expectation(description: "Promise was rejected") 33 | rejectionExpectation.isInverted = true 34 | let fulfillmentExpectation = expectation(description: "Promise was not fulfilled") 35 | 36 | testPromise.done { _ in 37 | fulfillmentExpectation.fulfill() 38 | }.catch(policy: .allErrors) { (error) in 39 | rejectionExpectation.fulfill() 40 | } 41 | 42 | testResolver.fulfill("") 43 | 44 | wait(for: [rejectionExpectation, fulfillmentExpectation], timeout: 0.1) 45 | } 46 | 47 | func testCancel() { 48 | let rejectionExpectation = expectation(description: "Promise was not rejected") 49 | let fulfillmentExpectation = expectation(description: "Promise was fulfilled") 50 | fulfillmentExpectation.isInverted = true 51 | 52 | testPromise.done { _ in 53 | fulfillmentExpectation.fulfill() 54 | }.catch(policy: .allErrors) { (error) in 55 | switch error { 56 | case CancellablePromiseError.cancelled: 57 | rejectionExpectation.fulfill() 58 | default: 59 | break 60 | } 61 | } 62 | 63 | testPromise.cancel() 64 | 65 | wait(for: [rejectionExpectation, fulfillmentExpectation], timeout: 0.1) 66 | } 67 | 68 | func testReject() { 69 | let rejectionExpectation = expectation(description: "Promise was not rejected") 70 | let fulfillmentExpectation = expectation(description: "Promise was fulfilled") 71 | fulfillmentExpectation.isInverted = true 72 | 73 | testPromise.done { _ in 74 | fulfillmentExpectation.fulfill() 75 | }.catch { (error) in 76 | rejectionExpectation.fulfill() 77 | } 78 | 79 | class TestError: Error { } 80 | testResolver.reject(TestError()) 81 | 82 | wait(for: [rejectionExpectation, fulfillmentExpectation], timeout: 0.1) 83 | } 84 | 85 | } 86 | -------------------------------------------------------------------------------- /Example/Tests/WhenFulfilledTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // WhenFulfilledTests.swift 3 | // CancellablePromiseKit 4 | // 5 | // Created by Johannes Dörr on 13.05.18. 6 | // Copyright © 2018 Johannes Dörr. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | import PromiseKit 11 | import CancellablePromiseKit 12 | 13 | typealias PromiseAndResolver = (promise: CancellablePromise, resolver: Resolver) 14 | 15 | func createPromise() -> PromiseAndResolver { 16 | let (basePromise, resolver) = Promise.pending() 17 | let promise = CancellablePromise(using: basePromise, cancel: { resolver.reject(CancellablePromiseError.cancelled) }) 18 | return (promise, resolver) 19 | } 20 | 21 | class WhenFulfilledTests: XCTestCase { 22 | 23 | var testPromise1: PromiseAndResolver! 24 | var testPromise2: PromiseAndResolver! 25 | 26 | class Error: Swift.Error { } 27 | 28 | override func setUp() { 29 | super.setUp() 30 | testPromise1 = createPromise() 31 | testPromise2 = createPromise() 32 | } 33 | 34 | override func tearDown() { 35 | super.tearDown() 36 | testPromise1 = nil 37 | testPromise2 = nil 38 | } 39 | 40 | func testFulfilled() { 41 | let expectation = self.expectation(description: "Promise was not fulfilled") 42 | 43 | let testValue1 = "test1" 44 | let testValue2 = "test2" 45 | let cancellablePromise = when(fulfilled: [testPromise1.promise, testPromise2.promise]) 46 | _ = cancellablePromise.done { (strings) in 47 | if strings == [testValue1, testValue2] { 48 | expectation.fulfill() 49 | } 50 | } 51 | testPromise1.resolver.fulfill(testValue1) 52 | testPromise2.resolver.fulfill(testValue2) 53 | 54 | wait(for: [expectation], timeout: 0.1) 55 | } 56 | 57 | func testRejected() { 58 | let expectation = self.expectation(description: "Promise was not rejected") 59 | 60 | let cancellablePromise = when(fulfilled: [testPromise1.promise, testPromise2.promise]) 61 | _ = cancellablePromise.catch { (error) in 62 | expectation.fulfill() 63 | } 64 | testPromise2.resolver.reject(Error()) 65 | 66 | wait(for: [expectation], timeout: 0.1) 67 | } 68 | 69 | func testRejectedOtherFulfilled() { 70 | let expectation = self.expectation(description: "Promise was not rejected") 71 | 72 | let cancellablePromise = when(fulfilled: [testPromise1.promise, testPromise2.promise]) 73 | _ = cancellablePromise.catch { (error) in 74 | expectation.fulfill() 75 | } 76 | testPromise1.resolver.fulfill("test1") 77 | testPromise2.resolver.reject(Error()) 78 | 79 | wait(for: [expectation], timeout: 0.1) 80 | } 81 | 82 | func testRejectedAutoCancelOther() { 83 | let expectation = self.expectation(description: "Promise was not rejected") 84 | let cancelExpectation = self.expectation(description: "Other Promise was not cancelled") 85 | 86 | self.testPromise1.promise.catch(policy: .allErrors) { error in 87 | switch error { 88 | case CancellablePromiseError.cancelled: 89 | cancelExpectation.fulfill() 90 | default: 91 | break 92 | } 93 | } 94 | 95 | let cancellablePromise = when(fulfilled: [testPromise1.promise, testPromise2.promise], autoCancel: true) 96 | _ = cancellablePromise.catch { (error) in 97 | expectation.fulfill() 98 | } 99 | testPromise2.resolver.reject(Error()) 100 | 101 | wait(for: [expectation, cancelExpectation], timeout: 0.1) 102 | } 103 | 104 | func testCancel() { 105 | let expectation = self.expectation(description: "Promise was not cancelled") 106 | let cancelExpectation = self.expectation(description: "Promise 1 was cancelled") 107 | cancelExpectation.isInverted = true 108 | 109 | self.testPromise2.promise.catch(policy: .allErrors) { error in 110 | cancelExpectation.fulfill() 111 | } 112 | 113 | let cancellablePromise = when(fulfilled: [testPromise1.promise, testPromise2.promise]) 114 | _ = cancellablePromise.catch(policy: .allErrors) { (error) in 115 | switch error { 116 | case CancellablePromiseError.cancelled: 117 | expectation.fulfill() 118 | default: 119 | break 120 | } 121 | } 122 | testPromise1.resolver.fulfill("test1") 123 | cancellablePromise.cancel() 124 | 125 | wait(for: [expectation, cancelExpectation], timeout: 0.1) 126 | } 127 | 128 | func testCancelAutoCancelOther() { 129 | let expectation = self.expectation(description: "Promise was not cancelled") 130 | let cancelExpectation = self.expectation(description: "Promise 1 was not cancelled") 131 | 132 | self.testPromise2.promise.catch(policy: .allErrors) { error in 133 | switch error { 134 | case CancellablePromiseError.cancelled: 135 | cancelExpectation.fulfill() 136 | default: 137 | break 138 | } 139 | } 140 | 141 | let cancellablePromise = when(fulfilled: [testPromise1.promise, testPromise2.promise], autoCancel: true) 142 | _ = cancellablePromise.catch(policy: .allErrors) { (error) in 143 | switch error { 144 | case CancellablePromiseError.cancelled: 145 | expectation.fulfill() 146 | default: 147 | break 148 | } 149 | } 150 | testPromise1.resolver.fulfill("test1") 151 | cancellablePromise.cancel() 152 | 153 | wait(for: [expectation, cancelExpectation], timeout: 0.1) 154 | } 155 | 156 | } 157 | -------------------------------------------------------------------------------- /Example/Tests/WhenResolvedTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // WhenResolvedTests.swift 3 | // CancellablePromiseKit 4 | // 5 | // Created by Johannes Dörr on 13.05.18. 6 | // Copyright © 2018 Johannes Dörr. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | import XCTest 12 | import PromiseKit 13 | import CancellablePromiseKit 14 | 15 | class WhenResolvedTests: XCTestCase { 16 | 17 | var testPromise1: PromiseAndResolver! 18 | var testPromise2: PromiseAndResolver! 19 | 20 | class Error: Swift.Error { } 21 | 22 | override func setUp() { 23 | super.setUp() 24 | testPromise1 = createPromise() 25 | testPromise2 = createPromise() 26 | } 27 | 28 | override func tearDown() { 29 | super.tearDown() 30 | testPromise1 = nil 31 | testPromise2 = nil 32 | } 33 | 34 | func testFulfilled() { 35 | let expectation = self.expectation(description: "Promise was not fulfilled") 36 | 37 | let testValue1 = "test1" 38 | let cancellablePromise = when(resolved: [testPromise1.promise, testPromise2.promise]) 39 | _ = cancellablePromise.done { (results) in 40 | switch (results[0], results[1]) { 41 | case (.fulfilled(let value), .rejected) where value == testValue1: 42 | expectation.fulfill() 43 | default: 44 | break 45 | } 46 | } 47 | testPromise1.resolver.fulfill(testValue1) 48 | testPromise2.resolver.reject(Error()) 49 | 50 | wait(for: [expectation], timeout: 0.1) 51 | } 52 | 53 | func testCancel() { 54 | let expectation = self.expectation(description: "Promise was not cancelled") 55 | let cancelExpectation = self.expectation(description: "Promise 1 was cancelled") 56 | cancelExpectation.isInverted = true 57 | 58 | self.testPromise2.promise.catch(policy: .allErrors) { error in 59 | cancelExpectation.fulfill() 60 | } 61 | 62 | let testValue1 = "test1" 63 | let cancellablePromise = when(resolved: [testPromise1.promise, testPromise2.promise]) 64 | _ = cancellablePromise.catch(policy: .allErrors) { (error) in 65 | switch error { 66 | case CancellablePromiseError.cancelled: 67 | expectation.fulfill() 68 | default: 69 | break 70 | } 71 | } 72 | testPromise1.resolver.fulfill(testValue1) 73 | cancellablePromise.cancel() 74 | 75 | wait(for: [expectation, cancelExpectation], timeout: 0.1) 76 | } 77 | 78 | func testCancelAutoCancel() { 79 | let expectation = self.expectation(description: "Promise was not cancelled") 80 | let cancelExpectation = self.expectation(description: "Promise 1 was not cancelled") 81 | 82 | self.testPromise2.promise.catch(policy: .allErrors) { error in 83 | switch error { 84 | case CancellablePromiseError.cancelled: 85 | cancelExpectation.fulfill() 86 | default: 87 | break 88 | } 89 | } 90 | 91 | let testValue1 = "test1" 92 | let cancellablePromise = when(resolved: [testPromise1.promise, testPromise2.promise], autoCancel: true) 93 | _ = cancellablePromise.catch(policy: .allErrors) { (error) in 94 | switch error { 95 | case CancellablePromiseError.cancelled: 96 | expectation.fulfill() 97 | default: 98 | break 99 | } 100 | } 101 | testPromise1.resolver.fulfill(testValue1) 102 | cancellablePromise.cancel() 103 | 104 | wait(for: [expectation, cancelExpectation], timeout: 0.1) 105 | } 106 | 107 | } 108 | -------------------------------------------------------------------------------- /Example/Tests/WhenTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // WhenTests.swift 3 | // CancellablePromiseKit 4 | // 5 | // Created by Johannes Dörr on 13.05.18. 6 | // Copyright © 2018 Johannes Dörr. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | import PromiseKit 11 | import CancellablePromiseKit 12 | 13 | class WhenTests: XCTestCase { 14 | 15 | var testPromise: (promise: Promise, resolver: Resolver)! 16 | 17 | class Error: Swift.Error { } 18 | 19 | override func setUp() { 20 | super.setUp() 21 | testPromise = Promise.pending() 22 | } 23 | 24 | override func tearDown() { 25 | super.tearDown() 26 | testPromise = nil 27 | } 28 | 29 | func testFulfilled() { 30 | let expectation = self.expectation(description: "Promise was not fulfilled") 31 | 32 | let (conditionPromise, _) = Promise.pending() 33 | let testValue = "test" 34 | _ = when(testPromise.promise, while: conditionPromise).done { value in 35 | if value == testValue { 36 | expectation.fulfill() 37 | } 38 | } 39 | testPromise.resolver.fulfill(testValue) 40 | 41 | wait(for: [expectation], timeout: 0.1) 42 | } 43 | 44 | func testRejected() { 45 | let expectation = self.expectation(description: "Promise was not rejected") 46 | 47 | let (conditionPromise, _) = Promise.pending() 48 | _ = when(testPromise.promise, while: conditionPromise).catch({ (error) in 49 | expectation.fulfill() 50 | }) 51 | testPromise.resolver.reject(Error()) 52 | 53 | wait(for: [expectation], timeout: 0.1) 54 | } 55 | 56 | func testRejectedCondition() { 57 | let expectation = self.expectation(description: "Promise was not rejected") 58 | 59 | let conditionPromise = Promise(error: Error()) 60 | _ = when(testPromise.promise, while: conditionPromise).catch({ (error) in 61 | expectation.fulfill() 62 | }) 63 | 64 | wait(for: [expectation], timeout: 0.1) 65 | } 66 | 67 | func testFulfilledAndConditionFulfilled() { 68 | let expectation = self.expectation(description: "Promise was not fulfilled") 69 | 70 | let (conditionPromise, conditionResolver) = Promise.pending() 71 | let testValue = "test" 72 | _ = when(testPromise.promise, while: conditionPromise).done { value in 73 | if value == testValue { 74 | expectation.fulfill() 75 | } 76 | } 77 | conditionResolver.fulfill(Void()) 78 | testPromise.resolver.fulfill(testValue) 79 | 80 | wait(for: [expectation], timeout: 0.1) 81 | } 82 | 83 | func testConditionRejected() { 84 | let expectation = self.expectation(description: "Promise was fulfilled") 85 | expectation.isInverted = true 86 | 87 | let (conditionPromise, conditionResolver) = Promise.pending() 88 | let testValue = "test" 89 | _ = when(testPromise.promise, while: conditionPromise).done { value in 90 | if value == testValue { 91 | expectation.fulfill() 92 | } 93 | } 94 | conditionResolver.reject(Error()) 95 | testPromise.resolver.fulfill(testValue) 96 | 97 | wait(for: [expectation], timeout: 0.1) 98 | } 99 | 100 | func testConditionRejectedAfterFulfilled() { 101 | let expectation = self.expectation(description: "Promise was not fulfilled") 102 | 103 | let (conditionPromise, conditionResolver) = Promise.pending() 104 | let testValue = "test" 105 | _ = when(testPromise.promise, while: conditionPromise).done { value in 106 | if value == testValue { 107 | expectation.fulfill() 108 | } 109 | } 110 | testPromise.resolver.fulfill(testValue) 111 | conditionResolver.reject(Error()) 112 | 113 | wait(for: [expectation], timeout: 0.1) 114 | } 115 | 116 | func testConditionRejectedAfterFulfilledDismiss() { 117 | let expectation = self.expectation(description: "Promise was not rejected") 118 | 119 | let (conditionPromise, conditionResolver) = Promise.pending() 120 | _ = when(testPromise.promise, while: conditionPromise, isEnsured: true).catch(policy: .allErrors) { (error) in 121 | switch error { 122 | case CancellablePromiseError.cancelled: 123 | expectation.fulfill() 124 | default: 125 | break 126 | } 127 | } 128 | testPromise.resolver.fulfill("test") 129 | conditionResolver.reject(Error()) 130 | 131 | wait(for: [expectation], timeout: 0.1) 132 | } 133 | 134 | } 135 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Johannes Dörr 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CancellablePromiseKit 2 | 3 | CancellablePromiseKit is an extension for [PromiseKit](https://github.com/mxcl/PromiseKit). A `Promise` is an abstraction of an asynchonous operation that can succeed or fail. A `CancellablePromise`, provided by this library, extends this concept to represent tasks that can be cancelled/aborted. 4 | 5 | 6 | ## Installation 7 | 8 | CancellablePromiseKit is available through [CocoaPods](https://cocoapods.org). To install 9 | it, simply add the following line to your Podfile: 10 | 11 | ```ruby 12 | pod 'CancellablePromiseKit' 13 | ``` 14 | 15 | To run the unit tests, clone the repo, and run `pod install` from the Example directory first. 16 | 17 | ## Making cancellable Promises 18 | 19 | Creating a `CancellablePromise` is done similarly to creating a `Promise`: 20 | 21 | ```swift 22 | func startTask() -> CancellablePromise { 23 | let task = Task(…) 24 | return CancellablePromise { resolver in 25 | task.completion = { (value, error) in 26 | resolver.resolve(value, error) 27 | } 28 | let cancel = { 29 | task.stop() 30 | print("Task was cancelled") 31 | } 32 | return cancel 33 | } 34 | } 35 | let runningTask = startTask() 36 | ``` 37 | 38 | This initializer is almost identical to the one of `Promise`. However, the block has to return a handler that will be executed when the `CancellablePromise` is cancelled. It has to perform the necessary steps to abort the underlying task. 39 | 40 | 41 | ## Cancelling 42 | 43 | A `CancellablePromise` has a `cancel()` function for stopping the task: 44 | 45 | ```swift 46 | runningTask.cancel() 47 | ``` 48 | 49 | Calling `cancel()` will reject the promise with a `CancellablePromiseError.cancelled`. A `catch` handler in your promise chain will not be called unless you set the policy to `.allErrors`: 50 | 51 | ```swift 52 | runningTask.catch(policy: .allErrors) { error in 53 | // Will be called with error being CancellablePromiseError.cancelled 54 | } 55 | runningTask.cancel() 56 | ``` 57 | 58 | 59 | ## `race` and `when` variants with `autoCancel` 60 | 61 | The `race` function of `PromiseKit` allows you to wait until the first task of a list of tasks fulfills. You can use this task's result for further processing. The other tasks, however, continue executing, although their result will be ignored. If you're working with `CancellablePromise`s, there is a special overloaded `race(:, autoCancel:)` function that offers you an additional parameter called `autoCancel`. If you set it to `true`, all other tasks in the list will be cancelled: 62 | 63 | ```swift 64 | race([cancellablePromise1, cancellablePromise2, cancellablePromise3], autoCancel: true) 65 | ``` 66 | If `cancellablePromise1` will fulfill, `cancellablePromise2` and `cancellablePromise3` will be cancelled automatically. So, if these represent large downloads for example, no further bandwidth will be wasted. 67 | 68 | Similarly, there are `when(resolved:, autoCancel:)` and `when(fulfilled:, autoCancel:)` that cancel all other promises if one fails. 69 | 70 | The `race(:, autoCancel:)`, `when(resolved:, autoCancel:)` and `when(fulfilled:, autoCancel:)` return a `CancellablePromise` which itself can be cancelled. Cancelling that promise will cancel the passed promises only if autoCancel is `true`. 71 | 72 | The `autoCancel` parameter is `false` by default so that leaving it out will produce the same behaviour as with the regular `PromiseKit`. 73 | 74 | 75 | ## `then` 76 | 77 | The overloaded `then` function allows you to chain `CancellablePromise`s which creates another `CancellablePromise`: 78 | 79 | ```swift 80 | let cancellableChain = cancellablePromise1.then { 81 | cancellablePromise2 82 | } 83 | cancellableChain.cancel() 84 | ``` 85 | Cancelling the chain will cancel all included pending promises, i.e. `cancellablePromise1` and `cancellablePromise2` in this example. 86 | 87 | 88 | ## `asPromise` and `asCancellable` 89 | 90 | If you want to use a `Promise` and a `CancellablePromise` in one expression (like `when` or `race`), you can convert between them. Every `CancellablePromise` provides `asPromise()`, and every `Promise` provides `asCancellable()`. Calling `cancel()` on the latter will cause a reject, but the underlying promise, that you called `asCancellable()` on, will continue beeing `pending`. 91 | 92 | 93 | ## Other initializers of `CancellablePromise` 94 | 95 | `CancellablePromise` is not a subclass of `Promise`, but a wrapper around it. You can create a new instance by passing a promise and a cancel block: 96 | 97 | ```swift 98 | let existingPromise, existingResolver = Promise.pending() 99 | let cancelFunction = { existingResolver.reject(MyError()) } 100 | 101 | let cancellablePromise = CancellablePromise(using: existingPromise, cancel: cancelFunction) 102 | ``` 103 | 104 | 105 | In some cases, you're building your cancellable task using other promises. In that case, you can use the initializer that provides you with a `cancelPromise`. It is a `Promise` that nevers fulfills, but which will reject when `cancel()` is called. Putting it in a `race` with another promise allows you wait until that promise fulfills, unless the process is cancelled. The following example executes two tasks in parallel, followed by a third task. The whole process can be cancelled at any time: 106 | 107 | ```swift 108 | let cancellablePromise = CancellablePromise(wrapper: { cancelPromise in 109 | let task1: Promise = Task() 110 | let task2: Promise = Task() 111 | let tasks = when(fulfilled: task1, task2) 112 | return firstly { 113 | race(tasks.asVoid(), cancelPromise) 114 | }.then { 115 | let value1 = task1.value! 116 | let value2 = task2.value! 117 | let task3: Promise = AnotherTask(value1, value2) 118 | return race(task3.asVoid(), cancelPromise).map { 119 | task3.value! 120 | } 121 | } 122 | }) 123 | ``` 124 | 125 | Calling `cancellablePromise.cancel()` in this example will cause `cancelPromise` to reject, which will cause all `race` calls and hereby the whole `cancellablePromise` to reject. 126 | 127 | 128 | ### `when` variants for `cancelPromise` 129 | 130 | As shown in the previous example, building a cancellable promise often involves waiting for another promise to finish, while also expecting a cancellation. Building this with `race` requires the promises to be converted to `Promise`, which makes getting the resolved value cumbersome. For that reason, there is the `when(:, while:)` overload. 131 | 132 | Instead of: 133 | ```swift 134 | let task: Promise = ... 135 | race(task.asVoid(), cancelPromise).then { 136 | let value = tasks.value! 137 | // use value 138 | } 139 | ``` 140 | 141 | you can write: 142 | ```swift 143 | when(task, while: cancelPromise).then { value in 144 | // use value 145 | } 146 | ``` 147 | 148 | The example above can be rewritten as: 149 | 150 | ```swift 151 | let cancellablePromise = CancellablePromise(wrapper: { cancelPromise in 152 | let task1: Promise = Task() 153 | let task2: Promise = Task() 154 | return firstly { 155 | let tasks = when(fulfilled: task1, task2) 156 | return when(tasks, while: cancelPromise) 157 | }.then { (value1, value2) in 158 | let task3: Promise = AnotherTask(value1, value2) 159 | return when(task3, while: cancelPromise) 160 | } 161 | }) 162 | ``` 163 | 164 | In order to perform some actions when `cancelPromise` rejects, use `catch`: 165 | 166 | ```swift 167 | let taskWithCancel = when(task, while: cancelPromise).then { value in 168 | // ... 169 | } 170 | taskWithCancel.catch(policy: .allErrors) { error in 171 | switch error { 172 | case CancellablePromiseError.cancelled: 173 | // do something to cancel the underlying task 174 | default: 175 | // task failed 176 | } 177 | } 178 | ``` 179 | 180 | 181 | ## TODO 182 | 183 | - There should be overloads for `done`, `get`, `catch` etc. that return a `CancellablePromise`. At the moment, the implementations of `Thenable` take effect and return a regular `Promise`. 184 | - There should be a `firstly` for `CancellablePromise`. 185 | - There should be an `asCancellable()` on `Guarantee` that returns a `CancellablePromise`. There won't be a ~~CancellableGuarantee~~ because a task that offers to be cancelled cannot also claim to always fulfill. 186 | - `PromiseKit` has many overloads for `race` and `when` that can receive arrays, varadic parameters, etc. `CancellablePromiseKit` could have those, too. 187 | - There could be factory functions like `pending`, `value`, etc., like in `PromiseKit` 188 | 189 | 190 | ## Author 191 | 192 | Johannes Dörr, mail@johannesdoerr.de 193 | 194 | 195 | ## License 196 | 197 | CancellablePromiseKit is available under the MIT license. See the LICENSE file for more info. 198 | -------------------------------------------------------------------------------- /_Pods.xcodeproj: -------------------------------------------------------------------------------- 1 | Example/Pods/Pods.xcodeproj --------------------------------------------------------------------------------