├── .gitignore ├── Package.resolved ├── Tests ├── LinuxMain.swift └── FutureTests │ ├── XCTestManifests.swift │ ├── FutureFirstTests.swift │ ├── FutureTests.swift │ ├── FutureAllTests.swift │ └── FutureAnyTests.swift ├── Sources └── Future │ ├── NoError.swift │ ├── Futures.swift │ └── Future.swift ├── LICENSE.md ├── Package.swift └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | /.build 3 | /Packages 4 | /*.xcodeproj 5 | /.swiftpm/ 6 | -------------------------------------------------------------------------------- /Package.resolved: -------------------------------------------------------------------------------- 1 | { 2 | "object": { 3 | "pins": [ 4 | 5 | ] 6 | }, 7 | "version": 1 8 | } 9 | -------------------------------------------------------------------------------- /Tests/LinuxMain.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | 3 | import FutureTests 4 | 5 | var tests = [XCTestCaseEntry]() 6 | tests += FutureTests.__allTests() 7 | 8 | XCTMain(tests) 9 | -------------------------------------------------------------------------------- /Sources/Future/NoError.swift: -------------------------------------------------------------------------------- 1 | // ====--------------------------------------------------------==== 2 | // Futures 3 | // https://github.com/genius/future 4 | // ====--------------------------------------------------------==== 5 | 6 | import Foundation 7 | 8 | /** 9 | An uninhabitable enum that can be used to denote when there is no 10 | possible error in a Future 11 | */ 12 | 13 | public enum NoError: Error {} 14 | 15 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 10 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:4.2 2 | // The swift-tools-version declares the minimum version of Swift required to build this package. 3 | 4 | import PackageDescription 5 | 6 | let package = Package( 7 | name: "Future", 8 | products: [ 9 | // Products define the executables and libraries produced by a package, and make them visible to other packages. 10 | .library( 11 | name: "Future", 12 | targets: ["Future"]), 13 | ], 14 | dependencies: [ 15 | // Dependencies declare other packages that this package depends on. 16 | // .package(url: /* package url */, from: "1.0.0"), 17 | ], 18 | targets: [ 19 | // Targets are the basic building blocks of a package. A target can define a module or a test suite. 20 | // Targets can depend on other targets in this package, and on products in packages which this package depends on. 21 | .target( 22 | name: "Future", 23 | dependencies: []), 24 | .testTarget( 25 | name: "FutureTests", 26 | dependencies: ["Future"]), 27 | ] 28 | ) 29 | -------------------------------------------------------------------------------- /Tests/FutureTests/XCTestManifests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | 3 | extension FutureAllTests { 4 | static let __allTests = [ 5 | ("testLikeTypeFuturesAllNoSuccesses", testLikeTypeFuturesAllNoSuccesses), 6 | ("testLikeTypeFuturesAllSomeSuccesses", testLikeTypeFuturesAllSomeSuccesses), 7 | ("testLikeTypeFuturesAllSuccesses", testLikeTypeFuturesAllSuccesses), 8 | ] 9 | } 10 | 11 | extension FutureAnyTests { 12 | static let __allTests = [ 13 | ("testLikeTypeFuturesAnyAllSuccesses", testLikeTypeFuturesAnyAllSuccesses), 14 | ("testLikeTypeFuturesAnyNoSuccesses", testLikeTypeFuturesAnyNoSuccesses), 15 | ("testLikeTypeFuturesAnySomeSuccesses", testLikeTypeFuturesAnySomeSuccesses), 16 | ] 17 | } 18 | 19 | extension FutureFirstTests { 20 | static let __allTests = [ 21 | ("testLikeTypeFuturesFirstError", testLikeTypeFuturesFirstError), 22 | ("testLikeTypeFuturesFirstSuccess", testLikeTypeFuturesFirstSuccess), 23 | ] 24 | } 25 | 26 | extension FutureTests { 27 | static let __allTests = [ 28 | ("testAlways", testAlways), 29 | ("testAsync", testAsync), 30 | ("testChaining", testChaining), 31 | ("testFailingFuture", testFailingFuture), 32 | ("testSuccessFuture", testSuccessFuture), 33 | ] 34 | } 35 | 36 | #if !os(macOS) 37 | public func __allTests() -> [XCTestCaseEntry] { 38 | return [ 39 | testCase(FutureAllTests.__allTests), 40 | testCase(FutureAnyTests.__allTests), 41 | testCase(FutureFirstTests.__allTests), 42 | testCase(FutureTests.__allTests), 43 | ] 44 | } 45 | #endif 46 | -------------------------------------------------------------------------------- /Tests/FutureTests/FutureFirstTests.swift: -------------------------------------------------------------------------------- 1 | // ====--------------------------------------------------------==== 2 | // Futures 3 | // https://github.com/genius/future 4 | // ====--------------------------------------------------------==== 5 | 6 | import XCTest 7 | @testable import Future 8 | 9 | private enum FutureError: Int, Error { 10 | case failure 11 | case otherFailure 12 | } 13 | 14 | // ====--------------------------------------------------------==== 15 | 16 | final class FutureFirstTests: XCTestCase { 17 | static var allTests = [ 18 | ("testLikeTypeFuturesFirstSuccess", testLikeTypeFuturesFirstSuccess), 19 | ("testLikeTypeFuturesFirstError", testLikeTypeFuturesFirstError) 20 | ] 21 | 22 | func testLikeTypeFuturesFirstSuccess() { 23 | let futureOne = Future { resolver in 24 | resolver.resolve(value: 1) 25 | } 26 | 27 | let futureTwo = Future { resolver in 28 | DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { 29 | resolver.resolve(value: 2) 30 | } 31 | } 32 | 33 | let expectation = self.expectation(description: "Futures.first will resolve") 34 | Futures.first([futureOne, futureTwo]).then { value in 35 | XCTAssertEqual(value, 1) 36 | expectation.fulfill() 37 | }.catch { _ in 38 | XCTFail() 39 | } 40 | 41 | self.waitForExpectations(timeout: 0.5, handler: nil) 42 | } 43 | 44 | func testLikeTypeFuturesFirstError() { 45 | let futureOne = Future { resolver in 46 | resolver.reject(error: .failure) 47 | } 48 | 49 | let futureTwo = Future { resolver in 50 | DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { 51 | resolver.reject(error: .otherFailure) 52 | } 53 | } 54 | 55 | let expectation = self.expectation(description: "Futures.first will reject") 56 | Futures.first([futureOne, futureTwo]).then { value in 57 | XCTFail() 58 | }.catch { error in 59 | XCTAssertEqual(error, .failure) 60 | expectation.fulfill() 61 | } 62 | 63 | self.waitForExpectations(timeout: 0.5, handler: nil) 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Future 2 | 3 | A Future is a simplified take on Promises, written in Swift. It aims to provide the following for it's users: 4 | 5 | 1. A clean separation of success & error handlers, with no optional values in completion blocks. 6 | 2. Type-safe error handling 7 | 3. An easy to use call site that better matches how we _describe_ code. 8 | 9 | ## Simple Usage 10 | 11 | When calling a method which returns a Future: 12 | 13 | ``` 14 | doAsyncThing().then { result in 15 | // do something with result 16 | } 17 | ``` 18 | 19 | Result, here, will be non-optional and the `then` block will only be called if the future has completed successfully. 20 | 21 | Error handling is done similarly: 22 | 23 | ``` 24 | doAsyncThingThatErrors().catch { error in 25 | // do something with error 26 | } 27 | ``` 28 | 29 | Errors here are also non-optional, and of the type defined by the Future itself (as opposed to non-typed `throws` calls) 30 | 31 | ## Returning A Future 32 | 33 | Futures rely heavily on generics to provide type safety. 34 | 35 | ``` 36 | func doAsyncThing() -> Future { 37 | } 38 | ``` 39 | 40 | The first value of the Future<> definition denotes the type expected with a successful Future, the second denotes the type expected with a failing Future. The error type must conform to Swift's own `Error` type. 41 | 42 | The Future's initializer aims to retain type safety, but eliminate as much of the tedious type annotations as possible. 43 | 44 | ``` 45 | func doAsyncThing() -> Future { 46 | return Future { resolver in 47 | // ... 48 | } 49 | } 50 | ``` 51 | 52 | The `resolver` value here passed into the block is a Result.Resolver, which has two methods: 53 | 54 | `resolver.resolve(value:)` 55 | 56 | and 57 | 58 | `resolver.reject(error:)` 59 | 60 | Which will either resolve or reject the associated Future appropriately. 61 | 62 | ## Grouping Futures 63 | 64 | Some methods to compose Futures exist on the `Futures` namespace. They are: 65 | 66 | `Futures.all()` 67 | 68 | Which returns a Future which will be resolved with a Collection of Results if, and only if, all of the passed Futures complete successfully. 69 | 70 | `Futures.any()` 71 | 72 | Which returns a Future which will be resolved with a Collection of Results if one or more of the passed Futures complete successfully. 73 | 74 | `Futures.first()` 75 | 76 | Which returns a Future which will resolve or reject with the value of the first passed Future which completes. 77 | 78 | 79 | ## Installation 80 | 81 | ### SwiftPM 82 | 83 | Right now the preferred way to try out Futures is via the Swift Package Manager. 84 | 85 | ``` 86 | .package(url: "https://github.com/genius/future", from: "0.0.1") 87 | ``` 88 | 89 | ### Manually 90 | 91 | You can also copy the `Future.swift` file from the `Sources` directory of your own project if installation via the Swift Package Manager is not available to you. 92 | 93 | ## Contributing 94 | 95 | Bug reports, constructive feedback and pull requests are always welcome. If you're using Futures in your own apps, we'd love to hear about it. 96 | -------------------------------------------------------------------------------- /Tests/FutureTests/FutureTests.swift: -------------------------------------------------------------------------------- 1 | // ====--------------------------------------------------------==== 2 | // Futures 3 | // https://github.com/genius/future 4 | // ====--------------------------------------------------------==== 5 | 6 | import XCTest 7 | @testable import Future 8 | 9 | private enum FutureError: Int, Error { 10 | case failure 11 | case otherFailure 12 | } 13 | 14 | // ====--------------------------------------------------------==== 15 | 16 | final class FutureTests: XCTestCase { 17 | static var allTests = [ 18 | ("testSuccessFuture", testSuccessFuture), 19 | ("testFailingFuture", testFailingFuture), 20 | ("testChaining", testChaining), 21 | ("testAsync", testAsync), 22 | ("testAlways", testAlways) 23 | ] 24 | 25 | func testSuccessFuture() { 26 | let future: Future = Future { resolver in 27 | resolver.resolve(value: true) 28 | } 29 | 30 | future.then { result in 31 | XCTAssertEqual(result, true) 32 | } 33 | 34 | future.catch { _ in 35 | XCTFail() 36 | } 37 | } 38 | 39 | func testFailingFuture() { 40 | let future: Future = Future { resolver in 41 | resolver.reject(error: .failure) 42 | } 43 | 44 | future.then { _ in 45 | XCTFail() 46 | } 47 | 48 | future.catch { error in 49 | XCTAssertEqual(error, .failure) 50 | } 51 | } 52 | 53 | func testChaining() { 54 | let future: Future = Future { resolver in 55 | resolver.resolve(value: true) 56 | } 57 | 58 | future.then { result in 59 | XCTAssertEqual(result, true) 60 | }.then { result in 61 | XCTAssertEqual(result, true) 62 | }.catch { _ in 63 | XCTFail() 64 | } 65 | } 66 | 67 | func testAsync() { 68 | let future: Future = Future { resolver in 69 | DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) { 70 | resolver.resolve(value: true) 71 | } 72 | } 73 | 74 | let expectation = self.expectation(description: "Async futures should work") 75 | future.then { success in 76 | XCTAssertEqual(success, true) 77 | expectation.fulfill() 78 | 79 | }.catch { _ in 80 | XCTFail() 81 | } 82 | 83 | self.wait(for: [expectation], timeout: 5.0) 84 | } 85 | 86 | func testAlways() { 87 | let futureOne: Future = Future { resolver in 88 | resolver.reject(error: .otherFailure) 89 | } 90 | 91 | futureOne.always { value, error in 92 | XCTAssertNil(value) 93 | XCTAssertNotNil(error) 94 | } 95 | 96 | let futureTwo: Future = Future { resolver in 97 | resolver.resolve(value: true) 98 | } 99 | 100 | futureTwo.always { value, error in 101 | XCTAssertNotNil(value) 102 | XCTAssertNil(error) 103 | } 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /Tests/FutureTests/FutureAllTests.swift: -------------------------------------------------------------------------------- 1 | // ====--------------------------------------------------------==== 2 | // Futures 3 | // https://github.com/genius/future 4 | // ====--------------------------------------------------------==== 5 | 6 | import XCTest 7 | @testable import Future 8 | 9 | private enum FutureError: Int, Error { 10 | case failure 11 | case otherFailure 12 | } 13 | 14 | // ====--------------------------------------------------------==== 15 | 16 | final class FutureAllTests: XCTestCase { 17 | static var allTests = [ 18 | ("testLikeTypeFuturesAllSuccesses", testLikeTypeFuturesAllSuccesses), 19 | ("testLikeTypeFuturesAllSomeSuccesses", testLikeTypeFuturesAllSomeSuccesses), 20 | ("testLikeTypeFuturesAllNoSuccesses", testLikeTypeFuturesAllNoSuccesses) 21 | ] 22 | 23 | func testLikeTypeFuturesAllSuccesses() { 24 | let futureOne = Future { resolver in 25 | resolver.resolve(value: 1) 26 | } 27 | 28 | let futureTwo = Future { resolver in 29 | DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { 30 | resolver.resolve(value: 2) 31 | } 32 | } 33 | 34 | let expectation = self.expectation(description: "Futures.all will resolve") 35 | let future = Futures.all([futureOne, futureTwo]) 36 | 37 | future.then { results in 38 | XCTAssertEqual(results.rawValue.count, 2) 39 | expectation.fulfill() 40 | 41 | }.catch { _ in 42 | XCTFail() 43 | } 44 | 45 | self.waitForExpectations(timeout: 0.5, handler: nil) 46 | } 47 | 48 | func testLikeTypeFuturesAllSomeSuccesses() { 49 | let futureOne = Future { resolver in 50 | resolver.resolve(value: 1) 51 | } 52 | 53 | let futureTwo = Future { resolver in 54 | DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { 55 | resolver.reject(error: .failure) 56 | } 57 | } 58 | 59 | let expectation = self.expectation(description: "Futures.all will reject") 60 | let future = Futures.all([futureOne, futureTwo]) 61 | 62 | future.then { _ in 63 | XCTFail() 64 | 65 | }.catch { errors in 66 | XCTAssertEqual(errors.rawValue.count, 1) 67 | expectation.fulfill() 68 | } 69 | 70 | self.waitForExpectations(timeout: 0.5, handler: nil) 71 | } 72 | 73 | func testLikeTypeFuturesAllNoSuccesses() { 74 | let futureOne = Future { resolver in 75 | resolver.reject(error: .failure) 76 | } 77 | 78 | let futureTwo = Future { resolver in 79 | DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { 80 | resolver.reject(error: .failure) 81 | } 82 | } 83 | 84 | let expectation = self.expectation(description: "Futures.all will reject") 85 | let future = Futures.all([futureOne, futureTwo]) 86 | 87 | future.then { results in 88 | XCTFail() 89 | 90 | }.catch { errors in 91 | XCTAssertEqual(errors.rawValue.count, 2) 92 | expectation.fulfill() 93 | } 94 | 95 | self.waitForExpectations(timeout: 0.5, handler: nil) 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /Tests/FutureTests/FutureAnyTests.swift: -------------------------------------------------------------------------------- 1 | // ====--------------------------------------------------------==== 2 | // Futures 3 | // https://github.com/genius/future 4 | // ====--------------------------------------------------------==== 5 | 6 | import XCTest 7 | @testable import Future 8 | 9 | private enum FutureError: Int, Error { 10 | case failure 11 | case otherFailure 12 | } 13 | 14 | // ====--------------------------------------------------------==== 15 | 16 | final class FutureAnyTests: XCTestCase { 17 | static var allTests = [ 18 | ("testLikeTypeFuturesAnyAllSuccesses", testLikeTypeFuturesAnyAllSuccesses), 19 | ("testLikeTypeFuturesAnySomeSuccesses", testLikeTypeFuturesAnySomeSuccesses), 20 | ("testLikeTypeFuturesAnyNoSuccesses", testLikeTypeFuturesAnyNoSuccesses) 21 | ] 22 | 23 | func testLikeTypeFuturesAnyAllSuccesses() { 24 | let futureOne = Future { resolver in 25 | resolver.resolve(value: 1) 26 | } 27 | 28 | let futureTwo = Future { resolver in 29 | DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { 30 | resolver.resolve(value: 2) 31 | } 32 | } 33 | 34 | let expectation = self.expectation(description: "Futures.any will resolve") 35 | let future = Futures.any([futureOne, futureTwo]) 36 | 37 | future.then { results in 38 | XCTAssertEqual(results.rawValue.count, 2) 39 | expectation.fulfill() 40 | 41 | }.catch { _ in 42 | XCTFail() 43 | } 44 | 45 | self.waitForExpectations(timeout: 0.5, handler: nil) 46 | } 47 | 48 | func testLikeTypeFuturesAnySomeSuccesses() { 49 | let futureOne = Future { resolver in 50 | resolver.resolve(value: 1) 51 | } 52 | 53 | let futureTwo = Future { resolver in 54 | DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { 55 | resolver.reject(error: .failure) 56 | } 57 | } 58 | 59 | let expectation = self.expectation(description: "Futures.first will resolve") 60 | let future = Futures.any([futureOne, futureTwo]) 61 | 62 | future.then { results in 63 | XCTAssertEqual(results.rawValue.count, 1) 64 | expectation.fulfill() 65 | 66 | }.catch { _ in 67 | XCTFail() 68 | } 69 | 70 | self.waitForExpectations(timeout: 0.5, handler: nil) 71 | } 72 | 73 | func testLikeTypeFuturesAnyNoSuccesses() { 74 | let futureOne = Future { resolver in 75 | resolver.reject(error: .failure) 76 | } 77 | 78 | let futureTwo = Future { resolver in 79 | DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { 80 | resolver.reject(error: .failure) 81 | } 82 | } 83 | 84 | let expectation = self.expectation(description: "Futures.first will reject") 85 | let future = Futures.any([futureOne, futureTwo]) 86 | 87 | future.then { results in 88 | XCTFail() 89 | 90 | }.catch { errors in 91 | XCTAssertEqual(errors.rawValue.count, 2) 92 | expectation.fulfill() 93 | } 94 | 95 | self.waitForExpectations(timeout: 0.5, handler: nil) 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /Sources/Future/Futures.swift: -------------------------------------------------------------------------------- 1 | // ====--------------------------------------------------------==== 2 | // Futures 3 | // https://github.com/genius/future 4 | // ====--------------------------------------------------------==== 5 | 6 | import Foundation 7 | 8 | // ====--------------------------------------------------------==== 9 | /* 10 | Future [Result|Error]Collections are thin wrappers around arrays (themselves 11 | available as .rawValue) in order to solve the [E] !== Swift.Error issue. 12 | */ 13 | 14 | extension Future { 15 | /** 16 | A thin wrapper around an array of values returned by a collection 17 | of Futures. 18 | 19 | ``` 20 | Futures.all([doAsyncThing(), doOtherAsyncThing()]).then { results in 21 | let arrayValue = results.rawValue 22 | // or... 23 | let result = results[0] 24 | } 25 | ``` 26 | */ 27 | public struct ResultCollection { 28 | fileprivate(set) public var rawValue = [T]() 29 | 30 | public subscript(_ index: Int) -> T { 31 | return self.rawValue[index] 32 | } 33 | } 34 | 35 | /** 36 | A thin wrapper around an array of errors returned by a collection 37 | of Futures. 38 | 39 | ``` 40 | Futures.all([doAsyncThing(), doOtherAsyncThing()]).catch { errors in 41 | let arrayValue = errors 42 | // or... 43 | let result = results[0] 44 | } 45 | ``` 46 | */ 47 | 48 | public struct ErrorCollection: Swift.Error { 49 | fileprivate(set) public var rawValue = [E]() 50 | 51 | public subscript(_ index: Int) -> E { 52 | return self.rawValue[index] 53 | } 54 | } 55 | } 56 | 57 | // ====--------------------------------------------------------==== 58 | 59 | public enum Futures { 60 | /** 61 | Returns a Future that will be resolved after all passed futures complete that will be successful if all of the original futures are 62 | successful. This method makes no assumption about order or thread delivery. 63 | */ 64 | public static func all(_ futures: [Future]) -> Future.ResultCollection, Future.ErrorCollection> { 65 | return Future { resolver in 66 | var results = Future.ResultCollection() 67 | var errors = Future.ErrorCollection() 68 | let totalCount = futures.count 69 | 70 | futures.forEach { future in 71 | future.always { result, error in 72 | if let result = result { 73 | results.rawValue.append(result) 74 | } else { 75 | errors.rawValue.append(error!) 76 | } 77 | 78 | let resolvedCount = results.rawValue.count + errors.rawValue.count 79 | if resolvedCount == totalCount { 80 | if errors.rawValue.isEmpty { 81 | resolver.resolve(value: results) 82 | } else { 83 | resolver.reject(error: errors) 84 | } 85 | } 86 | } 87 | } 88 | } 89 | } 90 | 91 | /** 92 | Returns a Future that will be resolved after all passed futures complete that will be successful if any of the original futures are 93 | successful. This method makes no assumption about order or thread delivery. 94 | */ 95 | 96 | public static func any(_ futures: [Future]) -> Future.ResultCollection, Future.ErrorCollection> { 97 | return Future { resolver in 98 | var results = Future.ResultCollection() 99 | var errors = Future.ErrorCollection() 100 | let totalCount = futures.count 101 | 102 | futures.forEach { future in 103 | future.always { result, error in 104 | if let result = result { 105 | results.rawValue.append(result) 106 | } else { 107 | errors.rawValue.append(error!) 108 | } 109 | 110 | let resolvedCount = results.rawValue.count + errors.rawValue.count 111 | if resolvedCount == totalCount { 112 | if results.rawValue.isEmpty { 113 | resolver.reject(error: errors) 114 | } else { 115 | resolver.resolve(value: results) 116 | } 117 | } 118 | } 119 | } 120 | } 121 | } 122 | 123 | /** 124 | Returns a Future that will be resolved or rejected with the value or error of the first future to complete. If multiple 125 | Futures are already resolved at the time `first` is called, the first resolved future in the passed array will be used. 126 | */ 127 | 128 | public static func first(_ futures: [Future]) -> Future { 129 | return Future { resolver in 130 | // Safe due to the feature that calling resolve on a resolved Future's resolver is, basically, a no-op 131 | futures.forEach { future in 132 | future.then { value in 133 | resolver.resolve(value: value) 134 | 135 | }.catch { error in 136 | resolver.reject(error: error) 137 | 138 | } 139 | } 140 | } 141 | } 142 | } 143 | 144 | -------------------------------------------------------------------------------- /Sources/Future/Future.swift: -------------------------------------------------------------------------------- 1 | // ====--------------------------------------------------------==== 2 | // Futures 3 | // https://github.com/genius/future 4 | // ====--------------------------------------------------------==== 5 | 6 | import Foundation 7 | 8 | /** 9 | A Future is a simplified Promise whose main goals are: 10 | 11 | 1. Guaranteed type-safe values & errors in success and error blocks 12 | 2. A simplified async code flow where then / catch can be called and the 13 | code inside the block will be executed appropriately regardless of Future 14 | state when called. 15 | 3. Cleaner, more literate async method call sites. 16 | 17 | A Future has an associated Future.Resolver object whose job is to 18 | set and update the internal success / failure state of the Future itself. 19 | Whereas the Future should be considered safe and idiomatic to store and pass 20 | around, the Resolver shouldn't be. 21 | */ 22 | 23 | public final class Future { 24 | public typealias Value = T 25 | public typealias Error = E 26 | 27 | // ====--------------------------------------------------------==== 28 | 29 | /** 30 | The Resolver is in charge of resolving or rejecting the associated 31 | future, and should only be used inside the initializer block. Calling 32 | resolve() or reject() on the resolver will trigger the associated 33 | blocks on the Future 34 | */ 35 | 36 | public final class Resolver { 37 | private(set) public var isResolved = false 38 | fileprivate var _future: Future? 39 | 40 | fileprivate init(_ future: Future) { 41 | self._future = future 42 | } 43 | 44 | /** 45 | Sets the final state of the associated Future to the value, and 46 | immediately calls all then() observers with the value. 47 | */ 48 | 49 | public func resolve(value: Value) { 50 | self._future?.resolve(value: value) 51 | self._future = nil 52 | } 53 | 54 | /** 55 | Sets the final state of the associated Future to the error, and 56 | immediately calls all catch() observers with the error. 57 | */ 58 | 59 | public func reject(error: Error) { 60 | self._future?.reject(error: error) 61 | self._future = nil 62 | } 63 | } 64 | 65 | // Store the success / error values for future bindings 66 | private var _value: Value? 67 | private var _error: Error? 68 | 69 | // Store the observers for success / errors 70 | private var _observers = [((Value) -> Void)]() 71 | private var _errorObservers = [((Error) -> Void)]() 72 | 73 | public var isResolved: Bool { 74 | return self._value != nil || self._error != nil 75 | } 76 | 77 | // ====--------------------------------------------------------==== 78 | // MARK: - Init, etc... 79 | 80 | public init(_ block: (Future.Resolver) -> Void) { 81 | let resolver = Resolver(self) 82 | block(resolver) 83 | } 84 | 85 | // ====--------------------------------------------------------==== 86 | // MARK: - State Updating 87 | 88 | private func resolve(value: Value) { 89 | guard !self.isResolved else { 90 | return 91 | } 92 | 93 | self._value = value 94 | self._observers.forEach { $0(value) } 95 | } 96 | 97 | private func reject(error: Error) { 98 | guard !self.isResolved else { 99 | return 100 | } 101 | 102 | self._error = error 103 | self._errorObservers.forEach { $0(error) } 104 | } 105 | 106 | // ====--------------------------------------------------------==== 107 | // MARK: - Handlers 108 | 109 | /** 110 | Adds a success handler which will be invoked immediately if the Future has 111 | already been resolved successfully with a Value, or some time in the future 112 | when this has occurred. Returns itself. 113 | */ 114 | 115 | @discardableResult 116 | public func then(_ block: @escaping ((Value) -> Void)) -> Future { 117 | if let value = self._value { 118 | block(value) 119 | 120 | } else { 121 | self._observers.append(block) 122 | } 123 | 124 | return self 125 | } 126 | 127 | @discardableResult 128 | public func then(on: DispatchQueue, block: @escaping ((Value) -> Void)) -> Future { 129 | return self.then { value in 130 | on.async { block(value) } 131 | } 132 | } 133 | 134 | /** 135 | Adds an error handler which will be invoked immediately if the Future has 136 | already been rejected with an Error, or some time in the future 137 | when this has occurred. Returns itself. 138 | */ 139 | 140 | @discardableResult 141 | public func `catch`(_ block: @escaping ((Error) -> Void)) -> Future { 142 | if let error = self._error { 143 | block(error) 144 | 145 | } else { 146 | self._errorObservers.append(block) 147 | } 148 | 149 | return self 150 | } 151 | 152 | @discardableResult 153 | public func `catch`(on: DispatchQueue, block: @escaping ((Error) -> Void)) -> Future { 154 | return self.catch { error in 155 | on.async { block(error) } 156 | } 157 | } 158 | 159 | /** 160 | Adds a block handler containing optional Value and Error types which will always be 161 | called whenever the Future completes. 162 | */ 163 | 164 | @discardableResult 165 | public func always(_ block: @escaping ((Value?, Error?) -> Void)) -> Future { 166 | if let value = self._value { 167 | block(value, nil) 168 | 169 | } else if let error = self._error { 170 | block(nil, error) 171 | 172 | } else { 173 | self._observers.append({ value in 174 | block(value, nil) 175 | }) 176 | 177 | self._errorObservers.append({ error in 178 | block(nil, error) 179 | }) 180 | } 181 | 182 | return self 183 | } 184 | } 185 | 186 | public extension Future.Resolver where T == Void { 187 | func resolve() { 188 | self.resolve(value: ()) 189 | } 190 | } 191 | 192 | public extension Future { 193 | static func on(_ dispatchQueue: DispatchQueue, block: @escaping (Future.Resolver) -> Void) -> Future { 194 | return Future { resolver in 195 | dispatchQueue.async { block(resolver) } 196 | } 197 | } 198 | } 199 | --------------------------------------------------------------------------------