├── .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 | [](https://gitter.im/Thomvis/BrightFutures?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) [](https://travis-ci.org/Thomvis/BrightFutures) [](https://github.com/Carthage/Carthage) [](https://cocoapods.org/pods/BrightFutures) [](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