├── .github └── workflows │ └── tests.yml ├── .gitignore ├── .gitmodules ├── BrightFutures.podspec ├── BrightFutures.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ ├── BrightFutures.xccheckout │ │ └── IDEWorkspaceChecks.plist └── xcshareddata │ ├── xcbaselines │ └── E9DF081F194470060083F7F2.xcbaseline │ │ ├── C4417EF4-4E72-45DF-9C3D-F457191607C4.plist │ │ └── Info.plist │ └── xcschemes │ ├── BrightFutures-Mac.xcscheme │ ├── BrightFutures-iOS.xcscheme │ ├── BrightFutures-tvOS.xcscheme │ └── BrightFutures-watchOS.xcscheme ├── CHANGELOG.md ├── Documentation ├── Migration_2.0.md ├── Migration_3.0.md ├── Migration_4.0.md └── Migration_5.0.md ├── LICENSE ├── Package.swift ├── README.md ├── Sources └── BrightFutures │ ├── Async.swift │ ├── AsyncType+Debug.swift │ ├── AsyncType+ResultType.swift │ ├── AsyncType.swift │ ├── Dispatch+BrightFutures.swift │ ├── Errors.swift │ ├── ExecutionContext.swift │ ├── Future.swift │ ├── Info.plist │ ├── InvalidationToken.swift │ ├── MutableAsyncType+ResultType.swift │ ├── MutableAsyncType.swift │ ├── NSOperationQueue+BrightFutures.swift │ ├── Promise.swift │ ├── Result+BrightFutures.swift │ ├── ResultProtocol.swift │ ├── SequenceType+BrightFutures.swift │ └── SwiftConcurrency+BrightFutures.swift └── Tests └── BrightFuturesTests ├── BrightFuturesTests.swift ├── ErrorsTests.swift ├── ExecutionContextTests.swift ├── FutureDebugTests.swift ├── Info.plist ├── InvalidationTokenTests.swift ├── NSOperationQueueTests.swift ├── Number+BrightFutures.swift ├── PromiseTests.swift ├── QueueTests.swift ├── ResultTests.swift └── SwiftConcurrencyTests.swift /.github/workflows/tests.yml: -------------------------------------------------------------------------------- 1 | name: "BrightFutures" 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | test: 7 | name: Test 8 | runs-on: macOS-12 9 | env: 10 | DEVELOPER_DIR: /Applications/Xcode_13.4.app/Contents/Developer 11 | 12 | steps: 13 | - uses: actions/checkout@v2 14 | - name: macOS 15 | run: xcodebuild test -project BrightFutures.xcodeproj -scheme BrightFutures-Mac 16 | - name: iOS 17 | run: xcodebuild test -project BrightFutures.xcodeproj -scheme BrightFutures-iOS -sdk iphonesimulator -destination "platform=iOS Simulator,OS=15.5,name=iPhone 13 mini" 18 | - name: tvOS 19 | run: xcodebuild test -project BrightFutures.xcodeproj -scheme BrightFutures-tvOS -sdk appletvsimulator -destination "OS=15.4,name=Apple TV 4K (2nd generation)" 20 | - name: watchOS 21 | run: xcodebuild build -project BrightFutures.xcodeproj -scheme BrightFutures-watchOS -sdk watchsimulator 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # CocoaPods 2 | # 3 | # We recommend against adding the Pods directory to your .gitignore. However 4 | # you should judge for yourself, the pros and cons are mentioned at: 5 | # http://guides.cocoapods.org/using/using-cocoapods.html#should-i-ignore-the-pods-directory-in-source-control? 6 | # 7 | # Pods/ 8 | .DS_Store 9 | build/ 10 | *.pbxuser 11 | !default.pbxuser 12 | *.mode1v3 13 | !default.mode1v3 14 | *.mode2v3 15 | !default.mode2v3 16 | *.perspectivev3 17 | !default.perspectivev3 18 | !default.xcworkspace 19 | xcuserdata 20 | profile 21 | *.moved-aside 22 | DerivedData 23 | .idea/ 24 | Podfile.lock 25 | *.xcscmblueprint 26 | .build 27 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Thomvis/BrightFutures/a4b4cb27ef1525739988184dac73a1a462f9d62d/.gitmodules -------------------------------------------------------------------------------- /BrightFutures.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = 'BrightFutures' 3 | s.version = '8.2.0' 4 | s.license = 'MIT' 5 | s.summary = 'Write great asynchronous code in Swift using futures and promises' 6 | s.homepage = 'https://github.com/Thomvis/BrightFutures' 7 | s.social_media_url = 'https://twitter.com/thomvis' 8 | s.authors = { 'Thomas Visser' => 'thomas.visser@gmail.com' } 9 | s.source = { :git => 'https://github.com/Thomvis/BrightFutures.git', :tag => "#{s.version}" } 10 | 11 | s.ios.deployment_target = '9.0' 12 | s.osx.deployment_target = '10.10' 13 | s.tvos.deployment_target = '9.0' 14 | s.watchos.deployment_target = '3.1' 15 | 16 | s.source_files = 'Sources/BrightFutures/*.swift' 17 | 18 | s.requires_arc = true 19 | 20 | s.swift_version = '5.0' 21 | end 22 | -------------------------------------------------------------------------------- /BrightFutures.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /BrightFutures.xcodeproj/project.xcworkspace/xcshareddata/BrightFutures.xccheckout: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDESourceControlProjectFavoriteDictionaryKey 6 | 7 | IDESourceControlProjectIdentifier 8 | 3A31C8B6-B0CD-4D79-9DFB-AE57C9B04C9F 9 | IDESourceControlProjectName 10 | BrightFutures 11 | IDESourceControlProjectOriginsDictionary 12 | 13 | 93608FFF008CD82ADDFA850BB9C1630FA754586F 14 | github.com:Thomvis/BrightFutures.git 15 | 16 | IDESourceControlProjectPath 17 | BrightFutures.xcodeproj 18 | IDESourceControlProjectRelativeInstallPathDictionary 19 | 20 | 93608FFF008CD82ADDFA850BB9C1630FA754586F 21 | ../.. 22 | 23 | IDESourceControlProjectURL 24 | github.com:Thomvis/BrightFutures.git 25 | IDESourceControlProjectVersion 26 | 111 27 | IDESourceControlProjectWCCIdentifier 28 | 93608FFF008CD82ADDFA850BB9C1630FA754586F 29 | IDESourceControlProjectWCConfigurations 30 | 31 | 32 | IDESourceControlRepositoryExtensionIdentifierKey 33 | public.vcs.git 34 | IDESourceControlWCCIdentifierKey 35 | 93608FFF008CD82ADDFA850BB9C1630FA754586F 36 | IDESourceControlWCCName 37 | BrightFutures 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /BrightFutures.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /BrightFutures.xcodeproj/xcshareddata/xcbaselines/E9DF081F194470060083F7F2.xcbaseline/C4417EF4-4E72-45DF-9C3D-F457191607C4.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | classNames 6 | 7 | BrightFuturesTests 8 | 9 | testStress() 10 | 11 | com.apple.XCTPerformanceMetric_WallClockTime 12 | 13 | baselineAverage 14 | 0.16266 15 | baselineIntegrationDisplayName 16 | 16 Oct 2015 23:17:48 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /BrightFutures.xcodeproj/xcshareddata/xcbaselines/E9DF081F194470060083F7F2.xcbaseline/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | runDestinationsByUUID 6 | 7 | C4417EF4-4E72-45DF-9C3D-F457191607C4 8 | 9 | localComputer 10 | 11 | busSpeedInMHz 12 | 100 13 | cpuCount 14 | 1 15 | cpuKind 16 | Intel Core i5 17 | cpuSpeedInMHz 18 | 2400 19 | logicalCPUCoresPerPackage 20 | 4 21 | modelCode 22 | MacBookPro11,1 23 | physicalCPUCoresPerPackage 24 | 2 25 | platformIdentifier 26 | com.apple.platform.macosx 27 | 28 | targetArchitecture 29 | x86_64 30 | targetDevice 31 | 32 | modelCode 33 | iPhone8,2 34 | platformIdentifier 35 | com.apple.platform.iphonesimulator 36 | 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /BrightFutures.xcodeproj/xcshareddata/xcschemes/BrightFutures-Mac.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 29 | 35 | 36 | 37 | 38 | 39 | 44 | 45 | 51 | 52 | 53 | 54 | 56 | 62 | 63 | 64 | 65 | 66 | 76 | 77 | 83 | 84 | 85 | 86 | 92 | 93 | 99 | 100 | 101 | 102 | 104 | 105 | 108 | 109 | 110 | -------------------------------------------------------------------------------- /BrightFutures.xcodeproj/xcshareddata/xcschemes/BrightFutures-iOS.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 29 | 35 | 36 | 37 | 38 | 39 | 45 | 46 | 52 | 53 | 54 | 55 | 57 | 63 | 64 | 65 | 66 | 67 | 77 | 78 | 84 | 85 | 86 | 87 | 93 | 94 | 100 | 101 | 102 | 103 | 105 | 106 | 109 | 110 | 111 | -------------------------------------------------------------------------------- /BrightFutures.xcodeproj/xcshareddata/xcschemes/BrightFutures-tvOS.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 37 | 38 | 39 | 40 | 42 | 48 | 49 | 50 | 51 | 52 | 62 | 63 | 69 | 70 | 71 | 72 | 78 | 79 | 85 | 86 | 87 | 88 | 90 | 91 | 94 | 95 | 96 | -------------------------------------------------------------------------------- /BrightFutures.xcodeproj/xcshareddata/xcschemes/BrightFutures-watchOS.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 43 | 44 | 50 | 51 | 52 | 53 | 59 | 60 | 66 | 67 | 68 | 69 | 71 | 72 | 75 | 76 | 77 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # 8.2.0 2 | - Adds support for Swift 5.6 and Xcode 13.3. 3 | - Adds support for awaiting a Future's value. 4 | - Adds support for @dynamicMemberLookup. 5 | 6 | # 8.1.0 7 | - Adds support for Swift 5.4 and Xcode 12.5. ([#216](https://github.com/Thomvis/BrightFutures/pull/216) & [#218](https://github.com/Thomvis/BrightFutures/pull/218), thanks [MagFer](https://github.com/MagFer), [MultiColourPixel](https://github.com/MultiColourPixel) & [paiv](https://github.com/paiv)) 8 | - Adds `Future.init(error: E, delay: DispatchTimeInterval)` ([#215](https://github.com/Thomvis/BrightFutures/pull/215), thanks [RomanPodymov](https://github.com/RomanPodymov)) 9 | - Fixed casing of various global constants. ([#176](https://github.com/Thomvis/BrightFutures/pull/176), thanks [Sajjon](https://github.com/Sajjon)) 10 | - Deprecated `NoValue` in favor of `Never` 11 | 12 | 13 | # 8.0.1 14 | - Fixes an issue that broke Swift Package Manager support. ([#208](https://github.com/Thomvis/BrightFutures/pull/208), thanks [slessans](https://github.com/slessans)!) 15 | 16 | # 8.0.0 17 | Adds support for Swift 5 and Xcode 10.2. Previous versions of Swift are no longer supported. 18 | 19 | - Migrated from [antitypical/Result](https://github.com/antitypical/Result) to the new Result type in the standard library 20 | - Removed `NoError` in favor of `Never` from the standard library 21 | 22 | Thanks to [kimdv](https://github.com/kimdv) for doing most of the migration work! 23 | 24 | # 7.0.1 25 | - Updates Result dependency to 4.1 compatible versions. ([#203](https://github.com/Thomvis/BrightFutures/pull/203]) , thanks [Jeroenbb94](https://github.com/Jeroenbb94)!) 26 | - Fixes an issue related to the threadDictionary on Linux ([#204](https://github.com/Thomvis/BrightFutures/pull/204), thanks [jgh-](https://github.com/jgh-)!) 27 | 28 | # 7.0.0 29 | Adds support for Swift 4.2 and Xcode 10. 30 | 31 | - Upgrade Result dependency to 4.0. 32 | - [FIX] Fixed a typo and incorrect reference in the `andThen` test ([https://github.com/Thomvis/BrightFutures/pull/197](#197), thanks [https://github.com/robertoaceves](robertoaceves)!) 33 | 34 | # 6.0.1 35 | Adds support for Swift 4.1 and Xcode 9.3 (tested with beta 4). 36 | 37 | - Workaround for [SR-7059](https://bugs.swift.org/browse/SR-7059) 38 | 39 | # 6.0.0 40 | Adds support for Swift 4 and Xcode 9 41 | 42 | # 5.2.0 43 | Adds support for Swift 3.1 and Xcode 8.3 44 | 45 | # 5.0.0 46 | Adds support for Swift 3 and Xcode 8 47 | 48 | # 4.1.1 49 | Adds support for Swift 2.3 and Xcode 8 (tested with beta 4) 50 | 51 | # 4.1.0 52 | - [FIX] `AsyncType.delay()` now correctly starts the delay after the AsyncType has completed, which was the intended behavior. This fix can be breaking if you depended on the faulty behavior. ([https://github.com/Thomvis/BrightFutures/pull/139](#139), thanks [peyton](https://github.com/peyton)!) 53 | 54 | # 4.0.0 55 | BrightFutures 4.0.0 is compatible with Swift 2.2 and Xcode 7.3. 56 | 57 | - [BREAKING] `NoError` has been removed from BrightFutures. 58 | - [Breaking] `SequenceType.fold(_:zero:f:)` and methods that use it (such as `SequenceType.traverse(_:f:)` and `SequenceType.sequence()`) are now slightly more asynchronous: to prevent stack overflows, after a certain number of items, it will perform an asynchronous call. 59 | - [FIX] Fixed stack overflow when using `sequence()` or `traverse()` on large sequences. 60 | 61 | # 3.3.0 62 | - Added three new variants of the `future` free function that enables easy wrapping of completionHandler-based API. Thanks @phimage! 63 | 64 | Note: some versions are missing here 65 | 66 | # 3.0.0-beta.4 67 | - The implementation of `mapError` now explicitly uses `ImmediateExecutionContext`, fixing unnecessary asynchronicity 68 | - Adds `delay(interval: NSTimeInterval)` on `Async`, which produces a new `Async` that completes with the original Async after the given delay 69 | - All FutureUtils free functions are now functions in extensions of the appropriate types (e.g. SequenceType) 70 | - `InvalidationToken` instances now have a `validContext` property which is an `ExecutionContext` that can be passed to any function that accepts an `ExecutionContext` to make the effect of that function depend on the validity of the token. 71 | - Added support for `NSOperationQueue` as an `ExecutionContext` 72 | 73 | # 2.0.1 74 | - Adds an implementation of `flatMap` that allows a function to be passed in immediately. Thanks @nghialv! 75 | 76 | # 3.0.0-beta.1 77 | This release is compatible with Swift 2. It is a direct port of 2.0, meaning it makes no use of new Swift 2 features yet. 78 | 79 | - Removed our homegrown 'ErrorType' with Swift 2's native one 80 | - The 'Box' dependency is gone. Swift 2 removed the need for boxing associated values in enums! 81 | 82 | Because antitypical/Result has not yet released a Swift 2 compatible release on CocoaPods, this version of BrightFutures can not yet be built using CocoaPods. 83 | 84 | # 2.0.0 85 | - Replaced homegrown `Result` and `Box` types with Rob Rix' excellent types. 86 | - Futures & Promises are now also parametrizable by their error type, in addition to their value type: `Future`. This allows you to use your own (Swifty) error type, instead of `NSError`! 87 | - Adds `BrightFuturesError` enum, containing all three possible errors that BrightFutures can return 88 | - Renames `asType` to `forceType` to indicate that it is a _dangerous_ operation 89 | 90 | - Adds missing documentation (jazzy reports 100% documentation coverage!) 91 | - Adds a lot of tests (test coverage is now at 97%, according to [SwiftCov](https://github.com/realm/SwiftCov)!) 92 | 93 | # 1.0.1 94 | - Updated README to reflect the pre-1.0.0 change from FutureUtils functions to free functions 95 | 96 | # 1.0.0 97 | - The FutureUtils class has been removed in favor of a collection of free functions. This allows for a nicer function type signature (e.g. accepting all sequences instead of just arrays) 98 | 99 | # 1.0.0-beta.3 100 | Note: The overview for this release is incomplete 101 | - Changed `ExecutionContext` from a protocol to a function type. This allows for better composition. It does mean that a Queue cannot be used directly as an `ExecutionContext`, instead use the `context` property (e.g. `Queue.main.context`) or the `toContext` function (e.g. `toContext(Queue.main)`). 102 | 103 | # 1.0.0-beta.2 104 | Note: this overview is incomplete 105 | - `TaskResultValueWrapper` has been renamed to the conventional name `Box` 106 | - `TaskResult` has been renamed to the conventional name `Result` 107 | 108 | # 1.0.0-beta.1 109 | This release marks the state of the project before this changelog was kept up to date. 110 | -------------------------------------------------------------------------------- /Documentation/Migration_2.0.md: -------------------------------------------------------------------------------- 1 | # Migrating from 1.0.1 to 2.0.0 2 | 3 | Please read the [changelog](../CHANGELOG.md) for 2.0 first. 4 | 5 | BrightFutures 2.0 has two new dependencies: Result and Box. If you're using CocoaPods, `pod update` should automatically integrate it into your project. If you're using Carthage, after running `carthage update`, you need to add `Result.framework` and `Box.framework` to your target like you have also done for `BrightFutures.framework`. 6 | 7 | In files where you're using `Result` or `Box`, you'll also need to add an import statement for the respecive frameworks. If you fail to do this, you will see errors like "Use of undeclared type 'Result'". 8 | 9 | If you see error messages around `import Result` or `import Box`, the new dependencies have not yet been integrated correctly. 10 | 11 | `Future` and `Result` in BrightFutures 1.0 have only one generic parameter: the value type. In BrightFutures 2.0, both types have gained a second generic parameter: the error type. This removes the dependency on `NSError`. If you want to continue to use `NSError`, this means that you'll have to go through your code and update the occurrences of `Future` and `Result` 12 | 13 | Examples: 14 | 15 | ```swift 16 | // BrightFutures 1.0: 17 | `func loadNextBatch() -> Future` 18 | 19 | // BrightFutures 2.0: 20 | `func loadNextBatch() -> Future` 21 | ``` 22 | 23 | For people that have been using an enum to represent all possible errors, you can now use that error type directly in `Future` and `Result` types without translating it to a `NSError` first. 24 | 25 | If you have been using `Future` in cases where you know the future will never succeed, you can now also use the `NoValue` type. `NoValue` is an enum with no cases, meaning it cannot be initiated. This offers a strong guarantee that a `Future` will never be able to succeed. The `InvalidationToken` uses this. 26 | 27 | If you have futures that you know can never fail, consider using the `NoError` as the error type. Like `NoValue`, `NoError` cannot be instantiated. 28 | 29 | The easiest way to create `Result` instances is through its two constructors. You won't need to use `Box` then: 30 | 31 | ```swift 32 | Result(success: 1314) // a Result.Success 33 | Result(error: .DeserializationFailed(object: json) // a Result.Failure 34 | ``` 35 | In addition to this migration guide, you can take a look at the changes that were made to the tests during the development of 2.0. These can be found in the [2.0 PR diff](https://github.com/Thomvis/BrightFutures/pull/51/files#diff-a6ad99ed0ef578b716f34ca4e2d578f7L43). 36 | -------------------------------------------------------------------------------- /Documentation/Migration_3.0.md: -------------------------------------------------------------------------------- 1 | # Migrating from 2.0.0 to 3.0.0 2 | 3 | - The 'Box' dependency is no longer needed. If you're using a dependency manager, you'll probably won't have to do anything. 4 | - The 'Result' dependency has been updated to a newer version. As a result the static `Result.success` and `Result.failure` methods are no longer available. Use `Result(value:)` and `Result(error:)` instead. 5 | - The static methods on `Future` to create a Future are removed in favor of new initializers: `Future.succeeded` becomes `Future(value:)`, `Future.failed` becomes `Future(failure:)`, `Future.never` becomes `Future()`. 6 | - It should no longer be needed to pass the execution context with an explicit parameter name. E.g. `onSuccess(context: q) {` now is `onSuccess(q) {` 7 | - Many free functions that used to take a Future (e.g. `flatten`, `promoteError`, `promoteValue`) are now methods on the Future itself (thanks to protocol extensions) 8 | - Completion handlers on a future no longer retain that future. If there are no references to a future (meaning that it cannot be completed), it will now be deallocated, regardless of it having completion handlers. 9 | 10 | There are some slight syntactical changes going from Swift 1.2 to Swift 2. Most of them can be fixed by Xcode automatically. 11 | 12 | Tip: use GitHub to generate a diff between the version you're using and the version you want to upgrade to and check out the changes made to the tests. 13 | -------------------------------------------------------------------------------- /Documentation/Migration_4.0.md: -------------------------------------------------------------------------------- 1 | # Migrating from 3.3.0 to 4.0.0 2 | 3 | 4.0.0 is a minor breaking release. When upgrading to BrightFutures 4, please perform the following steps: 4 | 5 | - `SequenceType.fold(_:zero:f:)` and methods that use it (such as `SequenceType.traverse(_:f:)` and `SequenceType.sequence()`) are now slightly more asynchronous. Previously, these methods could return immediately in some cases. This is no longer the case. For all usages in your project, please check that there are no assumptions made on the synchronous execution of those operations. 6 | - `NoError` has been removed from BrightFutures. Use Result's `NoError` instead. You'll have to add `import Result` in the files where you use `NoError`. -------------------------------------------------------------------------------- /Documentation/Migration_5.0.md: -------------------------------------------------------------------------------- 1 | # Migrating from 4.1.1 to 5.0.0 2 | 3 | This migration guide is a work in progress. 4 | 5 | For now, see https://github.com/Thomvis/BrightFutures/compare/v4.1.1...v5.0.0 -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Thomas Visser 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 all 13 | 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 THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.0 2 | import PackageDescription 3 | 4 | let package = Package( 5 | name: "BrightFutures", 6 | products: [ 7 | .library( 8 | name: "BrightFutures", 9 | targets: ["BrightFutures"]), 10 | ], 11 | targets: [ 12 | .target( 13 | name: "BrightFutures", 14 | dependencies: []), 15 | .testTarget( 16 | name: "BrightFuturesTests", 17 | dependencies: ["BrightFutures"], 18 | path: "Tests/BrightFuturesTests") 19 | ] 20 | ) 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | BrightFutures 2 | ============= 3 | 4 | **:warning: BrightFutures has reached end-of-life.** After a long period of limited development activity, Swift's Async/Await has made the library obsolete. Please consider migrating from BrightFutures to async/await. When you do so, the async `get()` method will prove to be useful: 5 | 6 | ```swift 7 | // in an async context... 8 | 9 | let userFuture = User.logIn(username, password) 10 | let user = try await userFuture.get() 11 | 12 | // or simply: 13 | let posts = try await Posts.fetchPosts(user).get() 14 | ``` 15 | 16 | 17 | 18 | The remainder of the README has not been updated recently, but is preserved for historic reasons. 19 | 20 | --- 21 | 22 | How do you leverage the power of Swift to write great asynchronous code? BrightFutures is our answer. 23 | 24 | BrightFutures implements proven [functional concepts](http://en.wikipedia.org/wiki/Futures_and_promises) in Swift to provide a powerful alternative to completion blocks and support typesafe error handling in asynchronous code. 25 | 26 | The goal of BrightFutures is to be *the* idiomatic Swift implementation of futures and promises. 27 | Our Big Hairy Audacious Goal (BHAG) is to be copy-pasted into the Swift standard library. 28 | 29 | The stability of BrightFutures has been proven through extensive use in production. It is currently being used in several apps, with a combined total of almost 500k monthly active users. If you use BrightFutures in production, we'd love to hear about it! 30 | 31 | ## Latest news 32 | [![Join the chat at https://gitter.im/Thomvis/BrightFutures](https://badges.gitter.im/Thomvis/BrightFutures.svg)](https://gitter.im/Thomvis/BrightFutures?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) [![GitHub Workflow tests.yml status badge](https://github.com/Thomvis/BrightFutures/actions/workflows/tests.yml/badge.svg?branch=master)](https://travis-ci.org/Thomvis/BrightFutures) [![Carthage compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](https://github.com/Carthage/Carthage) [![CocoaPods version](https://img.shields.io/cocoapods/v/BrightFutures.svg)](https://cocoapods.org/pods/BrightFutures) [![CocoaPods](https://img.shields.io/cocoapods/metrics/doc-percent/BrightFutures.svg?maxAge=2592000)](http://cocoadocs.org/docsets/BrightFutures) 33 | 34 | BrightFutures 8.0 is now available! This update adds Swift 5 compatibility. 35 | 36 | ## Installation 37 | ### [CocoaPods](http://cocoapods.org/) 38 | 39 | 1. Add the following to your [Podfile](http://guides.cocoapods.org/using/the-podfile.html): 40 | 41 | ```rb 42 | pod 'BrightFutures' 43 | ``` 44 | 45 | 2. Integrate your dependencies using frameworks: add `use_frameworks!` to your Podfile. 46 | 3. Run `pod install`. 47 | 48 | ### [Carthage](https://github.com/Carthage/Carthage) 49 | 50 | 1. Add the following to your [Cartfile](https://github.com/Carthage/Carthage/blob/master/Documentation/Artifacts.md#cartfile): 51 | 52 | ``` 53 | github "Thomvis/BrightFutures" 54 | ``` 55 | 56 | 2. Run `carthage update` and follow the steps as described in Carthage's [README](https://github.com/Carthage/Carthage#adding-frameworks-to-an-application). 57 | 58 | ## Documentation 59 | - This README covers almost all features of BrightFutures 60 | - The [tests](Tests/BrightFuturesTests) contain (trivial) usage examples for every feature (97% test coverage) 61 | - The primary author, Thomas Visser, gave [a talk](https://www.youtube.com/watch?v=lgJT2KMMEmU) at the April 2015 CocoaHeadsNL meetup 62 | - The [Highstreet Watch App](https://github.com/GetHighstreet/HighstreetWatchApp) was an Open Source WatchKit app that made extensive use of an earlier version of BrightFutures 63 | 64 | ## Examples 65 | We write a lot of asynchronous code. Whether we're waiting for something to come in from the network or want to perform an expensive calculation off the main thread and then update the UI, we often do the 'fire and callback' dance. Here's a typical snippet of asynchronous code: 66 | 67 | ```swift 68 | User.logIn(username, password) { user, error in 69 | if !error { 70 | Posts.fetchPosts(user, success: { posts in 71 | // do something with the user's posts 72 | }, failure: handleError) 73 | } else { 74 | handleError(error) // handeError is a custom function to handle errors 75 | } 76 | } 77 | ``` 78 | 79 | Now let's see what BrightFutures can do for you: 80 | 81 | ```swift 82 | User.logIn(username, password).flatMap { user in 83 | Posts.fetchPosts(user) 84 | }.onSuccess { posts in 85 | // do something with the user's posts 86 | }.onFailure { error in 87 | // either logging in or fetching posts failed 88 | } 89 | ``` 90 | 91 | Both `User.logIn` and `Posts.fetchPosts` now immediately return a `Future`. A future can either fail with an error or succeed with a value, which can be anything from an Int to your custom struct, class or tuple. You can keep a future around and register for callbacks for when the future succeeds or fails at your convenience. 92 | 93 | When the future returned from `User.logIn` fails, e.g. the username and password did not match, `flatMap` and `onSuccess` are skipped and `onFailure` is called with the error that occurred while logging in. If the login attempt succeeded, the resulting user object is passed to `flatMap`, which 'turns' the user into an array of his or her posts. If the posts could not be fetched, `onSuccess` is skipped and `onFailure` is called with the error that occurred when fetching the posts. If the posts could be fetched successfully, `onSuccess` is called with the user's posts. 94 | 95 | This is just the tip of the proverbial iceberg. A lot more examples and techniques can be found in this readme, by browsing through the tests or by checking out the official companion framework [FutureProofing](https://github.com/Thomvis/FutureProofing). 96 | 97 | ## Wrapping expressions 98 | If you already have a function (or really any expression) that you just want to execute asynchronously and have a Future to represent its result, you can easily wrap it in an `asyncValue` block: 99 | 100 | ```swift 101 | DispatchQueue.global().asyncValue { 102 | fibonacci(50) 103 | }.onSuccess { num in 104 | // value is 12586269025 105 | } 106 | ``` 107 | 108 | `asyncValue` is defined in an extension on GCD's `DispatchQueue`. While this is really short and simple, it is equally limited. In many cases, you will need a way to indicate that the task failed. To do this, instead of returning the value, you can return a Result. Results can indicate either a success or a failure: 109 | 110 | ```swift 111 | enum ReadmeError: Error { 112 | case RequestFailed, TimeServiceError 113 | } 114 | 115 | let f = DispatchQueue.global().asyncResult { () -> Result in 116 | if let now = serverTime() { 117 | return .success(now) 118 | } 119 | 120 | return .failure(ReadmeError.TimeServiceError) 121 | } 122 | 123 | f.onSuccess { value in 124 | // value will the NSDate from the server 125 | } 126 | ``` 127 | 128 | The future block needs an explicit type because the Swift compiler is not able to deduce the type of multi-statement blocks. 129 | 130 | Instead of wrapping existing expressions, it is often a better idea to use a Future as the return type of a method so all call sites can benefit. This is explained in the next section. 131 | 132 | ## Providing Futures 133 | Now let's assume the role of an API author who wants to use BrightFutures. A Future is designed to be read-only, except for the site where the Future is created. This is achieved via an initialiser on Future that takes a closure, the completion scope, in which you can complete the Future. The completion scope has one parameter that is also a closure which is invoked to set the value (or error) in the Future. 134 | 135 | ```swift 136 | func asyncCalculation() -> Future { 137 | return Future { complete in 138 | DispatchQueue.global().async { 139 | // do a complicated task and then hand the result to the promise: 140 | complete(.success("forty-two")) 141 | } 142 | } 143 | } 144 | ``` 145 | 146 | `Never` indicates that the `Future` cannot fail. This is guaranteed by the type system, since `Never` has no initializers. As an alternative to the completion scope, you could also create a `Promise`, which is the writeable equivalent of a Future, and store it somewhere for later use. 147 | 148 | ## Callbacks 149 | You can be informed of the result of a `Future` by registering callbacks: `onComplete`, `onSuccess` and `onFailure`. The order in which the callbacks are executed upon completion of the future is not guaranteed, but it is guaranteed that the callbacks are executed serially. It is not safe to add a new callback from within a callback of the same future. 150 | 151 | ## Chaining callbacks 152 | 153 | Using the `andThen` function on a `Future`, the order of callbacks can be explicitly defined. The closure passed to `andThen` is meant to perform side-effects and does not influence the result. `andThen` returns a new Future with the same result as this future that completes after the closure has been executed. 154 | 155 | ```swift 156 | var answer = 10 157 | 158 | let _ = Future(value: 4).andThen { result in 159 | switch result { 160 | case .success(let val): 161 | answer *= val 162 | case .failure(_): 163 | break 164 | } 165 | }.andThen { result in 166 | if case .success(_) = result { 167 | answer += 2 168 | } 169 | } 170 | 171 | // answer will be 42 (not 48) 172 | ``` 173 | 174 | ## Functional Composition 175 | 176 | ### map 177 | 178 | `map` returns a new Future that contains the error from this Future if this Future failed, or the return value from the given closure that was applied to the value of this Future. 179 | 180 | ```swift 181 | fibonacciFuture(10).map { number -> String in 182 | if number > 5 { 183 | return "large" 184 | } 185 | return "small" 186 | }.map { sizeString in 187 | sizeString == "large" 188 | }.onSuccess { numberIsLarge in 189 | // numberIsLarge is true 190 | } 191 | ``` 192 | 193 | ### flatMap 194 | 195 | `flatMap` is used to map the result of a future to the value of a new Future. 196 | 197 | ```swift 198 | fibonacciFuture(10).flatMap { number in 199 | fibonacciFuture(number) 200 | }.onSuccess { largeNumber in 201 | // largeNumber is 139583862445 202 | } 203 | ``` 204 | 205 | ### zip 206 | 207 | ```swift 208 | let f = Future(value: 1) 209 | let f1 = Future(value: 2) 210 | 211 | f.zip(f1).onSuccess { a, b in 212 | // a is 1, b is 2 213 | } 214 | ``` 215 | 216 | ### filter 217 | ```swift 218 | Future(value: 3) 219 | .filter { $0 > 5 } 220 | .onComplete { result in 221 | // failed with error NoSuchElementError 222 | } 223 | 224 | Future(value: "Swift") 225 | .filter { $0.hasPrefix("Sw") } 226 | .onComplete { result in 227 | // succeeded with value "Swift" 228 | } 229 | ``` 230 | 231 | ## Recovering from errors 232 | If a `Future` fails, use `recover` to offer a default or alternative value and continue the callback chain. 233 | 234 | ```swift 235 | // imagine a request failed 236 | Future(error: .RequestFailed) 237 | .recover { _ in // provide an offline default 238 | return 5 239 | }.onSuccess { value in 240 | // value is 5 if the request failed or 10 if the request succeeded 241 | } 242 | ``` 243 | 244 | In addition to `recover`, `recoverWith` can be used to provide a Future that will provide the value to recover with. 245 | 246 | ## Utility Functions 247 | BrightFutures also comes with a number of utility functions that simplify working with multiple futures. These are implemented as free (i.e. global) functions to work around current limitations of Swift. 248 | 249 | ## Fold 250 | The built-in `fold` function allows you to turn a list of values into a single value by performing an operation on every element in the list that *consumes* it as it is added to the resulting value. A trivial usecase for fold would be to calculate the sum of a list of integers. 251 | 252 | Folding a list of Futures is not very convenient with the built-in `fold` function, which is why BrightFutures provides one that works especially well for our use case. BrightFutures' `fold` turns a list of Futures into a single Future that contains the resulting value. This allows us to, for example, calculate the sum of the first 10 Future-wrapped elements of the fibonacci sequence: 253 | 254 | ```swift 255 | let fibonacciSequence = [fibonacciFuture(1), fibonacciFuture(2), ..., fibonacciFuture(10)] 256 | 257 | // 1+1+2+3+5+8+13+21+34+55 258 | fibonacciSequence.fold(0, f: { $0 + $1 }).onSuccess { sum in 259 | // sum is 143 260 | } 261 | ``` 262 | 263 | ## Sequence 264 | With `sequence`, you can turn a list of Futures into a single Future that contains a list of the results from those futures. 265 | 266 | ```swift 267 | let fibonacciSequence = [fibonacciFuture(1), fibonacciFuture(2), ..., fibonacciFuture(10)] 268 | 269 | fibonacciSequence.sequence().onSuccess { fibNumbers in 270 | // fibNumbers is an array of Ints: [1, 1, 2, 3, etc.] 271 | } 272 | ``` 273 | 274 | ## Traverse 275 | `traverse` combines `map` and `fold` in one convenient function. `traverse` takes a list of values and a closure that takes a single value from that list and turns it into a Future. The result of `traverse` is a single Future containing an array of the values from the Futures returned by the given closure. 276 | 277 | ```swift 278 | (1...10).traverse { 279 | i in fibonacciFuture(i) 280 | }.onSuccess { fibNumbers in 281 | // fibNumbers is an array of Ints: [1, 1, 2, 3, etc.] 282 | } 283 | ``` 284 | 285 | ## Delay 286 | `delay` returns a new Future that will complete after waiting for the given interval with the result of the previous Future. 287 | To simplify working with `DispatchTime` and `DispatchTimeInterval`, we recommend to use this [extension](https://gist.github.com/Thomvis/b378f926b6e1a48973f694419ed73aca). 288 | 289 | ```swift 290 | Future(value: 3).delay(2.seconds).andThen { result in 291 | // execute after two additional seconds 292 | } 293 | ``` 294 | 295 | ## Default Threading Model 296 | BrightFutures tries its best to provide a simple and sensible default threading model. In theory, all threads are created equally and BrightFutures shouldn't care about which thread it is on. In practice however, the main thread is _more equal than others_, because it has a special place in our hearts and because you'll often want to be on it to do UI updates. 297 | 298 | A lot of the methods on `Future` accept an optional _execution context_ and a block, e.g. `onSuccess`, `map`, `recover` and many more. The block is executed (when the future is completed) in the given execution context, which in practice is a GCD queue. When the context is not explicitly provided, the following rules will be followed to determine the execution context that is used: 299 | 300 | - if the method is called from the main thread, the block is executed on the main queue 301 | - if the method is not called from the main thread, the block is executed on a global queue 302 | 303 | If you want to have custom threading behavior, skip do do not the section. next [:wink:](https://twitter.com/nedbat/status/194452404794691584) 304 | 305 | ## Custom execution contexts 306 | The default threading behavior can be overridden by providing explicit execution contexts. You can choose from any of the built-in contexts or easily create your own. Default contexts include: any dispatch queue, any `NSOperationQueue` and the `ImmediateExecutionContext` for when you don't want to switch threads/queues. 307 | 308 | ```swift 309 | let f = Future { complete in 310 | DispatchQueue.global().async { 311 | complete(.success(fibonacci(10))) 312 | } 313 | } 314 | 315 | f.onComplete(DispatchQueue.main.context) { value in 316 | // update the UI, we're on the main thread 317 | } 318 | ``` 319 | 320 | Even though the future is completed from the global queue, the completion closure will be called on the main queue. 321 | 322 | ## Invalidation tokens 323 | An invalidation token can be used to invalidate a callback, preventing it from being executed upon completion of the future. This is particularly useful in cases where the context in which a callback is executed changes often and quickly, e.g. in reusable views such as table views and collection view cells. An example of the latter: 324 | 325 | ```swift 326 | class MyCell : UICollectionViewCell { 327 | var token = InvalidationToken() 328 | 329 | public override func prepareForReuse() { 330 | super.prepareForReuse() 331 | token.invalidate() 332 | token = InvalidationToken() 333 | } 334 | 335 | public func setModel(model: Model) { 336 | ImageLoader.loadImage(model.image).onSuccess(token.validContext) { [weak self] UIImage in 337 | self?.imageView.image = UIImage 338 | } 339 | } 340 | } 341 | ``` 342 | 343 | By invalidating the token on every reuse, we prevent that the image of the previous model is set after the next model has been set. 344 | 345 | Invalidation tokens _do not_ cancel the task that the future represents. That is a different problem. With invalidation tokens, the result is merely ignored. Invalidating a token after the original future completed does nothing. 346 | 347 | If you are looking for a way to cancel a running task, you could look into using [NSProgress](https://developer.apple.com/library/ios/documentation/Foundation/Reference/NSProgress_Class/Reference/Reference.html). 348 | 349 | ## Credits 350 | 351 | BrightFutures' primary author is [Thomas Visser](https://twitter.com/thomvis). He is lead iOS Engineer at [Highstreet](http://www.highstreetapp.com/). We welcome any feedback and pull requests. Get your name on [this list](https://github.com/Thomvis/BrightFutures/graphs/contributors)! 352 | 353 | BrightFutures was inspired by Facebook's [BFTasks](https://github.com/BoltsFramework/Bolts-iOS), the Promises & Futures implementation in [Scala](http://docs.scala-lang.org/overviews/core/futures.html) and Max Howell's [PromiseKit](https://github.com/mxcl/PromiseKit). 354 | 355 | ## License 356 | 357 | BrightFutures is available under the MIT license. See the LICENSE file for more info. 358 | -------------------------------------------------------------------------------- /Sources/BrightFutures/Async.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Async.swift 3 | // BrightFutures 4 | // 5 | // Created by Thomas Visser on 09/07/15. 6 | // Copyright © 2015 Thomas Visser. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | /// Implementation of the `AsyncType` protocol 12 | /// `Async` represents the result of an asynchronous operation 13 | /// and is typically returned from a method that initiates that 14 | /// asynchronous operation. 15 | /// Clients of that method receive the `Async` and can use it 16 | /// to register a callback for when the result of the asynchronous 17 | /// operation comes in. 18 | /// 19 | /// This class is often not used directly. Instead, its subclass 20 | /// `Future` is used. 21 | open class Async: AsyncType { 22 | 23 | typealias CompletionCallback = (Value) -> Void 24 | 25 | /// The actual result of the operation that the receiver represents or 26 | /// `.None` if the operation is not yet completed. 27 | public fileprivate(set) var result: Value? { 28 | willSet { 29 | assert(result == nil) 30 | } 31 | 32 | didSet { 33 | assert(result != nil) 34 | runCallbacks() 35 | } 36 | } 37 | 38 | /// This queue is used for all callback related administrative tasks 39 | /// to prevent that a callback is added to a completed future and never 40 | /// executed or perhaps excecuted twice. 41 | fileprivate let queue = DispatchQueue(label: "Internal Async Queue") 42 | 43 | /// Upon completion of the future, all callbacks are asynchronously scheduled to their 44 | /// respective execution contexts (which is either given by the client or returned from 45 | /// DefaultThreadingModel). Inside the context, this semaphore will be used 46 | /// to make sure that all callbacks are executed serially. 47 | fileprivate let callbackExecutionSemaphore = DispatchSemaphore(value: 1); 48 | fileprivate var callbacks = [CompletionCallback]() 49 | 50 | /// Creates an uncompleted `Async` 51 | public required init() { 52 | 53 | } 54 | 55 | /// Creates an `Async` that is completed with the given result 56 | public required init(result: Value) { 57 | self.result = result 58 | } 59 | 60 | /// Creates an `Async` that will be completed with the given result after the specified delay 61 | public required init(result: Value, delay: DispatchTimeInterval) { 62 | DispatchQueue.global().asyncAfter(deadline: DispatchTime.now() + delay) { 63 | self.complete(result) 64 | } 65 | } 66 | 67 | /// Creates an `Async` that is completed when the given other `Async` is completed 68 | public required init(other: A) where A.Value == Value { 69 | completeWith(other) 70 | } 71 | 72 | /// Creates an `Async` that can be completed by calling the `result` closure passed to 73 | /// the `resolver`. Example: 74 | /// 75 | /// Async { res in 76 | /// Queue.async { 77 | /// // do some work 78 | /// res(42) // complete the async with result '42' 79 | /// } 80 | /// } 81 | /// 82 | public required init(resolver: (_ result: @escaping (Value) -> Void) -> Void) { 83 | resolver { val in 84 | self.complete(val) 85 | } 86 | } 87 | 88 | private func runCallbacks() { 89 | guard let result = self.result else { 90 | assert(false, "can only run callbacks on a completed future") 91 | return 92 | } 93 | 94 | for callback in self.callbacks { 95 | callback(result) 96 | } 97 | 98 | self.callbacks.removeAll() 99 | } 100 | 101 | /// Adds the given closure as a callback for when the Async completes. The closure is executed on the given context. 102 | /// If no context is given, the behavior is defined by the default threading model (see README.md) 103 | /// Returns self 104 | @discardableResult 105 | open func onComplete(_ context: @escaping ExecutionContext = defaultContext(), callback: @escaping (Value) -> Void) -> Self { 106 | let wrappedCallback : (Value) -> Void = { [weak self] value in 107 | let s = self 108 | context { 109 | s?.callbackExecutionSemaphore.context { 110 | callback(value) 111 | } 112 | return 113 | } 114 | } 115 | 116 | queue.sync { 117 | if let value = self.result { 118 | wrappedCallback(value) 119 | } else { 120 | self.callbacks.append(wrappedCallback) 121 | 122 | } 123 | } 124 | 125 | return self 126 | } 127 | 128 | } 129 | 130 | extension Async: MutableAsyncType { 131 | @discardableResult 132 | func tryComplete(_ value: Value) -> Bool{ 133 | return queue.sync { 134 | guard self.result == nil else { 135 | return false 136 | } 137 | 138 | self.result = value 139 | return true 140 | } 141 | } 142 | } 143 | 144 | extension Async: CustomStringConvertible, CustomDebugStringConvertible { 145 | public var description: String { 146 | return "Async<\(Value.self)>(\(String(describing: self.result)))" 147 | } 148 | 149 | public var debugDescription: String { 150 | return description 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /Sources/BrightFutures/AsyncType+Debug.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AsyncType+Debug.swift 3 | // BrightFutures 4 | // 5 | // Created by Oleksii on 23/09/2016. 6 | // Copyright © 2016 Thomas Visser. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public protocol LoggerType { 12 | func log(message: String) 13 | func message(for value: Value, with identifier: String?, file: String, line: UInt, function: String) -> String 14 | } 15 | 16 | public extension LoggerType { 17 | func message(for value: Value, with identifier: String?, file: String, line: UInt, function: String) -> String { 18 | let messageBody: String 19 | 20 | if let identifier = identifier { 21 | messageBody = "Future \(identifier)" 22 | } else { 23 | let fileName = (file as NSString).lastPathComponent 24 | messageBody = "\(fileName) at line \(line), func: \(function) - future" 25 | } 26 | 27 | return "\(messageBody) completed" 28 | } 29 | } 30 | 31 | public struct Logger: LoggerType { 32 | public init() { 33 | } 34 | 35 | public func log(message: String) { 36 | print(message) 37 | } 38 | } 39 | 40 | public extension AsyncType { 41 | func debug(_ identifier: String? = nil, logger: LoggerType = Logger(), file: String = #file, line: UInt = #line, function: String = #function, context c: @escaping ExecutionContext = defaultContext()) -> Self { 42 | return andThen(context: c, callback: { result in 43 | let message = logger.message(for: result, with: identifier, file: file, line: line, function: function) 44 | logger.log(message: message) 45 | }) 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /Sources/BrightFutures/AsyncType+ResultType.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Async+ResultType.swift 3 | // BrightFutures 4 | // 5 | // Created by Thomas Visser on 10/07/15. 6 | // Copyright © 2015 Thomas Visser. All rights reserved. 7 | // 8 | 9 | public extension AsyncType where Value: ResultProtocol { 10 | /// `true` if the future completed with success, or `false` otherwise 11 | var isSuccess: Bool { 12 | return result?.analysis(ifSuccess: { _ in return true }, ifFailure: { _ in return false }) ?? false 13 | } 14 | 15 | /// `true` if the future failed, or `false` otherwise 16 | var isFailure: Bool { 17 | return result?.analysis(ifSuccess: { _ in return false }, ifFailure: { _ in return true }) ?? false 18 | } 19 | 20 | var value: Value.Value? { 21 | return result?.result.value 22 | } 23 | 24 | var error: Value.Error? { 25 | return result?.result.error 26 | } 27 | 28 | /// Adds the given closure as a callback for when the future succeeds. The closure is executed on the given context. 29 | /// If no context is given, the behavior is defined by the default threading model (see README.md) 30 | /// Returns self 31 | @discardableResult 32 | func onSuccess(_ context: @escaping ExecutionContext = defaultContext(), callback: @escaping (Value.Value) -> Void) -> Self { 33 | self.onComplete(context) { result in 34 | result.analysis(ifSuccess: callback, ifFailure: { _ in }) 35 | } 36 | 37 | return self 38 | } 39 | 40 | /// Adds the given closure as a callback for when the future fails. The closure is executed on the given context. 41 | /// If no context is given, the behavior is defined by the default threading model (see README.md) 42 | /// Returns self 43 | @discardableResult 44 | func onFailure(_ context: @escaping ExecutionContext = defaultContext(), callback: @escaping (Value.Error) -> Void) -> Self { 45 | self.onComplete(context) { result in 46 | result.analysis(ifSuccess: { _ in }, ifFailure: callback) 47 | } 48 | return self 49 | } 50 | 51 | /// Enables the the chaining of two future-wrapped asynchronous operations where the second operation depends on the success value of the first. 52 | /// Like map, the given closure (that returns the second operation) is only executed if the first operation (this future) is successful. 53 | /// If a regular `map` was used, the result would be a `Future>`. The implementation of this function uses `map`, but then flattens the result 54 | /// before returning it. 55 | /// 56 | /// If this future fails, the returned future will fail with the same error. 57 | /// If this future succeeds, the returned future will complete with the future returned from the given closure. 58 | /// 59 | /// The closure is executed on the given context. If no context is given, the behavior is defined by the default threading model (see README.md) 60 | func flatMap(_ context: @escaping ExecutionContext, f: @escaping (Value.Value) -> Future) -> Future { 61 | return map(context, f: f).flatten() 62 | } 63 | 64 | /// See `flatMap(context c: ExecutionContext, f: T -> Future) -> Future` 65 | /// The given closure is executed according to the default threading model (see README.md) 66 | func flatMap(_ f: @escaping (Value.Value) -> Future) -> Future { 67 | return flatMap(defaultContext(), f: f) 68 | } 69 | 70 | /// Transforms the given closure returning `Result` to a closure returning `Future` and then calls 71 | /// `flatMap(context c: ExecutionContext, f: T -> Future) -> Future` 72 | func flatMap(_ context: @escaping ExecutionContext, f: @escaping (Value.Value) -> Result) -> Future { 73 | return self.flatMap(context) { value in 74 | return Future(result: f(value)) 75 | } 76 | } 77 | 78 | /// See `flatMap(context c: ExecutionContext, f: T -> Result) -> Future` 79 | /// The given closure is executed according to the default threading model (see README.md) 80 | func flatMap(_ f: @escaping (Value.Value) -> Result) -> Future { 81 | return flatMap(defaultContext(), f: f) 82 | } 83 | 84 | /// See `map(context c: ExecutionContext, f: (T) -> U) -> Future` 85 | /// The given closure is executed according to the default threading model (see README.md) 86 | func map(_ f: @escaping (Value.Value) -> U) -> Future { 87 | return self.map(defaultContext(), f: f) 88 | } 89 | 90 | #if !swift(>=5.2) 91 | /// Similar to `func map(_ f: @escaping (Value.Value) -> U) -> Future`, but using `KeyPath` instead of a closure 92 | func map(_ keyPath: KeyPath) -> Future { 93 | return self.map(DefaultThreadingModel(), keyPath: keyPath) 94 | } 95 | #endif 96 | 97 | /// Returns a future that succeeds with the value returned from the given closure when it is invoked with the success value 98 | /// from this future. If this future fails, the returned future fails with the same error. 99 | /// The closure is executed on the given context. If no context is given, the behavior is defined by the default threading model (see README.md) 100 | func map(_ context: @escaping ExecutionContext, f: @escaping (Value.Value) -> U) -> Future { 101 | let res = Future() 102 | 103 | self.onComplete(context, callback: { (result: Value) in 104 | result.analysis( 105 | ifSuccess: { res.success(f($0)) }, 106 | ifFailure: { res.failure($0) }) 107 | }) 108 | 109 | return res 110 | } 111 | 112 | #if !swift(>=5.2) 113 | /// Similar to `func map(_ context: @escaping ExecutionContext, f: @escaping (Value.Value) -> U) -> Future`, but using `KeyPath` instead of a closure 114 | func map(_ context: @escaping ExecutionContext, keyPath: KeyPath) -> Future { 115 | return self.map { $0[keyPath: keyPath] } 116 | } 117 | #endif 118 | 119 | /// Returns a future that completes with this future if this future succeeds or with the value returned from the given closure 120 | /// when it is invoked with the error that this future failed with. 121 | /// The closure is executed on the given context. If no context is given, the behavior is defined by the default threading model (see README.md) 122 | func recover(context c: @escaping ExecutionContext = defaultContext(), task: @escaping (Value.Error) -> Value.Value) -> Future { 123 | return self.recoverWith(context: c) { error -> Future in 124 | return Future(value: task(error)) 125 | } 126 | } 127 | 128 | /// Returns a future that completes with this future if this future succeeds or with the value returned from the given closure 129 | /// when it is invoked with the error that this future failed with. 130 | /// This function should be used in cases where there are two asynchronous operations where the second operation (returned from the given closure) 131 | /// should only be executed if the first (this future) fails. 132 | /// The closure is executed on the given context. If no context is given, the behavior is defined by the default threading model (see README.md) 133 | func recoverWith(context c: @escaping ExecutionContext = defaultContext(), task: @escaping (Value.Error) -> Future) -> Future { 134 | let res = Future() 135 | 136 | self.onComplete(c) { result in 137 | result.analysis( 138 | ifSuccess: { res.success($0) }, 139 | ifFailure: { res.completeWith(task($0)) }) 140 | } 141 | 142 | return res 143 | } 144 | 145 | /// See `mapError(context c: ExecutionContext, f: E -> E1) -> Future` 146 | /// The given closure is executed according to the default threading model (see README.md) 147 | func mapError(_ f: @escaping (Value.Error) -> E1) -> Future { 148 | return mapError(defaultContext(), f: f) 149 | } 150 | 151 | /// Returns a future that fails with the error returned from the given closure when it is invoked with the error 152 | /// from this future. If this future succeeds, the returned future succeeds with the same value and the closure is not executed. 153 | /// The closure is executed on the given context. 154 | func mapError(_ context: @escaping ExecutionContext, f: @escaping (Value.Error) -> E1) -> Future { 155 | let res = Future() 156 | 157 | self.onComplete(context) { result in 158 | result.analysis( 159 | ifSuccess: { res.success($0) } , 160 | ifFailure: { res.failure(f($0)) }) 161 | } 162 | 163 | return res 164 | } 165 | 166 | /// Returns a future that succeeds with a tuple consisting of the success value of this future and the success value of the given future 167 | /// If either of the two futures fail, the returned future fails with the failure of this future or that future (in this order) 168 | func zip(_ that: Future) -> Future<(Value.Value,U), Value.Error> { 169 | return flatMap(immediateExecutionContext) { thisVal -> Future<(Value.Value,U), Value.Error> in 170 | return that.map(immediateExecutionContext) { thatVal in 171 | return (thisVal, thatVal) 172 | } 173 | } 174 | } 175 | 176 | /// Returns a future that succeeds with the value that this future succeeds with if it passes the test 177 | /// (i.e. the given closure returns `true` when invoked with the success value) or an error with code 178 | /// `ErrorCode.noSuchElement` if the test failed. 179 | /// If this future fails, the returned future fails with the same error. 180 | func filter(_ p: @escaping (Value.Value) -> Bool) -> Future> { 181 | return self.mapError(immediateExecutionContext) { error in 182 | return BrightFuturesError(external: error) 183 | }.flatMap(immediateExecutionContext) { value -> Result> in 184 | if p(value) { 185 | return .success(value) 186 | } else { 187 | return .failure(.noSuchElement) 188 | } 189 | } 190 | } 191 | 192 | /// Returns a new future with the new type. 193 | /// The value or error will be casted using `as!` and may cause a runtime error 194 | func forceType() -> Future { 195 | return self.map(immediateExecutionContext) { 196 | $0 as! U 197 | }.mapError(immediateExecutionContext) { 198 | $0 as! E1 199 | } 200 | } 201 | 202 | /// Returns a new future that completes with this future, but returns Void on success 203 | func asVoid() -> Future { 204 | return self.map(immediateExecutionContext) { _ in return () } 205 | } 206 | } 207 | 208 | public extension AsyncType where Value: ResultProtocol, Value.Value: AsyncType, Value.Value.Value: ResultProtocol, Value.Error == Value.Value.Value.Error { 209 | /// Returns a future that fails with the error from the outer or inner future or succeeds with the value from the inner future 210 | /// if both futures succeed. 211 | func flatten() -> Future { 212 | let f = Future() 213 | 214 | onComplete(immediateExecutionContext) { res in 215 | res.analysis(ifSuccess: { innerFuture -> () in 216 | innerFuture.onComplete(immediateExecutionContext) { (res:Value.Value.Value) in 217 | res.analysis(ifSuccess: { f.success($0) }, ifFailure: { err in f.failure(err) }) 218 | } 219 | }, ifFailure: { f.failure($0) }) 220 | } 221 | 222 | return f 223 | } 224 | 225 | } 226 | 227 | public extension AsyncType where Value: ResultProtocol, Value.Error == Never { 228 | /// 'promotes' a `Future` with error type `Never` to a `Future` with an error type of choice. 229 | /// This allows the `Future` to be used more easily in combination with other futures 230 | /// for operations such as `sequence` and `firstCompleted` 231 | /// This is a safe operation, because a `Future` with error type `Never` is guaranteed never to fail 232 | func promoteError() -> Future { 233 | let res = Future() 234 | 235 | self.onComplete(immediateExecutionContext) { result in 236 | switch result.result { 237 | case .success(let value): 238 | res.success(value) 239 | case .failure: 240 | break // future will never fail, so this cast will never get called 241 | } 242 | } 243 | 244 | return res 245 | } 246 | } 247 | 248 | public extension AsyncType where Value: ResultProtocol, Value.Error == BrightFuturesError { 249 | /// 'promotes' a `Future` with error type `BrightFuturesError` to a `Future` with an 250 | /// `BrightFuturesError` error type where `E` can be any type conforming to `ErrorType`. 251 | /// This allows the `Future` to be used more easily in combination with other futures 252 | /// for operations such as `sequence` and `firstCompleted` 253 | /// This is a safe operation, because a `BrightFuturesError` will never be `.External` 254 | func promoteError() -> Future> { 255 | return mapError(immediateExecutionContext) { err in 256 | switch err { 257 | case .noSuchElement: 258 | return BrightFuturesError.noSuchElement 259 | case .invalidationTokenInvalidated: 260 | return BrightFuturesError.invalidationTokenInvalidated 261 | case .illegalState: 262 | return BrightFuturesError.illegalState 263 | case .external(_): 264 | fatalError("Encountered BrightFuturesError.External with Never, which should be impossible") 265 | } 266 | } 267 | } 268 | } 269 | 270 | public extension AsyncType where Value: ResultProtocol, Value.Value == Never { 271 | /// 'promotes' a `Future` with value type `NoValue` to a `Future` with a value type of choice. 272 | /// This allows the `Future` to be used more easily in combination with other futures 273 | /// for operations such as `sequence` and `firstCompleted` 274 | /// This is a safe operation, because a `Future` with value type `NoValue` is guaranteed never to succeed 275 | func promoteValue() -> Future { 276 | Future { completion in 277 | self.onComplete(immediateExecutionContext) { result in 278 | switch result.result { 279 | case .success: 280 | break // future will never succeed, so this cast will never get called 281 | case .failure(let error): 282 | completion(.failure(error)) 283 | } 284 | } 285 | } 286 | } 287 | } 288 | 289 | -------------------------------------------------------------------------------- /Sources/BrightFutures/AsyncType.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Async.swift 3 | // BrightFutures 4 | // 5 | // Created by Thomas Visser on 09/07/15. 6 | // Copyright © 2015 Thomas Visser. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public protocol AsyncType { 12 | associatedtype Value 13 | 14 | var result: Value? { get } 15 | 16 | init() 17 | init(result: Value) 18 | init(result: Value, delay: DispatchTimeInterval) 19 | init(other: A) where A.Value == Value 20 | init(resolver: (_ result: @escaping (Value) -> Void) -> Void) 21 | 22 | @discardableResult 23 | func onComplete(_ context: @escaping ExecutionContext, callback: @escaping (Value) -> Void) -> Self 24 | } 25 | 26 | public extension AsyncType { 27 | /// `true` if the future completed (either `isSuccess` or `isFailure` will be `true`) 28 | var isCompleted: Bool { 29 | return result != nil 30 | } 31 | 32 | /// Blocks the current thread until the future is completed and then returns the result 33 | func forced() -> Value { 34 | return forced(DispatchTime.distantFuture)! 35 | } 36 | 37 | /// Blocks the current thread until the future is completed, but no longer than the given timeout 38 | /// If the future did not complete before the timeout, `nil` is returned, otherwise the result of the future is returned 39 | func forced(_ timeout: DispatchTime) -> Value? { 40 | if let result = result { 41 | return result 42 | } 43 | 44 | let sema = DispatchSemaphore(value: 0) 45 | var res: Value? = nil 46 | onComplete(DispatchQueue.global().context) { 47 | res = $0 48 | sema.signal() 49 | } 50 | 51 | let _ = sema.wait(timeout: timeout) 52 | 53 | return res 54 | } 55 | 56 | /// Alias of delay(queue:interval:) 57 | /// Will pass the main queue if we are currently on the main thread, or the 58 | /// global queue otherwise 59 | func delay(_ interval: DispatchTimeInterval) -> Self { 60 | if Thread.isMainThread { 61 | return delay(DispatchQueue.main, interval: interval) 62 | } 63 | 64 | return delay(DispatchQueue.global(), interval: interval) 65 | } 66 | 67 | /// Returns an Async that will complete with the result that this Async completes with 68 | /// after waiting for the given interval 69 | /// The delay is implemented using dispatch_after. The given queue is passed to that function. 70 | /// If you want a delay of 0 to mean 'delay until next runloop', you will want to pass the main 71 | /// queue. 72 | func delay(_ queue: DispatchQueue, interval: DispatchTimeInterval) -> Self { 73 | return Self { complete in 74 | onComplete(immediateExecutionContext) { result in 75 | queue.asyncAfter(deadline: DispatchTime.now() + interval) { 76 | complete(result) 77 | } 78 | } 79 | } 80 | } 81 | 82 | /// Adds the given closure as a callback for when this future completes. 83 | /// The closure is executed on the given context. If no context is given, the behavior is defined by the default threading model (see README.md) 84 | /// Returns a future that completes with the result from this future but only after executing the given closure 85 | func andThen(context c: @escaping ExecutionContext = defaultContext(), callback: @escaping (Self.Value) -> Void) -> Self { 86 | return Self { complete in 87 | onComplete(c) { result in 88 | callback(result) 89 | complete(result) 90 | } 91 | } 92 | } 93 | } 94 | 95 | public extension AsyncType where Value: AsyncType { 96 | func flatten() -> Self.Value { 97 | return Self.Value { complete in 98 | self.onComplete(immediateExecutionContext) { value in 99 | value.onComplete(immediateExecutionContext, callback: complete) 100 | } 101 | } 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /Sources/BrightFutures/Dispatch+BrightFutures.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Dispatch+BrightFutures.swift 3 | // BrightFutures 4 | // 5 | // Created by Thomas Visser on 13/08/16. 6 | // Copyright © 2016 Thomas Visser. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public extension DispatchQueue { 12 | var context: ExecutionContext { 13 | return { task in 14 | self.async(execute: task) 15 | } 16 | } 17 | 18 | func asyncValue(_ execute: @escaping () -> T) -> Future { 19 | return Future { completion in 20 | async { 21 | completion(.success(execute())) 22 | } 23 | } 24 | } 25 | 26 | func asyncResult(_ execute: @escaping () -> Result) -> Future { 27 | return Future { completion in 28 | async { 29 | completion(execute()) 30 | } 31 | } 32 | } 33 | 34 | func asyncValueAfter(_ deadline: DispatchTime, execute: @escaping () -> T) -> Future { 35 | return Future { completion in 36 | asyncAfter(deadline: deadline) { 37 | completion(.success(execute())) 38 | } 39 | } 40 | } 41 | 42 | } 43 | 44 | public extension DispatchSemaphore { 45 | var context: ExecutionContext { 46 | return { task in 47 | let _ = self.wait(timeout: DispatchTime.distantFuture) 48 | task() 49 | self.signal() 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /Sources/BrightFutures/Errors.swift: -------------------------------------------------------------------------------- 1 | // The MIT License (MIT) 2 | // 3 | // Copyright (c) 2014 Thomas Visser 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 all 13 | // 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 THE 21 | // SOFTWARE. 22 | 23 | import Foundation 24 | 25 | /// An enum representing every possible error for errors returned by BrightFutures 26 | /// A `BrightFuturesError` can also wrap an external error (e.g. coming from a user defined future) 27 | /// in its `External` case. `BrightFuturesError` has the type of the external error as its generic parameter. 28 | public enum BrightFuturesError: Error { 29 | 30 | /// Indicates that a matching element could not be found, e.g. while filtering or finding 31 | case noSuchElement 32 | 33 | /// Used in the implementation of InvalidationToken 34 | case invalidationTokenInvalidated 35 | 36 | /// Indicates that an invalid / unexpected state was reached. This error is used in places that should not be executed 37 | case illegalState 38 | 39 | /// Wraps a different ErrorType instance 40 | case external(E) 41 | 42 | /// Constructs a BrightFutures.External with the given external error 43 | public init(external: E) { 44 | self = .external(external) 45 | } 46 | } 47 | 48 | extension BrightFuturesError: Equatable where E: Equatable { 49 | /// Returns `true` if `left` and `right` are both of the same case ignoring .External associated value 50 | public static func ==(lhs: BrightFuturesError, rhs: BrightFuturesError) -> Bool { 51 | switch (lhs, rhs) { 52 | case (.noSuchElement, .noSuchElement): return true 53 | case (.invalidationTokenInvalidated, .invalidationTokenInvalidated): return true 54 | case (.external(let lhs), .external(let rhs)): return lhs == rhs 55 | default: return false 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /Sources/BrightFutures/ExecutionContext.swift: -------------------------------------------------------------------------------- 1 | // The MIT License (MIT) 2 | // 3 | // Copyright (c) 2014 Thomas Visser 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 all 13 | // 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 THE 21 | // SOFTWARE. 22 | 23 | import Foundation 24 | 25 | /// The context in which something can be executed 26 | /// By default, an execution context can be assumed to be asynchronous unless stated otherwise 27 | public typealias ExecutionContext = (@escaping () -> Void) -> Void 28 | 29 | /// Immediately executes the given task. No threading, no semaphores. 30 | public let immediateExecutionContext: ExecutionContext = { task in 31 | task() 32 | } 33 | 34 | /// Runs immediately if on the main thread, otherwise asynchronously on the main thread 35 | public let immediateOnMainExecutionContext: ExecutionContext = { task in 36 | if Thread.isMainThread { 37 | task() 38 | } else { 39 | DispatchQueue.main.async(execute: task) 40 | } 41 | } 42 | 43 | /// From https://github.com/BoltsFramework/Bolts-Swift/blob/5fe4df7acb384a93ad93e8595d42e2b431fdc266/Sources/BoltsSwift/Executor.swift#L56 44 | public let maxStackDepthExecutionContext: ExecutionContext = { task in 45 | struct Static { 46 | static let taskDepthKey = "nl.thomvis.BrightFutures" 47 | static let maxTaskDepth = 20 48 | } 49 | 50 | var localThreadDictionary = Thread.current.threadDictionary 51 | 52 | var previousDepth: Int 53 | if let depth = localThreadDictionary[Static.taskDepthKey] as? Int { 54 | previousDepth = depth 55 | } else { 56 | previousDepth = 0 57 | } 58 | 59 | if previousDepth > Static.maxTaskDepth { 60 | DispatchQueue.global().async(execute: task) 61 | } else { 62 | localThreadDictionary[Static.taskDepthKey] = previousDepth + 1 63 | task() 64 | localThreadDictionary[Static.taskDepthKey] = previousDepth 65 | } 66 | } 67 | 68 | /// Defines BrightFutures' default threading behavior: 69 | /// - if on the main thread, `DispatchQueue.main.context` is returned 70 | /// - if off the main thread, `DispatchQueue.global().context` is returned 71 | public func defaultContext() -> ExecutionContext { 72 | return (Thread.isMainThread ? DispatchQueue.main : DispatchQueue.global()).context 73 | } 74 | 75 | // Deprecations 76 | @available(*, deprecated, renamed: "immediateExecutionContext") 77 | public let ImmediateExecutionContext = immediateExecutionContext 78 | @available(*, deprecated, renamed: "immediateOnMainExecutionContext") 79 | public let ImmediateOnMainExecutionContext = immediateOnMainExecutionContext 80 | @available(*, deprecated, renamed: "maxStackDepthExecutionContext") 81 | public let MaxStackDepthExecutionContext = maxStackDepthExecutionContext 82 | @available(*, deprecated, renamed: "defaultContext") 83 | public let DefaultThreadingModel = defaultContext 84 | -------------------------------------------------------------------------------- /Sources/BrightFutures/Future.swift: -------------------------------------------------------------------------------- 1 | // The MIT License (MIT) 2 | // 3 | // Copyright (c) 2014 Thomas Visser 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 all 13 | // 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 THE 21 | // SOFTWARE. 22 | 23 | import Foundation 24 | 25 | /// A Future represents the outcome of an asynchronous operation 26 | /// The outcome will be represented as an instance of the `Result` enum and will be stored 27 | /// in the `result` property. As long as the operation is not yet completed, `result` will be nil. 28 | /// Interested parties can be informed of the completion by using one of the available callback 29 | /// registration methods (e.g. onComplete, onSuccess & onFailure) or by immediately composing/chaining 30 | /// subsequent actions (e.g. map, flatMap, recover, andThen, etc.). 31 | /// 32 | /// For more info, see the project README.md 33 | @dynamicMemberLookup 34 | public final class Future: Async> { 35 | 36 | public typealias CompletionCallback = (_ result: Result) -> Void 37 | public typealias SuccessCallback = (T) -> Void 38 | public typealias FailureCallback = (E) -> Void 39 | 40 | public required init() { 41 | super.init() 42 | } 43 | 44 | public required init(result: Future.Value) { 45 | super.init(result: result) 46 | } 47 | 48 | public init(value: T, delay: DispatchTimeInterval) { 49 | super.init(result: .success(value), delay: delay) 50 | } 51 | 52 | public init(error: E, delay: DispatchTimeInterval) { 53 | super.init(result: .failure(error), delay: delay) 54 | } 55 | 56 | public required init(other: A) where A.Value == Value { 57 | super.init(other: other) 58 | } 59 | 60 | public required init(result: Value, delay: DispatchTimeInterval) { 61 | super.init(result: result, delay: delay) 62 | } 63 | 64 | public convenience init(value: T) { 65 | self.init(result: .success(value)) 66 | } 67 | 68 | public convenience init(error: E) { 69 | self.init(result: .failure(error)) 70 | } 71 | 72 | public required init(resolver: (_ result: @escaping (Value) -> Void) -> Void) { 73 | super.init(resolver: resolver) 74 | } 75 | 76 | public subscript(dynamicMember keyPath: KeyPath) -> Future { 77 | map(immediateExecutionContext) { $0[keyPath: keyPath] } 78 | } 79 | 80 | } 81 | 82 | public func materialize(_ scope: ((T?, E?) -> Void) -> Void) -> Future { 83 | return Future { complete in 84 | scope { val, err in 85 | if let val = val { 86 | complete(.success(val)) 87 | } else if let err = err { 88 | complete(.failure(err)) 89 | } 90 | } 91 | } 92 | } 93 | 94 | public func materialize(_ scope: ((T) -> Void) -> Void) -> Future { 95 | return Future { complete in 96 | scope { val in 97 | complete(.success(val)) 98 | } 99 | } 100 | } 101 | 102 | public func materialize(_ scope: ((E?) -> Void) -> Void) -> Future { 103 | return Future { complete in 104 | scope { err in 105 | if let err = err { 106 | complete(.failure(err)) 107 | } else { 108 | complete(.success(())) 109 | } 110 | } 111 | } 112 | } 113 | 114 | /// Short-hand for `lhs.recover(rhs())` 115 | /// `rhs` is executed according to the default threading model (see README.md) 116 | public func ?? (_ lhs: Future, rhs: @autoclosure @escaping () -> T) -> Future { 117 | return lhs.recover(context: defaultContext(), task: { _ in 118 | return rhs() 119 | }) 120 | } 121 | 122 | /// Short-hand for `lhs.recoverWith(rhs())` 123 | /// `rhs` is executed according to the default threading model (see README.md) 124 | public func ?? (_ lhs: Future, rhs: @autoclosure @escaping () -> Future) -> Future { 125 | return lhs.recoverWith(context: defaultContext(), task: { _ in 126 | return rhs() 127 | }) 128 | } 129 | 130 | @available(*, deprecated, renamed: "Never") 131 | public enum NoValue { } 132 | -------------------------------------------------------------------------------- /Sources/BrightFutures/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 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | ${CURRENT_PROJECT_VERSION} 23 | NSPrincipalClass 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /Sources/BrightFutures/InvalidationToken.swift: -------------------------------------------------------------------------------- 1 | // 2 | // InvalidationToken.swift 3 | // BrightFutures 4 | // 5 | // Created by Thomas Visser on 15/01/15. 6 | // Copyright (c) 2015 Thomas Visser. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | /// The type that all invalidation tokens conform to 12 | public protocol InvalidationTokenType { 13 | 14 | /// Indicates if the token is invalid 15 | var isInvalid : Bool { get } 16 | 17 | /// The future will fail with .InvalidationTokenInvalidated when the token invalidates 18 | var future: Future> { get } 19 | 20 | /// This context executes as long as the token is valid. If the token is invalid, the given blocks are discarded 21 | func validContext(_ parentContext: @escaping ExecutionContext) -> ExecutionContext 22 | 23 | } 24 | 25 | extension InvalidationTokenType { 26 | /// Alias of context(parentContext:task:) which uses the default threading model 27 | /// Due to a limitation of the Swift compiler, we cannot express this with a single method 28 | public var validContext: ExecutionContext { 29 | return validContext(defaultContext()) 30 | } 31 | 32 | public func validContext(_ parentContext: @escaping ExecutionContext = defaultContext()) -> ExecutionContext { 33 | return { task in 34 | parentContext { 35 | if !self.isInvalid { 36 | task() 37 | } 38 | } 39 | } 40 | } 41 | } 42 | 43 | /// The type that all invalidation tokens that can be manually invalidated conform to 44 | public protocol ManualInvalidationTokenType : InvalidationTokenType { 45 | /// Invalidates the token 46 | func invalidate() 47 | } 48 | 49 | /// A default invalidation token implementation 50 | public class InvalidationToken : ManualInvalidationTokenType { 51 | 52 | public let future = Future>() 53 | 54 | /// Creates a new valid token 55 | public init() { } 56 | 57 | /// Indicates if the token is invalid 58 | public var isInvalid: Bool { 59 | return future.isCompleted 60 | } 61 | 62 | /// Invalidates the token 63 | public func invalidate() { 64 | future.failure(.invalidationTokenInvalidated) 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /Sources/BrightFutures/MutableAsyncType+ResultType.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MutableAsyncType+ResultType.swift 3 | // BrightFutures 4 | // 5 | // Created by Thomas Visser on 22/07/15. 6 | // Copyright © 2015 Thomas Visser. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | internal extension MutableAsyncType where Value: ResultProtocol { 12 | /// Completes the future with the given success value 13 | /// If the future is already completed, this function does nothing 14 | /// and an assert will be raised (if enabled) 15 | func success(_ value: Value.Value) { 16 | complete(Value(value: value)) 17 | } 18 | 19 | /// Tries to complete the future with the given success value 20 | /// If the future is already completed, nothing happens and `false` is returned 21 | /// otherwise the future is completed and `true` is returned 22 | func trySuccess(_ value: Value.Value) -> Bool { 23 | return tryComplete(Value(value: value)) 24 | } 25 | 26 | /// Completes the future with the given error 27 | /// If the future is already completed, this function does nothing 28 | /// and an assert will be raised (if enabled) 29 | func failure(_ error: Value.Error) { 30 | complete(Value(error: error)) 31 | } 32 | 33 | /// Tries to complete the future with the given error 34 | /// If the future is already completed, nothing happens and `false` is returned 35 | /// otherwise the future is completed and `true` is returned 36 | func tryFailure(_ error: Value.Error) -> Bool { 37 | return tryComplete(Value(error: error)) 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /Sources/BrightFutures/MutableAsyncType.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MutableAsyncType.swift 3 | // BrightFutures 4 | // 5 | // Created by Thomas Visser on 14/07/15. 6 | // Copyright © 2015 Thomas Visser. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | internal protocol MutableAsyncType: AsyncType { 12 | /// Complete the Async with the given value 13 | /// If the Async is already completed, nothing happens and `false` is returned 14 | /// otherwise the future is completed and `true` is returned 15 | func tryComplete(_ result: Value) -> Bool 16 | } 17 | 18 | extension MutableAsyncType { 19 | 20 | /// Completes the Async with the given result 21 | /// If the Async is already completed, this function throws an error 22 | func complete(_ result: Value) { 23 | if !tryComplete(result) { 24 | print(result) 25 | let error = "Attempted to completed an Async that is already completed. This could become a fatalError." 26 | assert(false, error) 27 | print(error) 28 | } 29 | } 30 | 31 | func completeWith(_ other: A) where A.Value == Value { 32 | other.onComplete(immediateExecutionContext, callback: self.complete) 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /Sources/BrightFutures/NSOperationQueue+BrightFutures.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NSOperationQueue+BrightFutures.swift 3 | // BrightFutures 4 | // 5 | // Created by Thomas Visser on 18/09/15. 6 | // Copyright © 2015 Thomas Visser. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public extension OperationQueue { 12 | /// An execution context that operates on the receiver. 13 | /// Tasks added to the execution context are executed as operations on the queue. 14 | var context: ExecutionContext { 15 | return { [weak self] task in 16 | self?.addOperation(BlockOperation(block: task)) 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Sources/BrightFutures/Promise.swift: -------------------------------------------------------------------------------- 1 | // The MIT License (MIT) 2 | // 3 | // Copyright (c) 2014 Thomas Visser 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 all 13 | // 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 THE 21 | // SOFTWARE. 22 | 23 | import Foundation 24 | 25 | /// The source of a future. Create a `Promise` when you are 26 | /// performing an asynchronous task and want to return a future. 27 | /// Return the future and keep the promise around to complete it 28 | /// when the asynchronous operation is completed. Completing a 29 | /// promise is thread safe and is typically performed from the 30 | /// (background) thread where the operation itself is also performed. 31 | public final class Promise { 32 | 33 | /// The future that will complete through this promise 34 | public let future: Future 35 | 36 | /// Creates a new promise with a pending future 37 | public init() { 38 | self.future = Future() 39 | } 40 | 41 | /// Completes the promise's future with the given future 42 | public func completeWith(_ other: Future) { 43 | future.completeWith(other) 44 | } 45 | 46 | /// Completes the promise's future with the given success value 47 | /// See `Future.success(value: T)` 48 | public func success(_ value: T) { 49 | future.success(value) 50 | } 51 | 52 | /// Attempts to complete the promise's future with the given success value 53 | /// See `future.trySuccess(value: T)` 54 | @discardableResult 55 | public func trySuccess(_ value: T) -> Bool { 56 | return future.trySuccess(value) 57 | } 58 | 59 | /// Completes the promise's future with the given error 60 | /// See `future.failure(error: E)` 61 | public func failure(_ error: E) { 62 | future.failure(error) 63 | } 64 | 65 | /// Attempts to complete the promise's future with the given error 66 | /// See `future.tryFailure(error: E)` 67 | @discardableResult 68 | public func tryFailure(_ error: E) -> Bool { 69 | return future.tryFailure(error) 70 | } 71 | 72 | /// Completes the promise's future with the given result 73 | /// See `future.complete(result: Result)` 74 | public func complete(_ result: Result) { 75 | future.complete(result) 76 | } 77 | 78 | /// Attempts to complete the promise's future with the given result 79 | /// See `future.tryComplete(result: Result)` 80 | @discardableResult 81 | public func tryComplete(_ result: Result) -> Bool { 82 | return future.tryComplete(result) 83 | } 84 | 85 | } 86 | -------------------------------------------------------------------------------- /Sources/BrightFutures/Result+BrightFutures.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Result+BrightFutures.swift 3 | // BrightFutures 4 | // 5 | // Created by Thomas Visser on 30/08/15. 6 | // Copyright © 2015 Thomas Visser. All rights reserved. 7 | // 8 | 9 | public extension ResultProtocol { 10 | 11 | /// Case analysis for Result. 12 | /// 13 | /// Returns the value produced by applying `ifFailure` to `failure` Results, or `ifSuccess` to `success` Results. 14 | func analysis(ifSuccess: (Value) -> Result, ifFailure: (Error) -> Result) -> Result { 15 | switch self.result { 16 | case .success(let value): 17 | return ifSuccess(value) 18 | case .failure(let error): 19 | return ifFailure(error) 20 | } 21 | } 22 | 23 | } 24 | 25 | extension ResultProtocol { 26 | /// Enables the chaining of two failable operations where the second operation is asynchronous and 27 | /// represented by a future. 28 | /// Like map, the given closure (that performs the second operation) is only executed 29 | /// if the first operation result is a .success 30 | /// If a regular `map` was used, the result would be `Result>`. 31 | /// The implementation of this function uses `map`, but then flattens the result before returning it. 32 | public func flatMap(_ f: (Value) -> Future) -> Future { 33 | return analysis(ifSuccess: { 34 | return f($0) 35 | }, ifFailure: { 36 | return Future(error: $0) 37 | }) 38 | } 39 | } 40 | 41 | extension ResultProtocol where Value: ResultProtocol, Error == Value.Error { 42 | 43 | /// Returns a .failure with the error from the outer or inner result if either of the two failed 44 | /// or a .success with the success value from the inner Result 45 | public func flatten() -> Result { 46 | return analysis(ifSuccess: { innerRes in 47 | return innerRes.analysis(ifSuccess: { 48 | return .success($0) 49 | }, ifFailure: { 50 | return .failure($0) 51 | }) 52 | }, ifFailure: { 53 | return .failure($0) 54 | }) 55 | } 56 | } 57 | 58 | extension ResultProtocol where Value: AsyncType, Value.Value: ResultProtocol, Error == Value.Value.Error { 59 | /// Returns the inner future if the outer result succeeded or a failed future 60 | /// with the error from the outer result otherwise 61 | public func flatten() -> Future { 62 | return Future { complete in 63 | analysis(ifSuccess: { innerFuture ->() in 64 | innerFuture.onComplete(immediateExecutionContext) { res in 65 | complete(res.analysis(ifSuccess: { 66 | return .success($0) 67 | }, ifFailure: { 68 | return .failure($0) 69 | })) 70 | } 71 | }, ifFailure: { 72 | complete(.failure($0)) 73 | }) 74 | } 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /Sources/BrightFutures/ResultProtocol.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ResultProtocol.swift 3 | // BrightFutures-iOS 4 | // 5 | // Created by Kim de Vos on 26/03/2019. 6 | // Copyright © 2019 Thomas Visser. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | /// A protocol that can be used to constrain associated types as `Result`. 12 | public protocol ResultProtocol { 13 | associatedtype Value 14 | associatedtype Error: Swift.Error 15 | 16 | init(value: Value) 17 | init(error: Error) 18 | 19 | var result: Result { get } 20 | } 21 | 22 | extension Result: ResultProtocol { 23 | /// Constructs a success wrapping a `value`. 24 | public init(value: Success) { 25 | self = .success(value) 26 | } 27 | 28 | /// Constructs a failure wrapping an `error`. 29 | public init(error: Failure) { 30 | self = .failure(error) 31 | } 32 | 33 | public var result: Result { 34 | return self 35 | } 36 | 37 | public var value: Success? { 38 | switch self { 39 | case .success(let value): return value 40 | case .failure: return nil 41 | } 42 | } 43 | 44 | public var error: Failure? { 45 | switch self { 46 | case .success: return nil 47 | case .failure(let error): return error 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /Sources/BrightFutures/SequenceType+BrightFutures.swift: -------------------------------------------------------------------------------- 1 | // The MIT License (MIT) 2 | // 3 | // Copyright (c) 2014 Thomas Visser 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 all 13 | // 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 THE 21 | // SOFTWARE. 22 | 23 | import Foundation 24 | 25 | extension Sequence { 26 | /// Turns a sequence of T's into an array of `Future`'s by calling the given closure for each element in the sequence. 27 | /// If no context is provided, the given closure is executed on `Queue.global` 28 | public func traverse(_ context: @escaping ExecutionContext = DispatchQueue.global().context, f: (Iterator.Element) -> A) -> Future<[U], E> where A.Value: ResultProtocol, A.Value.Value == U, A.Value.Error == E { 29 | return map(f).fold(context, zero: [U]()) { (list: [U], elem: U) -> [U] in 30 | return list + [elem] 31 | } 32 | } 33 | } 34 | 35 | extension Sequence where Iterator.Element: AsyncType { 36 | /// Returns a future that returns with the first future from the given sequence that completes 37 | /// (regardless of whether that future succeeds or fails) 38 | public func firstCompleted() -> Iterator.Element { 39 | let res = Async() 40 | for fut in self { 41 | fut.onComplete(DispatchQueue.global().context) { 42 | res.tryComplete($0) 43 | } 44 | } 45 | return Iterator.Element(other: res) 46 | } 47 | } 48 | 49 | extension Sequence where Iterator.Element: AsyncType, Iterator.Element.Value: ResultProtocol { 50 | 51 | //// The free functions in this file operate on sequences of Futures 52 | 53 | /// Performs the fold operation over a sequence of futures. The folding is performed 54 | /// on `Queue.global`. 55 | /// (The Swift compiler does not allow a context parameter with a default value 56 | /// so we define some functions twice) 57 | public func fold(_ zero: R, f: @escaping (R, Iterator.Element.Value.Value) -> R) -> Future { 58 | return fold(DispatchQueue.global().context, zero: zero, f: f) 59 | } 60 | 61 | /// Performs the fold operation over a sequence of futures. The folding is performed 62 | /// in the given context. 63 | public func fold(_ context: @escaping ExecutionContext, zero: R, f: @escaping (R, Iterator.Element.Value.Value) -> R) -> Future { 64 | return reduce(Future(value: zero)) { zero, elem in 65 | return zero.flatMap(maxStackDepthExecutionContext) { zeroVal in 66 | elem.map(context) { elemVal in 67 | return f(zeroVal, elemVal) 68 | } 69 | } 70 | } 71 | } 72 | 73 | /// Turns a sequence of `Future`'s into a future with an array of T's (Future<[T]>) 74 | /// If one of the futures in the given sequence fails, the returned future will fail 75 | /// with the error of the first future that comes first in the list. 76 | public func sequence() -> Future<[Iterator.Element.Value.Value], Iterator.Element.Value.Error> { 77 | return traverse(immediateExecutionContext) { 78 | return $0 79 | } 80 | } 81 | 82 | /// See `find>(seq: S, context c: ExecutionContext, p: T -> Bool) -> Future` 83 | public func find(_ p: @escaping (Iterator.Element.Value.Value) -> Bool) -> Future> { 84 | return find(DispatchQueue.global().context, p: p) 85 | } 86 | 87 | /// Returns a future that succeeds with the value from the first future in the given 88 | /// sequence that passes the test `p`. 89 | /// If any of the futures in the given sequence fail, the returned future fails with the 90 | /// error of the first failed future in the sequence. 91 | /// If no futures in the sequence pass the test, a future with an error with NoSuchElement is returned. 92 | public func find(_ context: @escaping ExecutionContext, p: @escaping (Iterator.Element.Value.Value) -> Bool) -> Future> { 93 | return sequence().mapError(immediateExecutionContext) { error in 94 | return BrightFuturesError(external: error) 95 | }.flatMap(context) { val -> Result> in 96 | for elem in val { 97 | if (p(elem)) { 98 | return .success(elem) 99 | } 100 | } 101 | return .failure(.noSuchElement) 102 | } 103 | } 104 | } 105 | 106 | extension Sequence where Iterator.Element: ResultProtocol { 107 | /// Turns a sequence of `Result`'s into a Result with an array of T's (`Result<[T]>`) 108 | /// If one of the results in the given sequence is a .failure, the returned result is a .failure with the 109 | /// error from the first failed result from the sequence. 110 | public func sequence() -> Result<[Iterator.Element.Value], Iterator.Element.Error> { 111 | return reduce(.success([])) { (res, elem) -> Result<[Iterator.Element.Value], Iterator.Element.Error> in 112 | switch res { 113 | case .success(let resultSequence): 114 | return elem.analysis(ifSuccess: { 115 | let newSeq = resultSequence + [$0] 116 | return .success(newSeq) 117 | }, ifFailure: { 118 | return .failure($0) 119 | }) 120 | case .failure(_): 121 | return res 122 | } 123 | } 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /Sources/BrightFutures/SwiftConcurrency+BrightFutures.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SwiftConcurrency+BrightFutures.swift 3 | // BrightFutures 4 | // 5 | // Created by Thomas Visser on 14/06/2021. 6 | // Copyright © 2021 Thomas Visser. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | #if swift(>=5.5) 12 | @available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *) 13 | public extension Async { 14 | @_disfavoredOverload 15 | func get() async -> Value { 16 | await withCheckedContinuation { continuation in 17 | onComplete { result in 18 | continuation.resume(returning: result) 19 | } 20 | } 21 | } 22 | } 23 | 24 | @available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *) 25 | public extension Async where Value: ResultProtocol { 26 | @_disfavoredOverload 27 | func get() async throws -> Value.Value { 28 | try await withCheckedThrowingContinuation { continuation in 29 | onComplete { result in 30 | continuation.resume(with: result.result) 31 | } 32 | } 33 | } 34 | } 35 | 36 | @available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *) 37 | public extension Async where Value: ResultProtocol, Value.Error == Never { 38 | func get() async -> Value.Value { 39 | await withCheckedContinuation { continuation in 40 | onComplete { result in 41 | continuation.resume(with: result.result) 42 | } 43 | } 44 | } 45 | } 46 | #endif 47 | -------------------------------------------------------------------------------- /Tests/BrightFuturesTests/BrightFuturesTests.swift: -------------------------------------------------------------------------------- 1 | // The MIT License (MIT) 2 | // 3 | // Copyright (c) 2014 Thomas Visser 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 all 13 | // 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 THE 21 | // SOFTWARE. 22 | 23 | import XCTest 24 | import BrightFutures 25 | 26 | class BrightFuturesTests: XCTestCase { 27 | 28 | override func setUp() { 29 | super.setUp() 30 | // Put setup code here. This method is called before the invocation of each test method in the class. 31 | } 32 | 33 | override func tearDown() { 34 | // Put teardown code here. This method is called after the invocation of each test method in the class. 35 | super.tearDown() 36 | } 37 | } 38 | 39 | extension BrightFuturesTests { 40 | func testCompletedFuture() { 41 | let f = Future(value: 2) 42 | 43 | let completeExpectation = self.expectation(description: "immediate complete") 44 | 45 | f.onComplete { result in 46 | XCTAssert(result.isSuccess) 47 | completeExpectation.fulfill() 48 | } 49 | 50 | let successExpectation = self.expectation(description: "immediate success") 51 | 52 | f.onSuccess { value in 53 | XCTAssert(value == 2, "Computation should be returned") 54 | successExpectation.fulfill() 55 | } 56 | 57 | self.waitForExpectations(timeout: 2, handler: nil) 58 | } 59 | 60 | func testCompletedVoidFuture() { 61 | let f = Future(value: ()) 62 | XCTAssert(f.isCompleted, "void future should be completed") 63 | XCTAssert(f.isSuccess, "void future should be success") 64 | } 65 | 66 | func testFailedFuture() { 67 | let error = NSError(domain: "test", code: 0, userInfo: nil) 68 | let f = Future(error: error) 69 | 70 | let completeExpectation = self.expectation(description: "immediate complete") 71 | 72 | f.onComplete { result in 73 | switch result { 74 | case .success(_): 75 | XCTAssert(false) 76 | case .failure(let err): 77 | XCTAssertEqual(err, error) 78 | } 79 | completeExpectation.fulfill() 80 | } 81 | 82 | let failureExpectation = self.expectation(description: "immediate failure") 83 | 84 | f.onFailure { err in 85 | XCTAssert(err == error) 86 | failureExpectation.fulfill() 87 | } 88 | 89 | f.onSuccess { value in 90 | XCTFail("success should not be called") 91 | } 92 | 93 | self.waitForExpectations(timeout: 2, handler: nil) 94 | } 95 | 96 | func testCompleteAfterFuture() { 97 | let f = Future(value: 3, delay: 1.second) 98 | 99 | XCTAssertFalse(f.isCompleted) 100 | 101 | Thread.sleep(forTimeInterval: 0.2) 102 | 103 | XCTAssertFalse(f.isCompleted) 104 | 105 | Thread.sleep(forTimeInterval: 1.0) 106 | 107 | XCTAssert(f.isCompleted) 108 | 109 | if let val = f.value { 110 | XCTAssertEqual(val, 3); 111 | } 112 | } 113 | 114 | func testFailedAfterFuture() { 115 | let f = Future(error: .justAnError, delay: 1.second) 116 | 117 | XCTAssertFalse(f.isCompleted) 118 | 119 | Thread.sleep(forTimeInterval: 0.2) 120 | 121 | XCTAssertFalse(f.isCompleted) 122 | 123 | Thread.sleep(forTimeInterval: 1.0) 124 | 125 | XCTAssert(f.isCompleted) 126 | 127 | if let error = f.error { 128 | switch error { 129 | case .justAnError: 130 | XCTAssert(true) 131 | case .justAnotherError: 132 | XCTAssert(false) 133 | } 134 | } 135 | } 136 | 137 | // this is inherently impossible to test, but we'll give it a try 138 | func testNeverCompletingFuture() { 139 | let f = Future() 140 | XCTAssert(!f.isCompleted) 141 | XCTAssert(!f.isSuccess) 142 | XCTAssert(!f.isFailure) 143 | 144 | sleep(UInt32(Double(arc4random_uniform(100))/100.0)) 145 | 146 | XCTAssert(!f.isCompleted) 147 | } 148 | 149 | func testFutureWithOther() { 150 | let p = Promise() 151 | let f = Future(other: p.future) 152 | 153 | XCTAssert(!f.isCompleted) 154 | 155 | p.success(1) 156 | 157 | XCTAssertEqual(f.value, 1); 158 | } 159 | 160 | func testForceTypeSuccess() { 161 | let f: Future = Future(value: Foundation.TimeInterval(3.0)) 162 | let f1: Future = f.forceType() 163 | 164 | XCTAssertEqual(TimeInterval(3.0), f1.result!.value!, "Should be a time interval") 165 | } 166 | 167 | func testAsVoid() { 168 | let f = Future(value: 10) 169 | 170 | let e = self.expectation() 171 | f.asVoid().onComplete { v in 172 | e.fulfill() 173 | } 174 | 175 | self.waitForExpectations(timeout: 2, handler: nil) 176 | } 177 | 178 | func testForceTypeFailure() { 179 | class TestError: Error { 180 | var _domain: String { return "TestError" } 181 | var _code: Int { return 1 } 182 | } 183 | 184 | class SubError: TestError { 185 | override var _domain: String { return "" } 186 | override var _code: Int { return 2 } 187 | } 188 | 189 | let f: Future = Future(error: SubError()) 190 | let f1: Future = f.forceType() 191 | 192 | XCTAssertEqual(f1.result!.error!._code, 2, "Should be a SubError") 193 | } 194 | 195 | func testDefaultCallbackExecutionContextFromMain() { 196 | let f = Future(value: 1) 197 | let e = self.expectation() 198 | f.onSuccess { _ in 199 | XCTAssert(Thread.isMainThread, "the callback should run on main") 200 | e.fulfill() 201 | } 202 | 203 | self.waitForExpectations(timeout: 2, handler: nil) 204 | } 205 | 206 | func testDefaultCallbackExecutionContextFromBackground() { 207 | let f = Future(value: 1) 208 | let e = self.expectation() 209 | DispatchQueue.global().async { 210 | f.onSuccess { _ in 211 | XCTAssert(!Thread.isMainThread, "the callback should not be on the main thread") 212 | e.fulfill() 213 | } 214 | return 215 | } 216 | 217 | self.waitForExpectations(timeout: 2, handler: nil) 218 | } 219 | 220 | func testPromoteErrorNoSuchElement() { 221 | let f: Future> = Future(value: 3).filter { _ in false }.promoteError() 222 | 223 | let e = self.expectation() 224 | f.onFailure { err in 225 | XCTAssert(err == BrightFuturesError.noSuchElement) 226 | e.fulfill() 227 | } 228 | 229 | self.waitForExpectations(timeout: 2, handler: nil) 230 | } 231 | 232 | func testWrapCompletionHandlerValueError() { 233 | func testCall(_ val: Int, completionHandler: (Int?, TestError?) -> Void) { 234 | if val == 0 { 235 | completionHandler(nil, TestError.justAnError) 236 | } else { 237 | completionHandler(val, nil) 238 | } 239 | } 240 | 241 | let f = BrightFutures.materialize { (handler: (Int?, TestError?) -> Void) -> Void in 242 | testCall(2, completionHandler: handler) 243 | } 244 | XCTAssertEqual(f.value!, 2) 245 | 246 | let f2 = BrightFutures.materialize { (handler: (Int?, TestError?) -> Void) -> Void in 247 | testCall(0, completionHandler: handler) 248 | } 249 | XCTAssert(f2.error! == TestError.justAnError) 250 | } 251 | 252 | func testWrapCompletionHandlerValue() { 253 | func testCall(_ val: Int, completionHandler: (Int) -> Void) { 254 | completionHandler(val) 255 | } 256 | 257 | func testCall2(_ val: Int, completionHandler: (Int?) -> Void) { 258 | completionHandler(nil) 259 | } 260 | 261 | let f = BrightFutures.materialize { testCall(3, completionHandler: $0) } 262 | XCTAssertEqual(f.value!, 3) 263 | 264 | let f2 = BrightFutures.materialize { testCall2(4, completionHandler: $0) } 265 | XCTAssert(f2.value! == nil) 266 | } 267 | 268 | func testWrapCompletionHandlerError() { 269 | func testCall(_ val: Int, completionHandler: (TestError?) -> Void) { 270 | if val == 0 { 271 | completionHandler(nil) 272 | } else { 273 | completionHandler(TestError.justAnError) 274 | } 275 | } 276 | 277 | let f = BrightFutures.materialize { testCall(0, completionHandler: $0) } 278 | XCTAssert(f.error == nil) 279 | 280 | let f2 = BrightFutures.materialize { testCall(2, completionHandler: $0) } 281 | XCTAssert(f2.error! == TestError.justAnError) 282 | } 283 | } 284 | 285 | // MARK: Functional Composition 286 | /** 287 | * This extension contains all tests related to functional composition 288 | */ 289 | extension BrightFuturesTests { 290 | 291 | func testAndThen() { 292 | 293 | var answer = 10 294 | 295 | let e = self.expectation() 296 | 297 | let f = Future(value: 4) 298 | let f1 = f.andThen { result in 299 | if let val = result.value { 300 | answer *= val 301 | } 302 | } 303 | 304 | let f2 = f1.andThen { result in 305 | answer += 2 306 | } 307 | 308 | f.onSuccess { fval in 309 | f1.onSuccess { f1val in 310 | f2.onSuccess { f2val in 311 | 312 | XCTAssertEqual(fval, f1val, "future value should be passed transparently") 313 | XCTAssertEqual(f1val, f2val, "future value should be passed transparently") 314 | 315 | e.fulfill() 316 | } 317 | } 318 | } 319 | 320 | self.waitForExpectations(timeout: 20, handler: nil) 321 | 322 | XCTAssertEqual(42, answer, "andThens should be executed in order") 323 | } 324 | 325 | func testSimpleMap() { 326 | let e = self.expectation() 327 | 328 | func divideByFive(_ i: Int) -> Int { 329 | return i / 5 330 | } 331 | 332 | Future(value: fibonacci(10)).map(divideByFive).onSuccess { val in 333 | XCTAssertEqual(val, 11, "The 10th fibonacci number (55) divided by 5 is 11") 334 | e.fulfill() 335 | return 336 | } 337 | 338 | self.waitForExpectations(timeout: 2, handler: nil) 339 | } 340 | 341 | func testMapSuccess() { 342 | let e = self.expectation() 343 | 344 | DispatchQueue.global().asyncValue { 345 | fibonacci(10) 346 | }.map { value -> String in 347 | if value > 5 { 348 | return "large" 349 | } 350 | return "small" 351 | }.map { sizeString -> Bool in 352 | return sizeString == "large" 353 | }.onSuccess { numberIsLarge in 354 | XCTAssert(numberIsLarge) 355 | e.fulfill() 356 | } 357 | 358 | self.waitForExpectations(timeout: 2, handler: nil) 359 | } 360 | 361 | func testMapFailure() { 362 | 363 | let e = self.expectation() 364 | 365 | DispatchQueue.global().asyncResult { () -> Result in 366 | .failure(NSError(domain: "Tests", code: 123, userInfo: nil)) 367 | }.map { number in 368 | XCTAssert(false, "map should not be evaluated because of failure above") 369 | }.map { number in 370 | XCTAssert(false, "this map should also not be evaluated because of failure above") 371 | }.onFailure { error in 372 | XCTAssert(error.domain == "Tests") 373 | e.fulfill() 374 | } 375 | 376 | self.waitForExpectations(timeout: 2, handler: nil) 377 | } 378 | 379 | func testMapByKeyPath() { 380 | struct S { 381 | let a: Int 382 | } 383 | 384 | let e = self.expectation() 385 | 386 | DispatchQueue.global().asyncValue { 387 | S(a: 10) 388 | }.map(\.a).onSuccess { keyPathValue in 389 | XCTAssertEqual(keyPathValue, 10) 390 | e.fulfill() 391 | } 392 | 393 | self.waitForExpectations(timeout: 2, handler: nil) 394 | } 395 | 396 | func testRecover() { 397 | let e = self.expectation() 398 | Future(error: TestError.justAnError).recover { _ in 399 | return 3 400 | }.onSuccess { val in 401 | XCTAssertEqual(val, 3) 402 | e.fulfill() 403 | } 404 | 405 | let recov: () -> Int = { 406 | return 5 407 | } 408 | 409 | let e1 = self.expectation() 410 | (Future(error: TestError.justAnError) ?? recov()).onSuccess { value in 411 | XCTAssert(value == 5) 412 | e1.fulfill() 413 | } 414 | 415 | self.waitForExpectations(timeout: 2, handler: nil) 416 | } 417 | 418 | func testSkippedRecover() { 419 | let e = self.expectation() 420 | 421 | DispatchQueue.global().asyncValue { 422 | 3 423 | }.onSuccess { value in 424 | XCTAssert(value == 3) 425 | e.fulfill() 426 | } 427 | 428 | let e1 = self.expectation() 429 | 430 | 431 | let recov: () -> Int = { 432 | XCTFail("recover task should not be executed") 433 | return 5 434 | } 435 | 436 | (Future(value: 3) ?? recov()).onSuccess { value in 437 | XCTAssert(value == 3) 438 | e1.fulfill() 439 | } 440 | 441 | self.waitForExpectations(timeout: 2, handler: nil) 442 | } 443 | 444 | func testRecoverWith() { 445 | let e = self.expectation() 446 | 447 | DispatchQueue.global().asyncResult { 448 | .failure(NSError(domain: "NaN", code: 0, userInfo: nil)) 449 | }.recoverWith { _ in 450 | return DispatchQueue.global().asyncValue { 451 | fibonacci(5) 452 | } 453 | }.onSuccess { value in 454 | XCTAssert(value == 5) 455 | e.fulfill() 456 | } 457 | 458 | let e1 = self.expectation() 459 | 460 | let f: Future = Future(error: NSError(domain: "NaN", code: 0, userInfo: nil)) ?? Future(value: fibonacci(5)) 461 | 462 | f.onSuccess { 463 | XCTAssertEqual($0, 5) 464 | e1.fulfill() 465 | } 466 | 467 | self.waitForExpectations(timeout: 2, handler: nil) 468 | } 469 | 470 | func testMapError() { 471 | let e = self.expectation() 472 | 473 | Future(error: .justAnError).mapError { _ in 474 | return TestError.justAnotherError 475 | }.onFailure { error in 476 | XCTAssertEqual(error, TestError.justAnotherError) 477 | e.fulfill() 478 | } 479 | 480 | self.waitForExpectations(timeout: 2, handler: nil) 481 | } 482 | 483 | func testZip() { 484 | let f = Future(value: 1) 485 | let f1 = Future(value: 2) 486 | 487 | let e = self.expectation() 488 | 489 | f.zip(f1).onSuccess { (arg) in 490 | let (a, b) = arg 491 | XCTAssertEqual(a, 1) 492 | XCTAssertEqual(b, 2) 493 | e.fulfill() 494 | } 495 | 496 | self.waitForExpectations(timeout: 2, handler: nil) 497 | } 498 | 499 | func testZipThisFails() { 500 | let f: Future = DispatchQueue.global().asyncResult { () -> Result in 501 | sleep(1) 502 | return .failure(NSError(domain: "test", code: 2, userInfo: nil)) 503 | } 504 | 505 | let f1 = Future(value: 2) 506 | 507 | let e = self.expectation() 508 | 509 | f.zip(f1).onFailure { error in 510 | XCTAssert(error.domain == "test") 511 | XCTAssertEqual(error.code, 2) 512 | e.fulfill() 513 | } 514 | 515 | self.waitForExpectations(timeout: 2, handler: nil) 516 | } 517 | 518 | func testZipThatFails() { 519 | let f = DispatchQueue.global().asyncResult { () -> Result in 520 | sleep(1) 521 | return .failure(NSError(domain: "tester", code: 3, userInfo: nil)) 522 | } 523 | 524 | let f1 = Future(value: 2) 525 | 526 | let e = self.expectation() 527 | 528 | f1.zip(f).onFailure { error in 529 | XCTAssert(error.domain == "tester") 530 | XCTAssertEqual(error.code, 3) 531 | e.fulfill() 532 | } 533 | 534 | self.waitForExpectations(timeout: 2, handler: nil) 535 | } 536 | 537 | func testZipBothFail() { 538 | let f = DispatchQueue.global().asyncResult { () -> Result in 539 | sleep(1) 540 | return .failure(NSError(domain: "f-error", code: 3, userInfo: nil)) 541 | } 542 | 543 | let f1 = DispatchQueue.global().asyncResult { () -> Result in 544 | sleep(1) 545 | return .failure(NSError(domain: "f1-error", code: 4, userInfo: nil)) 546 | } 547 | 548 | let e = self.expectation() 549 | 550 | f.zip(f1).onFailure { error in 551 | XCTAssert(error.domain == "f-error") 552 | XCTAssertEqual(error.code, 3) 553 | e.fulfill() 554 | } 555 | 556 | self.waitForExpectations(timeout: 2, handler: nil) 557 | } 558 | 559 | func testFilterNoSuchElement() { 560 | let e = self.expectation() 561 | Future(value: 3).filter { $0 > 5}.onComplete { result in 562 | if let err = result.error { 563 | XCTAssert(err == BrightFuturesError.noSuchElement, "filter should yield no result") 564 | } 565 | 566 | e.fulfill() 567 | } 568 | self.waitForExpectations(timeout: 2, handler: nil) 569 | } 570 | 571 | func testFilterPasses() { 572 | let e = self.expectation() 573 | Future(value: "Thomas").filter { $0.hasPrefix("Th") }.onComplete { result in 574 | if let val = result.value { 575 | XCTAssert(val == "Thomas", "Filter should pass") 576 | } 577 | 578 | e.fulfill() 579 | } 580 | 581 | self.waitForExpectations(timeout: 2, handler: nil) 582 | } 583 | 584 | func testFilterFailedFuture() { 585 | let f = Future(error: TestError.justAnError) 586 | 587 | let e = self.expectation() 588 | f.filter { _ in false }.onFailure { error in 589 | XCTAssert(error == BrightFuturesError(external: TestError.justAnError)) 590 | e.fulfill() 591 | } 592 | 593 | self.waitForExpectations(timeout: 2, handler: nil) 594 | } 595 | 596 | func testForcedFuture() { 597 | var x = 10 598 | let f: Future = DispatchQueue.global().asyncValue { () -> Void in 599 | Thread.sleep(forTimeInterval: 0.5) 600 | x = 3 601 | } 602 | let _ = f.forced() 603 | XCTAssertEqual(x, 3) 604 | } 605 | 606 | func testForcedFutureWithTimeout() { 607 | let f: Future = DispatchQueue.global().asyncValue { 608 | Thread.sleep(forTimeInterval: 0.5) 609 | } 610 | 611 | XCTAssert(f.forced(100.milliseconds.fromNow) == nil) 612 | 613 | XCTAssert(f.forced(500.milliseconds.fromNow) != nil) 614 | } 615 | 616 | func testForcingCompletedFuture() { 617 | let f = Future(value: 1) 618 | XCTAssertEqual(f.forced().value!, 1) 619 | } 620 | 621 | func testDelay() { 622 | let t0 = CACurrentMediaTime() 623 | let f = Future(value: 1).delay(0.seconds); 624 | XCTAssertFalse(f.isCompleted) 625 | var isAsync = false 626 | 627 | let e = self.expectation() 628 | f.onComplete(immediateExecutionContext) { _ in 629 | XCTAssert(Thread.isMainThread) 630 | XCTAssert(isAsync) 631 | XCTAssert(CACurrentMediaTime() - t0 >= 0) 632 | }.delay(1.second).onComplete { _ in 633 | XCTAssert(Thread.isMainThread) 634 | XCTAssert(CACurrentMediaTime() - t0 >= 1) 635 | e.fulfill() 636 | } 637 | isAsync = true 638 | 639 | self.waitForExpectations(timeout: 2, handler: nil) 640 | } 641 | 642 | func testDelayOnGlobalQueue() { 643 | let e = self.expectation() 644 | DispatchQueue.global().async { 645 | Future(value: 1).delay(0.seconds).onComplete(immediateExecutionContext) { _ in 646 | XCTAssert(!Thread.isMainThread) 647 | e.fulfill() 648 | } 649 | } 650 | 651 | self.waitForExpectations(timeout: 2, handler: nil) 652 | } 653 | 654 | func testDelayChaining() { 655 | let e = self.expectation() 656 | let t0 = CACurrentMediaTime() 657 | let _ = Future(value: ()) 658 | .delay(1.second) 659 | .andThen { _ in XCTAssert(CACurrentMediaTime() - t0 >= 1) } 660 | .delay(1.second) 661 | .andThen { _ in 662 | XCTAssert(CACurrentMediaTime() - t0 >= 2) 663 | e.fulfill() 664 | } 665 | 666 | self.waitForExpectations(timeout: 3, handler: nil) 667 | } 668 | 669 | func testFlatMap() { 670 | let e = self.expectation() 671 | 672 | let finalString = "Greg" 673 | 674 | let flatMapped = Future(value: "Thomas").flatMap { _ in 675 | return Future(value: finalString) 676 | } 677 | 678 | flatMapped.onSuccess { s in 679 | XCTAssertEqual(s, finalString, "strings are not equal") 680 | e.fulfill() 681 | } 682 | 683 | self.waitForExpectations(timeout: 2, handler: nil) 684 | } 685 | 686 | func testFlatMapByPassingFunction() { 687 | let e = self.expectation() 688 | 689 | func toString(_ n: Int) -> Future { 690 | return Future(value: "\(n)") 691 | } 692 | 693 | let n = 1 694 | let flatMapped = Future(value: n).flatMap(toString) 695 | 696 | flatMapped.onSuccess { s in 697 | XCTAssertEqual(s, "\(n)", "strings are not equal") 698 | e.fulfill() 699 | } 700 | 701 | self.waitForExpectations(timeout: 2, handler: nil) 702 | } 703 | 704 | func testFlatMapResult() { 705 | let e = self.expectation() 706 | 707 | Future(value: 3).flatMap { _ in 708 | Result.success(22) 709 | }.onSuccess { val in 710 | XCTAssertEqual(val, 22) 711 | e.fulfill() 712 | } 713 | 714 | self.waitForExpectations(timeout: 2, handler: nil) 715 | } 716 | } 717 | 718 | // MARK: FutureUtils 719 | /** 720 | * This extension contains all tests related to FutureUtils 721 | */ 722 | extension BrightFuturesTests { 723 | func testUtilsTraverseSuccess() { 724 | let n = 10 725 | 726 | let f = (Array(1...n)).traverse { i in 727 | Future(value: fibonacci(i)) 728 | } 729 | 730 | let e = self.expectation() 731 | 732 | f.onSuccess { fibSeq in 733 | XCTAssertEqual(fibSeq.count, n) 734 | 735 | for i in 0 ..< fibSeq.count { 736 | XCTAssertEqual(fibSeq[i], fibonacci(i+1)) 737 | } 738 | e.fulfill() 739 | } 740 | 741 | self.waitForExpectations(timeout: 4, handler: nil) 742 | } 743 | 744 | func testUtilsTraverseEmpty() { 745 | let e = self.expectation() 746 | [Int]().traverse { Future(value: $0) }.onSuccess { res in 747 | XCTAssertEqual(res.count, 0); 748 | e.fulfill() 749 | } 750 | 751 | self.waitForExpectations(timeout: 2, handler: nil) 752 | } 753 | 754 | func testUtilsTraverseSingleError() { 755 | let e = self.expectation() 756 | 757 | let evenFuture: (Int) -> Future = { i in 758 | return DispatchQueue.global().asyncResult { 759 | if i % 2 == 0 { 760 | return .success(true) 761 | } else { 762 | return .failure(NSError(domain: "traverse-single-error", code: i, userInfo: nil)) 763 | } 764 | } 765 | } 766 | 767 | let f = [2,4,6,8,9,10].traverse(DispatchQueue.global().context, f: evenFuture) 768 | 769 | 770 | f.onFailure { err in 771 | XCTAssertEqual(err.code, 9) 772 | e.fulfill() 773 | } 774 | 775 | self.waitForExpectations(timeout: 2, handler: nil) 776 | } 777 | 778 | func testUtilsTraverseMultipleErrors() { 779 | let e = self.expectation() 780 | 781 | let evenFuture: (Int) -> Future = { i in 782 | return DispatchQueue.global().asyncResult { 783 | if i % 2 == 0 { 784 | return .success(true) 785 | } else { 786 | return .failure(NSError(domain: "traverse-single-error", code: i, userInfo: nil)) 787 | } 788 | } 789 | } 790 | 791 | [20,22,23,26,27,30].traverse(f: evenFuture).onFailure { err in 792 | XCTAssertEqual(err.code, 23) 793 | e.fulfill() 794 | } 795 | 796 | self.waitForExpectations(timeout: 2, handler: nil) 797 | } 798 | 799 | func testUtilsTraverseWithExecutionContext() { 800 | let e = self.expectation() 801 | 802 | Array(1...10).traverse(DispatchQueue.main.context) { _ -> Future in 803 | XCTAssert(Thread.isMainThread) 804 | return Future(value: 1) 805 | }.onComplete { _ in 806 | e.fulfill() 807 | } 808 | 809 | self.waitForExpectations(timeout: 2, handler: nil) 810 | } 811 | 812 | func testUtilsLargeSequence() { 813 | let promises = (1...500).map { _ in Promise() } 814 | let futures = promises.map { $0.future } 815 | 816 | let e = self.expectation() 817 | 818 | 819 | futures.sequence().onSuccess { nums in 820 | for (index, num) in nums.enumerated() { 821 | XCTAssertEqual(index, num) 822 | } 823 | 824 | e.fulfill() 825 | } 826 | 827 | for (i, promise) in promises.enumerated() { 828 | DispatchQueue.global().async { 829 | promise.success(i) 830 | } 831 | } 832 | 833 | self.waitForExpectations(timeout: 10, handler: nil) 834 | } 835 | 836 | func testUtilsFold() { 837 | // create a list of Futures containing the Fibonacci sequence 838 | let fibonacciList = (1...10).map { val in 839 | fibonacciFuture(val) 840 | } 841 | 842 | let e = self.expectation() 843 | 844 | fibonacciList.fold(0, f: { $0 + $1 }).onSuccess { val in 845 | XCTAssertEqual(val, 143) 846 | e.fulfill() 847 | } 848 | 849 | self.waitForExpectations(timeout: 2, handler: nil) 850 | } 851 | 852 | func testUtilsFoldWithError() { 853 | let error = NSError(domain: "fold-with-error", code: 0, userInfo: nil) 854 | 855 | // create a list of Futures containing the Fibonacci sequence and one error 856 | let fibonacciList = (1...10).map { val -> Future in 857 | if val == 3 { 858 | return Future(error: error) 859 | } else { 860 | return fibonacciFuture(val).promoteError() 861 | } 862 | } 863 | 864 | let e = self.expectation() 865 | 866 | fibonacciList.fold(0, f: { $0 + $1 }).onFailure { err in 867 | XCTAssertEqual(err, error) 868 | e.fulfill() 869 | } 870 | 871 | self.waitForExpectations(timeout: 2, handler: nil) 872 | } 873 | 874 | func testUtilsFoldWithExecutionContext() { 875 | let e = self.expectation() 876 | 877 | [Future(value: 1)].fold(DispatchQueue.main.context, zero: 10) { remainder, elem -> Int in 878 | XCTAssert(Thread.isMainThread) 879 | return remainder + elem 880 | }.onSuccess { val in 881 | XCTAssertEqual(val, 11) 882 | e.fulfill() 883 | } 884 | 885 | self.waitForExpectations(timeout: 2, handler: nil) 886 | } 887 | 888 | func testUtilsFoldWithEmptyList() { 889 | let z = "NaN" 890 | 891 | let e = self.expectation() 892 | 893 | [Future]().fold(z, f: { $0 + $1 }).onSuccess { val in 894 | XCTAssertEqual(val, z) 895 | e.fulfill() 896 | } 897 | 898 | self.waitForExpectations(timeout: 2, handler: nil) 899 | } 900 | 901 | func testUtilsFirstCompleted() { 902 | let futures: [Future] = [ 903 | Future(value: 3, delay: 500.milliseconds), 904 | Future(value: 13, delay: 300.milliseconds), 905 | Future(value: 23, delay: 400.milliseconds), 906 | Future(value: 4, delay: 300.milliseconds), 907 | Future(value: 9, delay: 100.milliseconds), 908 | Future(value: 83, delay: 400.milliseconds), 909 | ] 910 | 911 | let e = self.expectation() 912 | 913 | futures.firstCompleted().onSuccess { val in 914 | XCTAssertEqual(val, 9) 915 | e.fulfill() 916 | } 917 | 918 | self.waitForExpectations(timeout: 2, handler: nil) 919 | } 920 | 921 | func testUtilsFirstCompletedWithError() { 922 | let futures: [Future] = [ 923 | Future(value: 3, delay: 500.milliseconds), 924 | Future(error: .justAnotherError, delay: 300.milliseconds), 925 | Future(value: 23, delay: 400.milliseconds), 926 | Future(value: 4, delay: 300.milliseconds), 927 | Future(error: .justAnError, delay: 100.milliseconds), 928 | Future(value: 83, delay: 400.milliseconds), 929 | ] 930 | 931 | let e = self.expectation() 932 | 933 | futures.firstCompleted().onSuccess { _ in 934 | XCTAssert(false) 935 | e.fulfill() 936 | }.onFailure { error in 937 | switch error { 938 | case .justAnError: 939 | XCTAssert(true) 940 | case .justAnotherError: 941 | XCTAssert(false) 942 | } 943 | e.fulfill() 944 | } 945 | 946 | self.waitForExpectations(timeout: 2, handler: nil) 947 | } 948 | 949 | func testUtilsSequence() { 950 | let futures = (1...10).map { fibonacciFuture($0) } 951 | 952 | let e = self.expectation() 953 | 954 | futures.sequence().onSuccess { fibs in 955 | for (index, num) in fibs.enumerated() { 956 | XCTAssertEqual(fibonacci(index+1), num) 957 | } 958 | 959 | e.fulfill() 960 | } 961 | 962 | self.waitForExpectations(timeout: 2, handler: nil) 963 | } 964 | 965 | func testUtilsSequenceEmpty() { 966 | let e = self.expectation() 967 | 968 | [Future]().sequence().onSuccess { val in 969 | XCTAssertEqual(val.count, 0) 970 | 971 | e.fulfill() 972 | } 973 | 974 | self.waitForExpectations(timeout: 2, handler: nil) 975 | } 976 | 977 | func testUtilsFindSuccess() { 978 | let futures: [Future] = [ 979 | Future(value: 1), 980 | Future(value: 3, delay: 200.milliseconds), 981 | Future(value: 5), 982 | Future(value: 7), 983 | Future(value: 8, delay: 300.milliseconds), 984 | Future(value: 9) 985 | ]; 986 | 987 | let f = futures.find(DispatchQueue.global().context) { val in 988 | return val % 2 == 0 989 | } 990 | 991 | let e = self.expectation() 992 | 993 | f.onSuccess { val in 994 | XCTAssertEqual(val, 8, "First matching value is 8") 995 | e.fulfill() 996 | } 997 | 998 | self.waitForExpectations(timeout: 2, handler: nil) 999 | } 1000 | 1001 | func testUtilsFindNoSuchElement() { 1002 | let futures: [Future] = [ 1003 | Future(value: 1), 1004 | Future(value: 3, delay: 200.milliseconds), 1005 | Future(value: 5), 1006 | Future(value: 7), 1007 | Future(value: 9, delay: 400.milliseconds), 1008 | ]; 1009 | 1010 | let f = futures.find { val in 1011 | return val % 2 == 0 1012 | } 1013 | 1014 | let e = self.expectation() 1015 | 1016 | f.onFailure { err in 1017 | XCTAssert(err == BrightFuturesError.noSuchElement, "No matching elements") 1018 | e.fulfill() 1019 | } 1020 | 1021 | self.waitForExpectations(timeout: 2, handler: nil) 1022 | } 1023 | 1024 | func testUtilsFindWithError() { 1025 | let f = [Future(error: .justAnError)].find(immediateExecutionContext) { $0 } 1026 | XCTAssert(f.error! == BrightFuturesError.external(.justAnError)); 1027 | } 1028 | 1029 | func testPromoteError() { 1030 | let _: Future = Future().promoteError() 1031 | } 1032 | 1033 | func testPromoteBrightFuturesError() { 1034 | let _: Future> = Future>(error: .noSuchElement).promoteError() 1035 | let _: Future> = Future>(error: .invalidationTokenInvalidated).promoteError() 1036 | let _: Future> = Future>(error: .illegalState).promoteError() 1037 | } 1038 | 1039 | func testPromoteValue() { 1040 | let _: Future = Future().promoteValue() 1041 | } 1042 | 1043 | func testFlatten() { 1044 | let a: Async = Async(result: Async(result: 2)).flatten() 1045 | a.onComplete(immediateExecutionContext) { val in 1046 | XCTAssertEqual(val, 2) 1047 | } 1048 | } 1049 | 1050 | } 1051 | 1052 | /** 1053 | * This extension contains miscellaneous tests 1054 | */ 1055 | extension BrightFuturesTests { 1056 | // Creates a lot of futures and adds completion blocks concurrently, which should all fire 1057 | func testStress() { 1058 | self.measure { 1059 | let instances = 100; 1060 | var successfulFutures = [Future]() 1061 | var failingFutures = [Future]() 1062 | let contexts: [ExecutionContext] = [immediateExecutionContext, DispatchQueue.main.context, DispatchQueue.global().context] 1063 | 1064 | let randomContext: () -> ExecutionContext = { contexts[Int(arc4random_uniform(UInt32(contexts.count)))] } 1065 | let randomFuture: () -> Future = { 1066 | if arc4random() % 2 == 0 { 1067 | return successfulFutures[Int(arc4random_uniform(UInt32(successfulFutures.count)))] 1068 | } else { 1069 | return failingFutures[Int(arc4random_uniform(UInt32(failingFutures.count)))] 1070 | } 1071 | } 1072 | 1073 | var finalSum = 0; 1074 | 1075 | for _ in 1...instances { 1076 | var future: Future 1077 | if arc4random() % 2 == 0 { 1078 | let futureResult: Int = Int(arc4random_uniform(10)) 1079 | finalSum += futureResult 1080 | future = self.succeedingFuture(futureResult) 1081 | successfulFutures.append(future) 1082 | } else { 1083 | future = self.failingFuture() 1084 | failingFutures.append(future) 1085 | } 1086 | 1087 | let context = randomContext() 1088 | let e = self.expectation(description: "future completes in context \(String(describing: context))") 1089 | 1090 | future.onComplete(context) { res in 1091 | e.fulfill() 1092 | } 1093 | 1094 | 1095 | } 1096 | 1097 | for _ in 1...instances*10 { 1098 | let f = randomFuture() 1099 | 1100 | let context = randomContext() 1101 | let e = self.expectation(description: "future completes in context \(String(describing: context))") 1102 | 1103 | DispatchQueue.global().async { 1104 | usleep(arc4random_uniform(100)) 1105 | 1106 | f.onComplete(context) { res in 1107 | e.fulfill() 1108 | } 1109 | } 1110 | } 1111 | 1112 | self.waitForExpectations(timeout: 10, handler: nil) 1113 | } 1114 | } 1115 | 1116 | func testSerialCallbacks() { 1117 | let p = Promise() 1118 | 1119 | var executingCallbacks = 0 1120 | for _ in 0..<10 { 1121 | let e = self.expectation() 1122 | p.future.onComplete(DispatchQueue.global().context) { _ in 1123 | XCTAssert(executingCallbacks == 0, "This should be the only executing callback") 1124 | 1125 | executingCallbacks += 1 1126 | 1127 | // sleep a bit to increase the chances of other callback blocks executing 1128 | Thread.sleep(forTimeInterval: 0.06) 1129 | 1130 | executingCallbacks -= 1 1131 | 1132 | e.fulfill() 1133 | } 1134 | 1135 | let e1 = self.expectation() 1136 | p.future.onComplete(DispatchQueue.main.context) { _ in 1137 | XCTAssert(executingCallbacks == 0, "This should be the only executing callback") 1138 | 1139 | executingCallbacks += 1 1140 | 1141 | // sleep a bit to increase the chances of other callback blocks executing 1142 | Thread.sleep(forTimeInterval: 0.06) 1143 | 1144 | executingCallbacks -= 1 1145 | 1146 | e1.fulfill() 1147 | } 1148 | } 1149 | 1150 | p.success(()) 1151 | 1152 | self.waitForExpectations(timeout: 5, handler: nil) 1153 | } 1154 | 1155 | // Test for https://github.com/Thomvis/BrightFutures/issues/18 1156 | func testCompletionBlockOnMainQueue() { 1157 | let key = DispatchSpecificKey() 1158 | let value = "value" 1159 | 1160 | 1161 | DispatchQueue.main.setSpecific(key: key, value: value) 1162 | XCTAssertEqual(DispatchQueue.getSpecific(key: key), value, "value should have been set on the main (i.e. current) queue") 1163 | 1164 | let e = self.expectation() 1165 | Future(value: 1).onSuccess(DispatchQueue.main.context) { val in 1166 | XCTAssertEqual(DispatchQueue.getSpecific(key: key), value, "we should now too be on the main queue") 1167 | e.fulfill() 1168 | } 1169 | 1170 | self.waitForExpectations(timeout: 2, handler: nil) 1171 | } 1172 | 1173 | func testRelease() { 1174 | weak var f: Future? = nil 1175 | 1176 | var f1: Future? = Future().map { $0 }.onSuccess { _ in }.onComplete { _ in } 1177 | 1178 | f = f1 1179 | XCTAssertNotNil(f1); 1180 | XCTAssertNotNil(f); 1181 | f1 = nil 1182 | XCTAssertNil(f1) 1183 | XCTAssertNil(f) 1184 | } 1185 | 1186 | func testDescription() { 1187 | let a = Async(result: 1) 1188 | XCTAssertEqual(a.description, "Async(Optional(1))") 1189 | XCTAssertEqual(a.debugDescription, a.description) 1190 | } 1191 | 1192 | func testDynamicMemberLookup() { 1193 | struct Person { 1194 | let name: String 1195 | } 1196 | 1197 | let f = Future(value: Person(name: "Thomas")) 1198 | let f1 = f.name 1199 | XCTAssertEqual(f1.value!, "Thomas") 1200 | } 1201 | 1202 | } 1203 | 1204 | /** 1205 | * This extension contains utility methods used in the tests above 1206 | */ 1207 | extension XCTestCase { 1208 | func expectation() -> XCTestExpectation { 1209 | return self.expectation(description: "no description") 1210 | } 1211 | 1212 | func failingFuture() -> Future { 1213 | return DispatchQueue.global().asyncResult { 1214 | usleep(arc4random_uniform(100)) 1215 | return .failure(NSError(domain: "failedFuture", code: 0, userInfo: nil)) 1216 | } 1217 | } 1218 | 1219 | func succeedingFuture(_ val: U) -> Future { 1220 | return DispatchQueue.global().asyncResult { 1221 | usleep(arc4random_uniform(100)) 1222 | return .success(val) 1223 | } 1224 | } 1225 | } 1226 | 1227 | func fibonacci(_ n: Int) -> Int { 1228 | switch n { 1229 | case 0...1: 1230 | return n 1231 | default: 1232 | return fibonacci(n - 1) + fibonacci(n - 2) 1233 | } 1234 | } 1235 | 1236 | func fibonacciFuture(_ n: Int) -> Future { 1237 | return Future(value: fibonacci(n)) 1238 | } 1239 | 1240 | func getMutablePointer (_ object: AnyObject) -> UnsafeMutableRawPointer { 1241 | return UnsafeMutableRawPointer(bitPattern: Int(UInt(bitPattern: ObjectIdentifier(object))))! 1242 | } 1243 | -------------------------------------------------------------------------------- /Tests/BrightFuturesTests/ErrorsTests.swift: -------------------------------------------------------------------------------- 1 | // The MIT License (MIT) 2 | // 3 | // Copyright (c) 2014 Thomas Visser 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 all 13 | // 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 THE 21 | // SOFTWARE. 22 | 23 | import XCTest 24 | import BrightFutures 25 | 26 | enum TestError: Error { 27 | case justAnError 28 | case justAnotherError 29 | } 30 | 31 | extension TestError: Equatable {} 32 | 33 | class ErrorsTests: XCTestCase { 34 | 35 | func testExternalError() { 36 | let externalError = TestError.justAnError 37 | let error = BrightFuturesError(external: externalError) 38 | 39 | switch error { 40 | case .external(let err): 41 | XCTAssertEqual(err, TestError.justAnError, "Should be same error") 42 | default: 43 | XCTFail("Should match with the external case") 44 | } 45 | } 46 | 47 | } 48 | -------------------------------------------------------------------------------- /Tests/BrightFuturesTests/ExecutionContextTests.swift: -------------------------------------------------------------------------------- 1 | // The MIT License (MIT) 2 | // 3 | // Copyright (c) 2014 Thomas Visser 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 all 13 | // 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 THE 21 | // SOFTWARE. 22 | 23 | import XCTest 24 | import BrightFutures 25 | 26 | class Counter { 27 | var i: Int = 0 28 | } 29 | 30 | class ExecutionContextTests: XCTestCase { 31 | 32 | func testImmediateOnMainThreadContextOnMainThread() { 33 | let counter = Counter() 34 | 35 | counter.i = 1 36 | 37 | immediateOnMainExecutionContext { 38 | XCTAssert(Thread.isMainThread) 39 | counter.i = 2 40 | } 41 | 42 | XCTAssertEqual(counter.i, 2) 43 | } 44 | 45 | func testImmediateOnMainThreadContextOnBackgroundThread() { 46 | let e = self.expectation() 47 | DispatchQueue.global().async { 48 | immediateOnMainExecutionContext { 49 | XCTAssert(Thread.isMainThread) 50 | e.fulfill() 51 | } 52 | } 53 | 54 | self.waitForExpectations(timeout: 2, handler: nil) 55 | } 56 | 57 | func testDispatchQueueToContext() { 58 | let key = DispatchSpecificKey() 59 | let value1 = "abc" 60 | 61 | let queue1 = DispatchQueue(label: "test1", qos: DispatchQoS.default, attributes: [], autoreleaseFrequency: .inherit, target: nil) 62 | queue1.setSpecific(key: key, value: value1) 63 | 64 | let e1 = self.expectation() 65 | queue1.context { 66 | XCTAssertEqual(DispatchQueue.getSpecific(key: key), value1) 67 | e1.fulfill() 68 | } 69 | 70 | let value2 = "def" 71 | 72 | let queue2 = DispatchQueue(label: "test2", qos: DispatchQoS.default, attributes: .concurrent, autoreleaseFrequency: .inherit, target: nil) 73 | queue2.setSpecific(key: key, value: value2) 74 | 75 | let e2 = self.expectation() 76 | queue2.context { 77 | XCTAssertEqual(DispatchQueue.getSpecific(key: key), value2) 78 | e2.fulfill() 79 | } 80 | 81 | self.waitForExpectations(timeout: 2, handler: nil) 82 | } 83 | 84 | } 85 | -------------------------------------------------------------------------------- /Tests/BrightFuturesTests/FutureDebugTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FutureDebugTests.swift 3 | // BrightFutures 4 | // 5 | // Created by Oleksii on 23/09/2016. 6 | // Copyright © 2016 Thomas Visser. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | import BrightFutures 11 | 12 | class TestLogger: LoggerType { 13 | var lastLoggedMessage: String? 14 | 15 | func log(message: String) { 16 | lastLoggedMessage = message 17 | } 18 | } 19 | 20 | class FutureDebugTests: XCTestCase { 21 | let testIdentifier = "testFutureIdentifier" 22 | let error = NSError(domain: "test", code: 0, userInfo: nil) 23 | let file = #file 24 | let fileName = (#file as NSString).lastPathComponent 25 | let line: UInt = #line 26 | let function = #function 27 | 28 | func testDebugFuture() { 29 | let logger = TestLogger() 30 | let f = Future(value: ()).debug(logger: logger, file: file, line: line, function: function) 31 | let expectedMessage = "\(fileName) at line \(line), func: \(function) - future completed" 32 | let debugExpectation = self.expectation(description: "debugLogged") 33 | 34 | f.onSuccess { _ in 35 | XCTAssertEqual(logger.lastLoggedMessage, expectedMessage) 36 | debugExpectation.fulfill() 37 | } 38 | 39 | self.waitForExpectations(timeout: 2, handler: nil) 40 | } 41 | 42 | func testDebugFutureWithIdentifier() { 43 | let logger = TestLogger() 44 | 45 | let f = Future(value: ()).debug(testIdentifier, logger: logger) 46 | let debugExpectation = self.expectation(description: "debugLogged") 47 | 48 | f.onSuccess { _ in 49 | XCTAssertEqual(logger.lastLoggedMessage, "Future \(self.testIdentifier) completed") 50 | debugExpectation.fulfill() 51 | } 52 | 53 | self.waitForExpectations(timeout: 2, handler: nil) 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /Tests/BrightFuturesTests/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 | -------------------------------------------------------------------------------- /Tests/BrightFuturesTests/InvalidationTokenTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // InvalidationTokenTests.swift 3 | // BrightFutures 4 | // 5 | // Created by Thomas Visser on 19/01/15. 6 | // Copyright (c) 2015 Thomas Visser. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | import BrightFutures 11 | 12 | class InvalidationTokenTests: XCTestCase { 13 | 14 | func testInvalidationTokenInit() { 15 | let token = InvalidationToken() 16 | XCTAssert(!token.isInvalid, "a token is valid by default") 17 | } 18 | 19 | func testInvalidateToken() { 20 | let token = InvalidationToken() 21 | token.invalidate() 22 | XCTAssert(token.isInvalid, "a token should become invalid after invalidating") 23 | } 24 | 25 | func testInvalidationTokenFuture() { 26 | let token = InvalidationToken() 27 | XCTAssertNotNil(token.future, "token should have a future") 28 | XCTAssert(!token.future.isCompleted, "token should have a future and not be complete") 29 | token.invalidate() 30 | XCTAssert(token.future.result?.error != nil, "future should have an error") 31 | if let error = token.future.result?.error { 32 | XCTAssert(error == BrightFuturesError.invalidationTokenInvalidated) 33 | } 34 | } 35 | 36 | func testCompletionAfterInvalidation() { 37 | let token = InvalidationToken() 38 | 39 | let p = Promise() 40 | 41 | p.future.onSuccess(token.validContext) { val in 42 | XCTAssert(false, "onSuccess should not get called") 43 | }.onFailure(token.validContext) { error in 44 | XCTAssert(false, "onSuccess should not get called") 45 | } 46 | 47 | let e = self.expectation() 48 | DispatchQueue.global().async { 49 | token.invalidate() 50 | p.success(2) 51 | Thread.sleep(forTimeInterval: 0.2); // make sure onSuccess is not called 52 | e.fulfill() 53 | } 54 | 55 | self.waitForExpectations(timeout: 2, handler: nil) 56 | } 57 | 58 | func testNonInvalidatedSucceededFutureOnSuccess() { 59 | let token = InvalidationToken() 60 | 61 | let e = self.expectation() 62 | Future(value: 3).onSuccess(token.validContext) { val in 63 | XCTAssertEqual(val, 3) 64 | e.fulfill() 65 | } 66 | 67 | self.waitForExpectations(timeout: 2, handler: nil) 68 | } 69 | 70 | func testNonInvalidatedSucceededFutureOnComplete() { 71 | let token = InvalidationToken() 72 | 73 | let e = self.expectation() 74 | Future(value: 3).onComplete(token.validContext) { res in 75 | XCTAssertEqual(res.value!, 3) 76 | e.fulfill() 77 | } 78 | 79 | self.waitForExpectations(timeout: 2, handler: nil) 80 | } 81 | 82 | func testNonInvalidatedFailedFutureOnFailure() { 83 | let token = InvalidationToken() 84 | 85 | let e = self.expectation() 86 | Future(error: TestError.justAnError).onFailure(token.validContext) { err in 87 | XCTAssertEqual(err, TestError.justAnError) 88 | e.fulfill() 89 | } 90 | 91 | self.waitForExpectations(timeout: 2, handler: nil) 92 | } 93 | 94 | func testStress() { 95 | class Counter { 96 | var i = 0 97 | } 98 | 99 | let q = DispatchQueue(label: "stress queue") 100 | 101 | var token: InvalidationToken! 102 | let counter = Counter() 103 | for _ in 1...100 { 104 | token = InvalidationToken() 105 | let currentI = counter.i 106 | let e = self.expectation() 107 | DispatchQueue.global().asyncValue { () -> Bool in 108 | let sleep: Foundation.TimeInterval = TimeInterval(arc4random() % 100) / 100000.0 109 | Thread.sleep(forTimeInterval: sleep) 110 | return true 111 | }.onSuccess(token.validContext(q.context)) { _ in 112 | XCTAssert(!token.isInvalid) 113 | XCTAssertEqual(currentI, counter.i, "onSuccess should only get called if the counter did not increment") 114 | }.onComplete(DispatchQueue.global().context) { _ in 115 | Thread.sleep(forTimeInterval: 0.0001); 116 | e.fulfill() 117 | } 118 | 119 | Thread.sleep(forTimeInterval: 0.0005) 120 | 121 | q.sync { 122 | token.invalidate() 123 | counter.i += 1 124 | } 125 | } 126 | 127 | self.waitForExpectations(timeout: 5, handler: nil) 128 | } 129 | 130 | } 131 | -------------------------------------------------------------------------------- /Tests/BrightFuturesTests/NSOperationQueueTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NSOperationQueueTests.swift 3 | // BrightFutures 4 | // 5 | // Created by Thomas Visser on 19/09/15. 6 | // Copyright © 2015 Thomas Visser. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | import BrightFutures 11 | 12 | class NSOperationQueueTests: XCTestCase { 13 | 14 | func testMaxConcurrentOperationCount() { 15 | let queue = OperationQueue() 16 | queue.maxConcurrentOperationCount = 12 17 | var i = 0 18 | 19 | let sem = DispatchSemaphore(value: 1) 20 | 21 | (0...100).forEach { n in 22 | let e = self.expectation() 23 | queue.context { 24 | sem.context { 25 | i += 1 26 | } 27 | XCTAssert(i <= queue.maxConcurrentOperationCount) 28 | 29 | sem.context { 30 | i -= 1 31 | } 32 | XCTAssert(i >= 0) 33 | 34 | e.fulfill() 35 | } 36 | } 37 | 38 | self.waitForExpectations(timeout: 5, handler: nil) 39 | XCTAssertEqual(i, 0) 40 | } 41 | 42 | } 43 | -------------------------------------------------------------------------------- /Tests/BrightFuturesTests/Number+BrightFutures.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Number+BrightFutures.swift 3 | // BrightFutures 4 | // 5 | // Created by Thomas Visser on 13/08/16. 6 | // Copyright © 2016 Thomas Visser. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public extension Int { 12 | 13 | var seconds: DispatchTimeInterval { 14 | return DispatchTimeInterval.seconds(self) 15 | } 16 | 17 | var second: DispatchTimeInterval { 18 | return seconds 19 | } 20 | 21 | var milliseconds: DispatchTimeInterval { 22 | return DispatchTimeInterval.milliseconds(self) 23 | } 24 | 25 | var millisecond: DispatchTimeInterval { 26 | return milliseconds 27 | } 28 | 29 | } 30 | 31 | public extension DispatchTimeInterval { 32 | var fromNow: DispatchTime { 33 | return DispatchTime.now() + self 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Tests/BrightFuturesTests/PromiseTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PromiseTests.swift 3 | // BrightFutures 4 | // 5 | // Created by Thomas Visser on 16/10/15. 6 | // Copyright © 2015 Thomas Visser. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | import BrightFutures 11 | 12 | class PromiseTests: XCTestCase { 13 | 14 | func testSuccessPromise() { 15 | let p = Promise() 16 | 17 | DispatchQueue.global().async { 18 | p.success(fibonacci(10)) 19 | } 20 | 21 | let e = self.expectation(description: "complete expectation") 22 | 23 | p.future.onComplete { result in 24 | switch result { 25 | case .success(let val): 26 | XCTAssert(Int(55) == val) 27 | case .failure(_): 28 | XCTAssert(false) 29 | } 30 | 31 | e.fulfill() 32 | } 33 | 34 | self.waitForExpectations(timeout: 2, handler: nil) 35 | } 36 | 37 | func testFailurePromise() { 38 | let p = Promise() 39 | 40 | DispatchQueue.global().async { 41 | p.tryFailure(TestError.justAnError) 42 | } 43 | 44 | let e = self.expectation(description: "complete expectation") 45 | 46 | p.future.onComplete { result in 47 | switch result { 48 | case .success(_): 49 | XCTFail("should not be success") 50 | case .failure(let err): 51 | XCTAssertEqual(err, TestError.justAnError) 52 | } 53 | 54 | e.fulfill() 55 | } 56 | 57 | self.waitForExpectations(timeout: 2, handler: nil) 58 | } 59 | 60 | func testCompletePromise() { 61 | let p = Promise() 62 | p.complete(.success(2)) 63 | 64 | XCTAssertEqual(p.future.value, 2) 65 | } 66 | 67 | func testPromiseCompleteWithSuccess() { 68 | let p = Promise() 69 | p.tryComplete(.success(2)) 70 | 71 | XCTAssert(p.future.isSuccess) 72 | XCTAssert(p.future.forced() == Result(value:2)) 73 | } 74 | 75 | func testPromiseCompleteWithFailure() { 76 | let p = Promise() 77 | p.tryComplete(.failure(.justAnError)) 78 | 79 | XCTAssert(p.future.isFailure) 80 | XCTAssert(p.future.forced() == Result(error:TestError.justAnError)) 81 | } 82 | 83 | func testPromiseTrySuccessTwice() { 84 | let p = Promise() 85 | XCTAssert(p.trySuccess(1)) 86 | XCTAssertFalse(p.trySuccess(2)) 87 | XCTAssertEqual(p.future.forced().value!, 1) 88 | } 89 | 90 | func testPromiseTryFailureTwice() { 91 | let p = Promise() 92 | XCTAssert(p.tryFailure(TestError.justAnError)) 93 | XCTAssertFalse(p.tryFailure(TestError.justAnotherError)) 94 | XCTAssertEqual(p.future.forced().error!, TestError.justAnError) 95 | } 96 | 97 | func testPromiseCompleteWithSucceedingFuture() { 98 | let p = Promise() 99 | let q = Promise() 100 | 101 | p.completeWith(q.future) 102 | 103 | XCTAssert(!p.future.isCompleted) 104 | q.success(1) 105 | XCTAssertEqual(p.future.value, 1) 106 | } 107 | 108 | func testPromiseCompleteWithFailingFuture() { 109 | let p = Promise() 110 | let q = Promise() 111 | 112 | p.completeWith(q.future) 113 | 114 | XCTAssert(!p.future.isCompleted) 115 | q.failure(.justAnError) 116 | XCTAssertEqual(p.future.error, .justAnError) 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /Tests/BrightFuturesTests/QueueTests.swift: -------------------------------------------------------------------------------- 1 | // The MIT License (MIT) 2 | // 3 | // Copyright (c) 2014 Thomas Visser 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 all 13 | // 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 THE 21 | // SOFTWARE. 22 | 23 | import XCTest 24 | import BrightFutures 25 | 26 | class QueueTests: XCTestCase { 27 | 28 | func testMain() { 29 | let e = self.expectation(description: "") 30 | DispatchQueue.global().async { 31 | DispatchQueue.main.sync { 32 | XCTAssert(Thread.isMainThread, "executing on the main queue should happen on the main thread") 33 | } 34 | e.fulfill() 35 | } 36 | 37 | self.waitForExpectations(timeout: 2, handler: nil) 38 | } 39 | 40 | func testSync() { 41 | var i = 1 42 | DispatchQueue.global().sync { 43 | i += 1 44 | } 45 | XCTAssert(i == 2, "sync should execute the block synchronously") 46 | } 47 | 48 | func testSyncWithResult() { 49 | let input = "42" 50 | let output: String = DispatchQueue.global().sync { 51 | input 52 | } 53 | 54 | XCTAssertEqual(input, output, "sync should return the return value of the block") 55 | } 56 | 57 | func testSyncThrowsNone() { 58 | let t: () throws -> Void = { } 59 | do { 60 | try DispatchQueue.global().sync(execute: t) 61 | XCTAssert(true) 62 | } catch _ { 63 | XCTFail() 64 | } 65 | } 66 | 67 | func testSyncThrowsError() { 68 | let t: () throws -> Void = { throw TestError.justAnError } 69 | do { 70 | try DispatchQueue.global().sync(execute: t) 71 | XCTFail() 72 | } catch TestError.justAnError { 73 | XCTAssert(true) 74 | } catch _ { 75 | XCTFail() 76 | } 77 | } 78 | 79 | func testAsync() { 80 | var res = 2 81 | let e = self.expectation(description: "") 82 | DispatchQueue.global().async { 83 | Thread.sleep(forTimeInterval: 1.0) 84 | res *= 2 85 | e.fulfill() 86 | } 87 | res += 2 88 | self.waitForExpectations(timeout: 2, handler: nil) 89 | XCTAssertEqual(res, 8, "async should not execute immediately") 90 | } 91 | 92 | func testAsyncFuture() { 93 | let f = DispatchQueue.global().asyncValue { () -> String in 94 | Thread.sleep(forTimeInterval: 1.0) 95 | return "fibonacci" 96 | } 97 | 98 | let e = self.expectation(description: "") 99 | f.onSuccess { val in 100 | XCTAssertEqual(val, "fibonacci", "the future should succeed with the value from the async block") 101 | e.fulfill() 102 | } 103 | 104 | self.waitForExpectations(timeout: 2, handler: nil) 105 | } 106 | 107 | func testAfter() { 108 | var res = 2 109 | let e = self.expectation(description: "") 110 | DispatchQueue.global().asyncAfter(deadline: 1.second.fromNow) { 111 | res *= 2 112 | e.fulfill() 113 | } 114 | res += 2 115 | self.waitForExpectations(timeout: 2, handler: nil) 116 | XCTAssertEqual(res, 8, "delay should not execute immediately") 117 | } 118 | 119 | func testAfterFuture() { 120 | // unfortunately, the compiler is not able to figure out that we want the 121 | // future-returning async method 122 | let f: Future = DispatchQueue.global().asyncValueAfter(1.second.fromNow) { 123 | return "fibonacci" 124 | } 125 | 126 | let e = self.expectation(description: "") 127 | f.onSuccess { val in 128 | XCTAssertEqual(val, "fibonacci", "the future should succeed with the value from the async block") 129 | e.fulfill() 130 | } 131 | 132 | self.waitForExpectations(timeout: 2, handler: nil) 133 | } 134 | 135 | } 136 | -------------------------------------------------------------------------------- /Tests/BrightFuturesTests/ResultTests.swift: -------------------------------------------------------------------------------- 1 | // The MIT License (MIT) 2 | // 3 | // Copyright (c) 2014 Thomas Visser 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 all 13 | // 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 THE 21 | // SOFTWARE. 22 | 23 | import XCTest 24 | import BrightFutures 25 | 26 | extension Result { 27 | var isSuccess: Bool { 28 | return self.analysis(ifSuccess: { _ in return true }, ifFailure: { _ in return false }) 29 | } 30 | var isFailure: Bool { 31 | return !isSuccess 32 | } 33 | } 34 | 35 | class ResultTests: XCTestCase { 36 | 37 | override func setUp() { 38 | super.setUp() 39 | // Put setup code here. This method is called before the invocation of each test method in the class. 40 | } 41 | 42 | override func tearDown() { 43 | // Put teardown code here. This method is called after the invocation of each test method in the class. 44 | super.tearDown() 45 | } 46 | 47 | func testSuccess() { 48 | let result = Result(value: 3) 49 | XCTAssert(result.isSuccess) 50 | XCTAssertFalse(result.isFailure) 51 | XCTAssertEqual(result.value!, 3) 52 | XCTAssertNil(result.error) 53 | 54 | let result1 = Result(value: 4) 55 | XCTAssert(result1.isSuccess) 56 | XCTAssertFalse(result1.isFailure) 57 | XCTAssertEqual(result1.value!, 4) 58 | XCTAssertNil(result1.error) 59 | } 60 | 61 | func testFailure() { 62 | let error = NSError(domain: "TestDomain", code: 2, userInfo: nil) 63 | let result = Result(error: error) 64 | XCTAssert(result.isFailure) 65 | XCTAssertFalse(result.isSuccess) 66 | XCTAssertEqual(result.error!, error) 67 | XCTAssertNil(result.value) 68 | } 69 | 70 | func testMapSuccess() { 71 | let r = Result(value: 2).map { i -> Bool in 72 | XCTAssertEqual(i, 2) 73 | return i % 2 == 0 74 | } 75 | 76 | XCTAssertTrue(r.isSuccess) 77 | XCTAssertEqual(r.value!, true) 78 | } 79 | 80 | func testMapFailure() { 81 | let r = Result(error: NSError(domain: "error", code: 1, userInfo: nil)).map { i -> Int in 82 | XCTAssert(false, "map should not get called if the result failed") 83 | return i * 2 84 | } 85 | 86 | XCTAssert(r.isFailure) 87 | XCTAssertEqual(r.error!.domain, "error") 88 | } 89 | 90 | func testFlatMapResultSuccess() { 91 | let r = divide(20, 5).flatMap { 92 | divide($0, 2) 93 | } 94 | 95 | XCTAssertEqual(r.value!, 2) 96 | } 97 | 98 | func testFlatMapResultFailure() { 99 | let r = divide(20, 0).flatMap { i -> Result in 100 | XCTAssert(false, "flatMap should not get called if the result failed") 101 | return divide(i, 2) 102 | } 103 | 104 | XCTAssert(r.isFailure) 105 | XCTAssertEqual(r.error!, MathError.divisionByZero) 106 | } 107 | 108 | func testFlatMapFutureSuccess() { 109 | let f = divide(100, 10).flatMap { i -> Future in 110 | return DispatchQueue.global().asyncValue { 111 | fibonacci(i) 112 | }.promoteError() 113 | } 114 | 115 | let e = self.expectation() 116 | 117 | f.onSuccess { i in 118 | XCTAssertEqual(i, 55) 119 | e.fulfill() 120 | } 121 | 122 | self.waitForExpectations(timeout: 2, handler: nil) 123 | } 124 | 125 | func testFlatMapFutureFailure() { 126 | let f = divide(100, 0).flatMap { i -> Future in 127 | XCTAssert(false, "flatMap should not get called if the result failed") 128 | return DispatchQueue.global().asyncValue { 129 | fibonacci(i) 130 | }.promoteError() 131 | } 132 | 133 | let e = self.expectation() 134 | 135 | f.onFailure { err in 136 | XCTAssertEqual(err, MathError.divisionByZero) 137 | e.fulfill() 138 | } 139 | 140 | self.waitForExpectations(timeout: 2, handler: nil) 141 | } 142 | 143 | func testSequenceSuccess() { 144 | let results: [Result] = (1...10).map { i in 145 | return divide(123, i) 146 | } 147 | 148 | let result: Result<[Int], MathError> = results.sequence() 149 | 150 | let outcome = [123, 61, 41, 30, 24, 20, 17, 15, 13, 12] 151 | XCTAssertEqual(result.value!, outcome) 152 | } 153 | 154 | func testSequenceFailure() { 155 | let results: [Result] = (-10...10).map { i in 156 | return divide(123, i) 157 | } 158 | 159 | let r = results.sequence() 160 | XCTAssert(r.isFailure) 161 | XCTAssertEqual(r.error!, MathError.divisionByZero) 162 | } 163 | 164 | func testFlattenInnerSuccess() { 165 | let fr = Result, Never>.success(.success(3)).flatten() 166 | XCTAssert(fr.isSuccess) 167 | XCTAssertEqual(fr.value, 3) 168 | } 169 | 170 | func testFlattenOuterFailure() { 171 | let fr = Result, TestError>.failure(.justAnError).flatten() 172 | XCTAssert(fr.isFailure) 173 | XCTAssertEqual(fr.error, .justAnError) 174 | } 175 | 176 | func testFlattenInnerFailure() { 177 | let fr = Result, TestError>.success(.failure(.justAnotherError)).flatten() 178 | XCTAssert(fr.isFailure) 179 | XCTAssertEqual(fr.error, .justAnotherError) 180 | } 181 | 182 | func testFlattenFutureInResultSuccess() { 183 | let f = Result, Never>.success(Future(value: 1)).flatten() 184 | XCTAssert(f.isSuccess) 185 | XCTAssertEqual(f.value, 1) 186 | } 187 | 188 | func testFlattenFutureInResultFailed() { 189 | let f = Result, TestError>.failure(.justAnError).flatten() 190 | XCTAssert(f.isFailure) 191 | XCTAssertEqual(f.error, .justAnError) 192 | } 193 | 194 | func testFlattenFailedFutureInSucceededResult() { 195 | let f = Result, TestError>.success(Future(error: .justAnotherError)).flatten() 196 | XCTAssert(f.isFailure) 197 | XCTAssertEqual(f.error, .justAnotherError) 198 | } 199 | } 200 | 201 | enum MathError: Error { 202 | case divisionByZero 203 | } 204 | 205 | func divide(_ a: Int, _ b: Int) -> Result { 206 | if (b == 0) { 207 | return .failure(.divisionByZero) 208 | } 209 | 210 | return .success(a / b) 211 | } 212 | -------------------------------------------------------------------------------- /Tests/BrightFuturesTests/SwiftConcurrencyTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SwiftConcurrencyTests.swift 3 | // BrightFutures 4 | // 5 | // Created by Thomas Visser on 14/06/2021. 6 | // Copyright © 2021 Thomas Visser. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import XCTest 11 | import BrightFutures 12 | 13 | #if swift(>=5.5) 14 | @available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *) 15 | class SwiftConcurrencyTests: XCTestCase { 16 | func testAsyncResult() async { 17 | let a = Async(result: 1) 18 | let result = await a.get() 19 | XCTAssertEqual(result, 1) 20 | } 21 | 22 | func testFutureValueWithoutError() async { 23 | let f = Future { completion in 24 | DispatchQueue.global().asyncAfter(deadline: .now() + .seconds(1)) { 25 | completion(.success(1)) 26 | } 27 | } 28 | 29 | let value = await f.get() 30 | XCTAssertEqual(value, 1) 31 | } 32 | 33 | func testFutureValue() async throws { 34 | let f = Future { completion in 35 | DispatchQueue.global().asyncAfter(deadline: .now() + .seconds(1)) { 36 | completion(.success(1)) 37 | } 38 | } 39 | 40 | let value = try await f.get() 41 | XCTAssertEqual(value, 1) 42 | } 43 | 44 | func testFutureValueThrows() async { 45 | let f = Future { completion in 46 | DispatchQueue.global().asyncAfter(deadline: .now() + .seconds(1)) { 47 | completion(.failure(LocalError.one)) 48 | } 49 | } 50 | do { 51 | _ = try await f.get() 52 | XCTFail() 53 | } catch { 54 | XCTAssertEqual(error as? LocalError, LocalError.one) 55 | } 56 | } 57 | } 58 | 59 | fileprivate enum LocalError: Error, Equatable { 60 | case one 61 | } 62 | 63 | #endif 64 | --------------------------------------------------------------------------------