├── .codebeatignore ├── .github └── FUNDING.yml ├── .gitignore ├── .swiftlint.yml ├── .swiftpm └── xcode │ └── package.xcworkspace │ └── contents.xcworkspacedata ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── Package.swift ├── README.md ├── Sources ├── PrivacyInfo.xcprivacy └── Then │ ├── Async.swift │ ├── Await+Operators.swift │ ├── Await.swift │ ├── Promise+Aliases.swift │ ├── Promise+BridgeError.swift │ ├── Promise+Chain.swift │ ├── Promise+Delay.swift │ ├── Promise+Error.swift │ ├── Promise+Finally.swift │ ├── Promise+First.swift │ ├── Promise+Helpers.swift │ ├── Promise+NoMatterWhat.swift │ ├── Promise+Progress.swift │ ├── Promise+Race.swift │ ├── Promise+Recover.swift │ ├── Promise+Retry.swift │ ├── Promise+Then.swift │ ├── Promise+Timeout.swift │ ├── Promise+Unwrap.swift │ ├── Promise+Validate.swift │ ├── Promise+Zip.swift │ ├── Promise+nil.swift │ ├── Promise.swift │ ├── PromiseBlocks.swift │ ├── PromiseError.swift │ ├── PromiseState.swift │ ├── VoidPromise.swift │ └── WhenAll.swift ├── Tests └── ThenTests │ ├── AsyncAwaitTests.swift │ ├── BridgeErrorTests.swift │ ├── ChainTests.swift │ ├── DelayTests.swift │ ├── FinallyTests.swift │ ├── Helpers.swift │ ├── MemoryTests.swift │ ├── NoMatterWhatTests.swift │ ├── OnErrorTests.swift │ ├── ProgressTests.swift │ ├── RaceTests.swift │ ├── RecoverTests.swift │ ├── RegisterThenTests.swift │ ├── RetryTests.swift │ ├── ThenTests.swift │ ├── TimeoutTests.swift │ ├── UnwrapTests.swift │ ├── ValidateTests.swift │ ├── WhenAllTests.swift │ └── ZipTests.swift └── banner.png /.codebeatignore: -------------------------------------------------------------------------------- 1 | thenTests/** 2 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | open_collective: freshos 2 | github: s4cha 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 4 | 5 | ## Build generated 6 | build/ 7 | DerivedData 8 | 9 | ## Various settings 10 | *.pbxuser 11 | !default.pbxuser 12 | *.mode1v3 13 | !default.mode1v3 14 | *.mode2v3 15 | !default.mode2v3 16 | *.perspectivev3 17 | !default.perspectivev3 18 | xcuserdata 19 | 20 | ## Other 21 | *.xccheckout 22 | *.moved-aside 23 | *.xcuserstate 24 | *.xcscmblueprint 25 | 26 | ## Obj-C/Swift specific 27 | *.hmap 28 | *.ipa 29 | 30 | ## Playgrounds 31 | timeline.xctimeline 32 | playground.xcworkspace 33 | 34 | # Swift Package Manager 35 | # 36 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 37 | # Packages/ 38 | .build/ 39 | 40 | # CocoaPods 41 | # 42 | # We recommend against adding the Pods directory to your .gitignore. However 43 | # you should judge for yourself, the pros and cons are mentioned at: 44 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 45 | # 46 | # Pods/ 47 | 48 | # Carthage 49 | # 50 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 51 | # 52 | 53 | Example/testThen/Carthage/Checkouts/ 54 | 55 | Carthage/Build 56 | 57 | # fastlane 58 | # 59 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 60 | # screenshots whenever they are needed. 61 | # For more information about the recommended setup visit: 62 | # https://github.com/fastlane/fastlane/blob/master/docs/Gitignore.md 63 | 64 | fastlane/report.xml 65 | fastlane/screenshots 66 | -------------------------------------------------------------------------------- /.swiftlint.yml: -------------------------------------------------------------------------------- 1 | 2 | disabled_rules: 3 | - trailing_whitespace 4 | - large_tuple 5 | - force_try 6 | - identifier_name 7 | opt_in_rules: 8 | # - missing_docs 9 | - force_unwrapping 10 | -------------------------------------------------------------------------------- /.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. 6 | 7 | ## Our Standards 8 | 9 | Examples of behavior that contributes to creating a positive environment include: 10 | 11 | * Using welcoming and inclusive language 12 | * Being respectful of differing viewpoints and experiences 13 | * Gracefully accepting constructive criticism 14 | * Focusing on what is best for the community 15 | * Showing empathy towards other community members 16 | 17 | Examples of unacceptable behavior by participants include: 18 | 19 | * The use of sexualized language or imagery and unwelcome sexual attention or advances 20 | * Trolling, insulting/derogatory comments, and personal or political attacks 21 | * Public or private harassment 22 | * Publishing others' private information, such as a physical or electronic address, without explicit permission 23 | * Other conduct which could reasonably be considered inappropriate in a professional setting 24 | 25 | ## Our Responsibilities 26 | 27 | Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. 28 | 29 | Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. 30 | 31 | ## Scope 32 | 33 | This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. 34 | 35 | ## Enforcement 36 | 37 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at sachadso@gmail.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. 38 | 39 | Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. 40 | 41 | ## Attribution 42 | 43 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] 44 | 45 | [homepage]: http://contributor-covenant.org 46 | [version]: http://contributor-covenant.org/version/1/4/ 47 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | First off, thank YOU! 2 | 3 | It's thanks to people like you that the project thrives :) 4 | If you are reading this, it means you want to contribute to this project one way or another. 5 | 6 | Our vision for Then is to simplify swift async code on all platforms running Swift code. 7 | 8 | We are very open to contributions and any ideas that can improve the project ! 9 | 10 | ## How can I contribute? 11 | 12 | - Report a bug or strange behavior via an issue 13 | - Suggest a new feature via an issue 14 | - Open a pull request 15 | 16 | In order to maximize your pull request approval, you may want to open an issue first. 17 | That way the addition or change can be discussed, so that you don't waste time on a feature that might be rejected later. 18 | 19 | 20 | When you issue a change, make sure you run the unit tests (Cmd+U) and check all pass. 21 | 22 | When adding a new feature, it's best if you can add the corresponding unit tests. If this is new to you, no problem, the team can help you 23 | or add the unit tests for you :) 24 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 S4cha 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 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.9 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: "Then", 8 | platforms: [ 9 | .iOS(.v12), 10 | .macOS(.v10_13), 11 | .tvOS(.v12), 12 | .watchOS(.v6) 13 | ], 14 | products: [ 15 | .library(name: "Then", targets: ["Then"]) 16 | ], 17 | targets: [ 18 | .target(name: "Then", path: "Sources", resources: [.copy("PrivacyInfo.xcprivacy")]), 19 | .testTarget(name: "ThenTests", dependencies: ["Then"]) 20 | ] 21 | ) 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![Then](https://raw.githubusercontent.com/freshOS/then/master/banner.png) 2 | 3 | # Then 4 | [![Language: Swift 5](https://img.shields.io/badge/language-swift5-f48041.svg?style=flat)](https://developer.apple.com/swift) 5 | ![Platform: iOS 8+/macOS10.11](https://img.shields.io/badge/platform-iOS|macOS|tvOS|watchOS-blue.svg?style=flat) 6 | [![SPM compatible](https://img.shields.io/badge/SPM-compatible-4BC51D.svg?style=flat)](https://swift.org/package-manager/) 7 | [![Build Status](https://app.bitrise.io/app/c6b39aea308618bf/status.svg?token=11AKF3ZUtPtNoXE9Lnk62A&branch=master)](https://app.bitrise.io/app/c6b39aea308618bf) 8 | [![License: MIT](http://img.shields.io/badge/license-MIT-lightgrey.svg?style=flat)](https://github.com/freshOS/then/blob/master/LICENSE) 9 | ![Release version](https://img.shields.io/github/v/release/freshos/then.svg) 10 | 11 | [Reason](#why) - [Example](#example) - [Documentation](#documentation) - [Installation](#installation) 12 | 13 | ```swift 14 | fetchUserId().then { id in 15 | print("UserID : \(id)") 16 | }.onError { e in 17 | print("An error occured : \(e)") 18 | }.finally { 19 | print("Everything is Done :)") 20 | } 21 | ``` 22 | 23 | ```swift 24 | let userId = try! awaitPromise(fetchUserId()) 25 | ``` 26 | 27 | Because async code is hard to write, hard to read, hard to reason about. **A pain to maintain** 28 | 29 | ## Try it 30 | then is part of [freshOS](https://freshos.github.io/) iOS toolset. Try it in an example App! Download Starter Project 31 | 32 | ## How 33 | By using a **then** keyword that enables you to write aSync code that *reads like an English sentence* 34 | Async code is now **concise**, **flexible** and **maintainable** ❤️ 35 | 36 | ## What 37 | - [x] Based on the popular `Promise` / `Future` concept 38 | - [x] `Async` / `Await` 39 | - [x] `progress` `race` `recover` `validate` `retry` `bridgeError` `chain` `noMatterWhat` ... 40 | - [x] Strongly Typed 41 | - [x] Pure Swift & Lightweight 42 | 43 | ## Example 44 | ### Before 45 | ```swift 46 | fetchUserId({ id in 47 | fetchUserNameFromId(id, success: { name in 48 | fetchUserFollowStatusFromName(name, success: { isFollowed in 49 | // The three calls in a row succeeded YAY! 50 | reloadList() 51 | }, failure: { error in 52 | // Fetching user ID failed 53 | reloadList() 54 | }) 55 | }, failure: { error in 56 | // Fetching user name failed 57 | reloadList() 58 | }) 59 | }) { error in 60 | // Fetching user follow status failed 61 | reloadList() 62 | } 63 | 🙉🙈🙊#callbackHell 64 | ``` 65 | 66 | ---- 67 | 68 | ### After 69 | 70 | ```swift 71 | fetchUserId() 72 | .then(fetchUserNameFromId) 73 | .then(fetchUserFollowStatusFromName) 74 | .then(updateFollowStatus) 75 | .onError(showErrorPopup) 76 | .finally(reloadList) 77 | ``` 78 | ## 🎉🎉🎉 79 | 80 | 81 | ## Going further 🤓 82 | 83 | ```swift 84 | fetchUserId().then { id in 85 | print("UserID : \(id)") 86 | }.onError { e in 87 | print("An error occured : \(e)") 88 | }.finally { 89 | print("Everything is Done :)") 90 | } 91 | ``` 92 | 93 | If we want this to be **maintainable**, it should read *like an English sentence* 94 | We can do this by extracting our blocks into separate functions: 95 | 96 | ```swift 97 | fetchUserId() 98 | .then(printUserID) 99 | .onError(showErrorPopup) 100 | .finally(reloadList) 101 | ``` 102 | 103 | This is now **concise**, **flexible**, **maintainable**, and it reads like an English sentence <3 104 | Mental sanity saved 105 | // #goodbyeCallbackHell 106 | 107 | 108 | ## Documentation 109 | 110 | 1. [Writing your own Promise](#writing-your-own-promise-💪) 111 | 2. [Progress](#progress) 112 | 3. [Registering a block for later](#registering-a-block-for-later) 113 | 4. [Returning a rejecting promise](#returning-a-rejecting-promise) 114 | 5. [Common Helpers](#common-helpers) 115 | 1. [race](#race) 116 | 2. [recover](#recover) 117 | 3. [validate](#validate) 118 | 4. [retry](#retry) 119 | 5. [bridgeError](#bridgeError) 120 | 6. [whenAll](#whenAll) 121 | 7. [chain](#chain) 122 | 8. [noMatterWhat](#nomatterwhat) 123 | 9. [unwrap](#unwrap) 124 | 6. [AsyncTask](#asynctask) 125 | 7. [Async/Await](#async/await) 126 | 127 | ### Writing your own Promise 💪 128 | Wondering what fetchUserId() is? 129 | It is a simple function that returns a strongly typed promise : 130 | 131 | ```swift 132 | func fetchUserId() -> Promise { 133 | return Promise { resolve, reject in 134 | print("fetching user Id ...") 135 | wait { resolve(1234) } 136 | } 137 | } 138 | ``` 139 | Here you would typically replace the dummy wait function by your network request <3 140 | 141 | ### Progress 142 | 143 | As for `then` and `onError`, you can also call a `progress` block for things like uploading an avatar for example. 144 | 145 | ```swift 146 | uploadAvatar().progress { p in 147 | // Here update progressView for example 148 | } 149 | .then(doSomething) 150 | .onError(showErrorPopup) 151 | .finally(doSomething) 152 | ``` 153 | 154 | ### Registering a block for later 155 | Our implementation slightly differs from the original javascript Promises. Indeed, they do not start right away, on purpose. Calling `then`, `onError`, or `finally` will start them automatically. 156 | 157 | Calling `then` starts a promise if it is not already started. 158 | In some cases, we only want to register some code for later. 159 | For instance, in the case of JSON to Swift model parsing, we often want to attach parsing blocks to JSON promises, but without starting them. 160 | 161 | In order to do that we need to use `registerThen` instead. It's the exact same thing as `then` without starting the promise right away. 162 | 163 | ```swift 164 | let fetchUsers:Promise<[User]> = fetchUsersJSON().registerThen(parseUsersJSON) 165 | 166 | // Here promise is not launched yet \o/ 167 | 168 | // later... 169 | fetchUsers.then { users in 170 | // YAY 171 | } 172 | ``` 173 | Note that `onError` and `finally` also have their non-starting counterparts : `registerOnError` and `registerFinally`. 174 | 175 | ### Returning a rejecting promise 176 | 177 | Oftetimes we need to return a rejecting promise as such : 178 | 179 | ```swift 180 | return Promise { _, reject in 181 | reject(anError) 182 | } 183 | ``` 184 | 185 | This can be written with the following shortcut : 186 | ```swift 187 | return Promise.reject(error:anError) 188 | ``` 189 | 190 | ### Common Helpers 191 | 192 | #### Race 193 | 194 | With `race`, you can send multiple tasks and get the result of the first one coming back : 195 | ```swift 196 | race(task1, task2, task3).then { work in 197 | // The first result ! 198 | } 199 | ``` 200 | 201 | #### Recover 202 | 203 | With `.recover`, you can provide a fallback value for a failed Promise. 204 | You can : 205 | - Recover with a value 206 | - Recover with a value for a specific Error type 207 | - Return a value from a block, enabling you to test the type of error and return distinct values. 208 | - Recover with another Promise with the same Type 209 | 210 | ```swift 211 | .recover(with: 12) 212 | .recover(MyError.defaultError, with: 12) 213 | .recover { e in 214 | if e == x { return 32 } 215 | if e == y { return 143 } 216 | throw MyError.defaultError 217 | } 218 | .recover { e -> Promise in 219 | // Deal with the error then 220 | return Promise.resolve(56) 221 | // Or 222 | return Promise.reject(e) 223 | } 224 | } 225 | .recover(with: Promise.resolve(56)) 226 | ``` 227 | Note that in the block version you can also throw your own error \o/ 228 | 229 | 230 | #### Validate 231 | 232 | With `.validate`, you can break the promise chain with an assertion block. 233 | 234 | You can: 235 | - Insert assertion in Promise chain 236 | - Insert assertion and return you own Error 237 | 238 | For instance checking if a user is allowed to drink alcohol : 239 | ```swift 240 | fetchUserAge() 241 | .validate { $0 > 18 } 242 | .then { age in 243 | // Offer a drink 244 | } 245 | 246 | .validate(withError: MyError.defaultError, { $0 > 18 })` 247 | ``` 248 | A failed validation will retrun a `PromiseError.validationFailed` by default. 249 | 250 | #### Retry 251 | 252 | With `retry`, you can restart a failed Promise X number of times. 253 | ```swift 254 | doSomething() 255 | .retry(10) 256 | .then { v in 257 | // YAY! 258 | }.onError { e in 259 | // Failed 10 times in a row 260 | } 261 | ``` 262 | 263 | #### BridgeError 264 | 265 | With `.bridgeError`, you can intercept a low-level Error and return your own high level error. 266 | The classic use-case is when you receive an api error and you bridge it to your own domain error. 267 | 268 | You can: 269 | - Catch all errors and use your own Error type 270 | - Catch only a specific error 271 | 272 | ```swift 273 | .bridgeError(to: MyError.defaultError) 274 | .bridgeError(SomeError, to: MyError.defaultError) 275 | ``` 276 | 277 | #### WhenAll 278 | 279 | With `.whenAll`, you can combine multiple calls and get all the results when all the promises are fulfilled : 280 | 281 | ```swift 282 | whenAll(fetchUsersA(),fetchUsersB(), fetchUsersC()).then { allUsers in 283 | // All the promises came back 284 | } 285 | ``` 286 | 287 | #### Chain 288 | 289 | With `chain`, you can add behaviours without changing the chain of Promises. 290 | 291 | A common use-case is for adding Analytics tracking like so: 292 | 293 | ```swift 294 | extension Photo { 295 | public func post() -> Async { 296 | return api.post(self).chain { _ in 297 | Tracker.trackEvent(.postPicture) 298 | } 299 | } 300 | } 301 | ``` 302 | 303 | #### NoMatterWhat 304 | 305 | With `noMatterWhat` you can add code to be executed in the middle of a promise chain, no matter what happens. 306 | 307 | ```swift 308 | func fetchNext() -> Promise<[T]> { 309 | isLoading = true 310 | call.params["page"] = page + 1 311 | return call.fetch() 312 | .registerThen(parseResponse) 313 | .resolveOnMainThread() 314 | .noMatterWhat { 315 | self.isLoading = false 316 | } 317 | } 318 | ``` 319 | 320 | #### Unwrap 321 | 322 | With `unwrap` you can transform an optional into a promise : 323 | 324 | ```swift 325 | func fetch(userId: String?) -> Promise { 326 | return unwrap(userId).then { 327 | network.get("/user/\($0)") 328 | } 329 | } 330 | ``` 331 | Unwrap will fail the promise chain with `unwrappingFailed` error in case of a nil value :) 332 | 333 | ### AsyncTask 334 | `AsyncTask` and `Async` typealisases are provided for those of us who think that Async can be clearer than `Promise`. 335 | Feel free to replace `Promise` by `AsyncTask` and `Promise` by `Async` wherever needed. 336 | This is purely for the eyes :) 337 | 338 | 339 | ### Async/Await 340 | 341 | `awaitPromise` waits for a promise to complete synchronously and yields the result : 342 | 343 | ```swift 344 | let photos = try! awaitPromise(getPhotos()) 345 | ``` 346 | 347 | `async` takes a block and wraps it in a background Promise. 348 | 349 | ```swift 350 | async { 351 | let photos = try awaitPromise(getPhotos()) 352 | } 353 | ``` 354 | Notice how we don't need the `!` anymore because `async` will catch the errors. 355 | 356 | 357 | Together, `async`/`awaitPromise` enable us to write asynchronous code in a synchronous manner : 358 | 359 | ```swift 360 | async { 361 | let userId = try awaitPromise(fetchUserId()) 362 | let userName = try awaitPromise(fetchUserNameFromId(userId)) 363 | let isFollowed = try awaitPromise(fetchUserFollowStatusFromName(userName)) 364 | return isFollowed 365 | }.then { isFollowed in 366 | print(isFollowed) 367 | }.onError { e in 368 | // handle errors 369 | } 370 | ``` 371 | 372 | #### Await operators 373 | Await comes with `..` shorthand operator. The `..?` will fallback to a nil value instead of throwing. 374 | ```swift 375 | let userId = try awaitPromise(fetchUserId()) 376 | ``` 377 | Can be written like this: 378 | ```swift 379 | let userId = try ..fetchUserId() 380 | ``` 381 | 382 | 383 | ## Installation 384 | 385 | The Swift Package Manager (SPM) is now the official way to install `Then`. The other package managers are now deprecated as of `5.1.3` and won't be supported in future versions. 386 | 387 | ### Swift Package Manager 388 | 389 | `Xcode` > `File` > `Swift Packages` > `Add Package Dependency...` > `Paste` `https://github.com/freshOS/Then` 390 | 391 | ### Cocoapods - Deprecated 392 | ```swift 393 | target 'MyApp' 394 | pod 'thenPromise' 395 | use_frameworks! 396 | ``` 397 | 398 | ### Carthage - Deprecated 399 | ``` 400 | github "freshOS/then" 401 | ``` 402 | 403 | ## Contributors 404 | 405 | [S4cha](https://github.com/S4cha), [Max Konovalov](https://github.com/maxkonovalov), [YannickDot](https://github.com/YannickDot), [Damien](https://github.com/damien-nd), 406 | [piterlouis](https://github.com/piterlouis) 407 | 408 | ## Swift Version 409 | 410 | - Swift 2 -> version [**1.4.2**](https://github.com/freshOS/then/releases/tag/1.4.2) 411 | - Swift 3 -> version [**2.2.5**](https://github.com/freshOS/then/releases/tag/2.2.5) 412 | - Swift 4 -> version [**3.1.0**](https://github.com/freshOS/then/releases/tag/3.1.0) 413 | - Swift 4.1 -> version [**4.1.1**](https://github.com/freshOS/then/releases/tag/4.1.1) 414 | - Swift 4.2 -> version [**4.2.0**](https://github.com/freshOS/then/releases/tag/4.2.0) 415 | - Swift 4.2.1 -> version [**4.2.0**](https://github.com/freshOS/then/releases/tag/4.2.1) 416 | - Swift 5.0 -> version [**5.0.0**](https://github.com/freshOS/then/releases/tag/5.0.0) 417 | - Swift 5.1 -> version [**5.1.0**](https://github.com/freshOS/then/releases/tag/5.1.0) 418 | - Swift 5.1.3 -> version [**5.1.2**](https://github.com/freshOS/then/releases/tag/5.1.2) 419 | 420 | 421 | 422 | ### Backers 423 | Like the project? Offer coffee or support us with a monthly donation and help us continue our activities :) 424 | 425 | 426 | 427 | 428 | 429 | 430 | 431 | 432 | 433 | 434 | 435 | 436 | 437 | 438 | 439 | 440 | 441 | 442 | 443 | 444 | 445 | 446 | 447 | 448 | 449 | 450 | 451 | 452 | 453 | 454 | 455 | 456 | ### Sponsors 457 | Become a sponsor and get your logo on our README on Github with a link to your site :) 458 | 459 | 460 | 461 | 462 | 463 | 464 | 465 | 466 | 467 | 468 | 469 | 470 | 471 | 472 | 473 | 474 | 475 | 476 | 477 | 478 | 479 | 480 | 481 | 482 | 483 | 484 | 485 | 486 | 487 | 488 | 489 | -------------------------------------------------------------------------------- /Sources/PrivacyInfo.xcprivacy: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | NSPrivacyTracking 6 | 7 | NSPrivacyTrackingDomains 8 | 9 | NSPrivacyCollectedDataTypes 10 | 11 | NSPrivacyAccessedAPITypes 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /Sources/Then/Async.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Async.swift 3 | // then 4 | // 5 | // Created by Sacha Durand Saint Omer on 13/03/2017. 6 | // Copyright © 2017 s4cha. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import Dispatch 11 | 12 | @discardableResult 13 | public func async(block:@escaping () throws -> T) -> Async { 14 | let p = Promise { resolve, reject in 15 | DispatchQueue(label: "then.async.queue", attributes: .concurrent).async { 16 | do { 17 | let t = try block() 18 | resolve(t) 19 | } catch { 20 | reject(error) 21 | } 22 | } 23 | } 24 | p.start() 25 | return p 26 | } 27 | -------------------------------------------------------------------------------- /Sources/Then/Await+Operators.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Await+Operators.swift 3 | // then 4 | // 5 | // Created by Sacha DSO on 30/05/2018. 6 | // Copyright © 2018 s4cha. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | prefix operator .. 12 | 13 | public prefix func .. (promise: Promise) throws -> T { 14 | return try awaitPromise(promise) 15 | } 16 | 17 | public prefix func .. (promise: Promise?) throws -> T { 18 | guard let promise = promise else { throw PromiseError.unwrappingFailed } 19 | return try awaitPromise(promise) 20 | } 21 | 22 | prefix operator ..? 23 | 24 | public prefix func ..? (promise: Promise) -> T? { 25 | do { 26 | return try awaitPromise(promise) 27 | } catch { 28 | return nil 29 | } 30 | } 31 | 32 | public prefix func ..? (promise: Promise?) -> T? { 33 | guard let promise = promise else { return nil } 34 | do { 35 | return try awaitPromise(promise) 36 | } catch { 37 | return nil 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /Sources/Then/Await.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Await.swift 3 | // then 4 | // 5 | // Created by Sacha Durand Saint Omer on 13/03/2017. 6 | // Copyright © 2017 s4cha. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import Dispatch 11 | 12 | @available(*, deprecated, message: "Use `awaitPromise` instead, to avoid confusion & conflict with Swift standard library's `await`.") 13 | @discardableResult public func await(_ promise: Promise) throws -> T { 14 | return try awaitPromise(promise) 15 | } 16 | 17 | @discardableResult public func awaitPromise(_ promise: Promise) throws -> T { 18 | var result: T! 19 | var error: Error? 20 | let group = DispatchGroup() 21 | group.enter() 22 | promise.then { t in 23 | result = t 24 | group.leave() 25 | }.onError { e in 26 | error = e 27 | group.leave() 28 | } 29 | group.wait() 30 | if let e = error { 31 | throw e 32 | } 33 | return result 34 | } 35 | -------------------------------------------------------------------------------- /Sources/Then/Promise+Aliases.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Promise+Aliases.swift 3 | // then 4 | // 5 | // Created by Sacha Durand Saint Omer on 20/02/2017. 6 | // Copyright © 2017 s4cha. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public typealias EmptyPromise = Promise 12 | public typealias Async = Promise 13 | public typealias AsyncTask = Async 14 | -------------------------------------------------------------------------------- /Sources/Then/Promise+BridgeError.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Promise+BridgeError.swift 3 | // then 4 | // 5 | // Created by Sacha Durand Saint Omer on 24/02/2017. 6 | // Copyright © 2017 s4cha. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public extension Promise { 12 | 13 | func bridgeError(to myError: Error) -> Promise { 14 | let p = newLinkedPromise() 15 | syncStateWithCallBacks( 16 | success: p.fulfill, 17 | failure: { _ in 18 | p.reject(myError) 19 | }, 20 | progress: p.setProgress) 21 | return p 22 | } 23 | 24 | func bridgeError(_ errorType: Error, to myError: Error) -> Promise { 25 | let p = newLinkedPromise() 26 | syncStateWithCallBacks( 27 | success: p.fulfill, 28 | failure: { e in 29 | if e._code == errorType._code && e._domain == errorType._domain { 30 | p.reject(myError) 31 | } else { 32 | p.reject(e) 33 | } 34 | }, 35 | progress: p.setProgress) 36 | return p 37 | } 38 | 39 | func bridgeError(_ block:@escaping (Error) throws -> Void) -> Promise { 40 | let p = newLinkedPromise() 41 | syncStateWithCallBacks( 42 | success: p.fulfill, 43 | failure: { e in 44 | do { 45 | try block(e) 46 | } catch { 47 | p.reject(error) 48 | } 49 | }, 50 | progress: p.setProgress) 51 | return p 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /Sources/Then/Promise+Chain.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Promise+Chain.swift 3 | // then 4 | // 5 | // Created by Sacha Durand Saint Omer on 13/03/2017. 6 | // Copyright © 2017 s4cha. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public extension Promise { 12 | 13 | func chain(_ block:@escaping (T) -> Void) -> Promise { 14 | let p = newLinkedPromise() 15 | syncStateWithCallBacks(success: { t in 16 | block(t) 17 | p.fulfill(t) 18 | }, failure: p.reject, progress: p.setProgress) 19 | return p 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Sources/Then/Promise+Delay.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Promise+Delay.swift 3 | // then 4 | // 5 | // Created by Sacha Durand Saint Omer on 09/08/2017. 6 | // Copyright © 2017 s4cha. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import Dispatch 11 | 12 | extension Promise { 13 | 14 | public func delay(_ time: TimeInterval) -> Promise { 15 | let p = newLinkedPromise() 16 | syncStateWithCallBacks( 17 | success: { t in 18 | Promises.callBackOnCallingQueueIn(time: time) { 19 | p.fulfill(t) 20 | } 21 | }, 22 | failure: p.reject, 23 | progress: p.setProgress) 24 | return p 25 | } 26 | } 27 | 28 | extension Promises { 29 | public static func delay(_ time: TimeInterval) -> Promise { 30 | return Promise { (resolve: @escaping (() -> Void), _: @escaping ((Error) -> Void)) in 31 | callBackOnCallingQueueIn(time: time, block: resolve) 32 | } 33 | } 34 | } 35 | 36 | extension Promises { 37 | 38 | static func callBackOnCallingQueueIn(time: TimeInterval, block: @escaping () -> Void) { 39 | if let callingQueue = OperationQueue.current?.underlyingQueue { 40 | DispatchQueue.global(qos: DispatchQoS.QoSClass.userInitiated).asyncAfter(deadline: .now() + time) { 41 | callingQueue.async { 42 | block() 43 | } 44 | } 45 | } else { 46 | DispatchQueue.global(qos: .userInitiated).asyncAfter(deadline: .now() + time) { 47 | block() 48 | } 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /Sources/Then/Promise+Error.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Promise+Error.swift 3 | // then 4 | // 5 | // Created by Sacha Durand Saint Omer on 20/02/2017. 6 | // Copyright © 2017 s4cha. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public extension Promise { 12 | 13 | @discardableResult func onError(_ block: @escaping (Error) -> Void) -> Promise { 14 | tryStartInitialPromiseAndStartIfneeded() 15 | return registerOnError(block) 16 | } 17 | 18 | @discardableResult func registerOnError(_ block: @escaping (Error) -> Void) -> Promise { 19 | let p = Promise() 20 | passAlongFirstPromiseStartFunctionAndStateTo(p) 21 | syncStateWithCallBacks( 22 | success: { _ in 23 | p.fulfill(()) 24 | }, 25 | failure: { e in 26 | block(e) 27 | p.fulfill(()) 28 | }, 29 | progress: p.setProgress 30 | ) 31 | p.start() 32 | return p 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /Sources/Then/Promise+Finally.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Promise+Finally.swift 3 | // then 4 | // 5 | // Created by Sacha Durand Saint Omer on 20/02/2017. 6 | // Copyright © 2017 s4cha. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public extension Promise { 12 | 13 | func finally(_ block: @escaping () -> Void) { 14 | tryStartInitialPromiseAndStartIfneeded() 15 | registerFinally(block) 16 | } 17 | 18 | func registerFinally(_ block: @escaping () -> Void) { 19 | synchronize { state, blocks in 20 | switch state { 21 | case .rejected, .fulfilled: 22 | block() 23 | case .dormant, .pending: 24 | blocks.finally.append(block) 25 | } 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Sources/Then/Promise+First.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Promise+First.swift 3 | // then 4 | // 5 | // Created by Sacha DSO on 31/01/2018. 6 | // Copyright © 2018 s4cha. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | extension Promise { 12 | public func first() -> Promise where T == [E] { 13 | return self.then { unwrap($0.first) } 14 | } 15 | } 16 | 17 | extension Promise { 18 | public func last() -> Promise where T == [E] { 19 | return self.then { unwrap($0.last) } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Sources/Then/Promise+Helpers.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Promise+Helpers.swift 3 | // then 4 | // 5 | // Created by Sacha Durand Saint Omer on 20/02/2017. 6 | // Copyright © 2017 s4cha. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public extension Promise { 12 | class func reject(_ error: Error = PromiseError.default) -> Promise { 13 | return Promise { _, reject in reject(error) } 14 | } 15 | } 16 | 17 | public extension Promise { 18 | class func resolve(_ value: T) -> Promise { 19 | return Promise { resolve, _ in resolve(value) } 20 | } 21 | } 22 | 23 | extension Promise where T == Void { 24 | public class func resolve() -> Promise { 25 | return Promise { resolve, _ in resolve() } 26 | } 27 | } 28 | 29 | public extension Promise { 30 | 31 | var value: T? { 32 | return synchronize { state, _ in 33 | return state.value 34 | } 35 | } 36 | 37 | var error: Error? { 38 | return synchronize { state, _ in 39 | return state.error 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /Sources/Then/Promise+NoMatterWhat.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Promise+NoMatterWhat.swift 3 | // then 4 | // 5 | // Created by Sacha Durand Saint Omer on 24/02/2017. 6 | // Copyright © 2017 s4cha. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | extension Promise { 12 | 13 | public func noMatterWhat(_ block: @escaping () -> Void) -> Promise { 14 | let p = newLinkedPromise() 15 | syncStateWithCallBacks( 16 | success: { t in 17 | block() 18 | p.fulfill(t) 19 | }, 20 | failure: { e in 21 | block() 22 | p.reject(e) 23 | }, 24 | progress: p.setProgress) 25 | return p 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /Sources/Then/Promise+Progress.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Promise+Progress.swift 3 | // then 4 | // 5 | // Created by Sacha Durand Saint Omer on 20/02/2017. 6 | // Copyright © 2017 s4cha. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public extension Promise { 12 | 13 | @discardableResult func progress(_ block: @escaping (Float) -> Void) -> Promise { 14 | tryStartInitialPromiseAndStartIfneeded() 15 | let p = newLinkedPromise() 16 | syncStateWithCallBacks( 17 | success: p.fulfill, 18 | failure: p.reject, 19 | progress: { f in 20 | block(f) 21 | p.setProgress(f) 22 | } 23 | ) 24 | p.start() 25 | return p 26 | } 27 | 28 | internal func setProgress(_ value: Float) { 29 | updateState(PromiseState.pending(progress: value)) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /Sources/Then/Promise+Race.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Promise+Race.swift 3 | // then 4 | // 5 | // Created by Sacha Durand Saint Omer on 22/02/2017. 6 | // Copyright © 2017 s4cha. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | extension Promises { 12 | 13 | /// `Promise.race(p1, p2, p3, p4...)`Takes the state of the fastest returning promise. 14 | /// If the first fails, it fails. If the first resolves, it resolves. 15 | public static func race(_ promises: Promise...) -> Promise { 16 | return Promise { resolve, reject in 17 | for p in promises { 18 | p.then { t in 19 | resolve(t) 20 | }.onError { e in 21 | reject(e) 22 | } 23 | } 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Sources/Then/Promise+Recover.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Promise+Recover.swift 3 | // then 4 | // 5 | // Created by Sacha Durand Saint Omer on 22/02/2017. 6 | // Copyright © 2017 s4cha. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | extension Promise { 12 | 13 | public func recover(with value: T) -> Promise { 14 | let p = newLinkedPromise() 15 | syncStateWithCallBacks( 16 | success: p.fulfill, 17 | failure: { _ in 18 | p.fulfill(value) 19 | }, progress: p.setProgress) 20 | return p 21 | } 22 | 23 | public func recover(_ errorType: E, with value: T) -> Promise { 24 | let p = newLinkedPromise() 25 | syncStateWithCallBacks( 26 | success: p.fulfill, 27 | failure: { e in 28 | if errorMatchesExpectedError(e, expectedError: errorType) { 29 | p.fulfill(value) 30 | } else { 31 | p.reject(e) 32 | } 33 | }, 34 | progress: p.setProgress) 35 | return p 36 | } 37 | 38 | public func recover(_ errorType: E, with value: T) -> Promise where E: Equatable { 39 | let p = newLinkedPromise() 40 | syncStateWithCallBacks( 41 | success: p.fulfill, 42 | failure: { e in 43 | if errorMatchesExpectedError(e, expectedError: errorType) { 44 | p.fulfill(value) 45 | } else { 46 | p.reject(e) 47 | } 48 | }, 49 | progress: p.setProgress) 50 | 51 | return p 52 | } 53 | 54 | public func recover(with promise: Promise) -> Promise { 55 | let p = newLinkedPromise() 56 | syncStateWithCallBacks( 57 | success: p.fulfill, 58 | failure: { _ in 59 | promise.then { t in 60 | p.fulfill(t) 61 | }.onError { error in 62 | p.reject(error) 63 | } 64 | }, 65 | progress: p.setProgress) 66 | return p 67 | } 68 | 69 | public func recover(_ block:@escaping (Error) throws -> T) -> Promise { 70 | let p = newLinkedPromise() 71 | syncStateWithCallBacks( 72 | success: p.fulfill, 73 | failure: { e in 74 | do { 75 | let v = try block(e) 76 | p.fulfill(v) 77 | } catch { 78 | p.reject(error) 79 | } 80 | }, progress: p.setProgress) 81 | return p 82 | } 83 | 84 | public func recover(_ block:@escaping (Error) throws -> Promise) -> Promise { 85 | let p = newLinkedPromise() 86 | syncStateWithCallBacks( 87 | success: p.fulfill, 88 | failure: { e in 89 | do { 90 | let promise = try block(e) 91 | promise.then { t in 92 | p.fulfill(t) 93 | }.onError { error in 94 | p.reject(error) 95 | } 96 | } catch { 97 | p.reject(error) 98 | } 99 | }, progress: p.setProgress) 100 | return p 101 | } 102 | 103 | } 104 | 105 | // Credits to Quick/Nimble for how to compare Errors 106 | // https://github.com/Quick/Nimble/blob/db706fc1d7130f6ac96c56aaf0e635fa3217fe57/Sources/ 107 | // Nimble/Utils/Errors.swift#L37-L53 108 | private func errorMatchesExpectedError(_ error: Error, expectedError: T) -> Bool { 109 | return error._domain == expectedError._domain && error._code == expectedError._code 110 | } 111 | 112 | private func errorMatchesExpectedError(_ error: Error, 113 | expectedError: T) -> Bool where T: Equatable { 114 | if let error = error as? T { 115 | return error == expectedError 116 | } 117 | return false 118 | } 119 | -------------------------------------------------------------------------------- /Sources/Then/Promise+Retry.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Promise+Retry.swift 3 | // then 4 | // 5 | // Created by Sacha Durand Saint Omer on 22/02/2017. 6 | // Copyright © 2017 s4cha. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | extension Promise { 12 | public func retry(_ nbOfTimes: UInt) -> Promise { 13 | guard nbOfTimes > 0 else { 14 | return Promise.reject(PromiseError.retryInvalidInput) 15 | } 16 | let p = newLinkedPromise() 17 | self.numberOfRetries = nbOfTimes 18 | self.syncStateWithCallBacks( 19 | success: { [weak self] t in 20 | self?.numberOfRetries = 0 21 | p.fulfill(t) 22 | }, 23 | failure: { [weak self] e in 24 | self?.numberOfRetries -= 1 25 | if self?.numberOfRetries == 0 { 26 | p.reject(e) 27 | } else { 28 | self?.resetState() 29 | self?.start() 30 | } 31 | }, 32 | progress: p.setProgress) 33 | return p 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Sources/Then/Promise+Then.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Promise+Then.swift 3 | // then 4 | // 5 | // Created by Sacha Durand Saint Omer on 20/02/2017. 6 | // Copyright © 2017 s4cha. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public extension Promise { 12 | 13 | @discardableResult func then(_ block: @escaping (T) -> X) -> Promise { 14 | let p = registerThen(block) 15 | tryStartInitialPromiseAndStartIfneeded() 16 | return p 17 | } 18 | 19 | @discardableResult func registerThen(_ block: @escaping (T) -> X) -> Promise { 20 | let p = Promise() 21 | 22 | synchronize { state, blocks in 23 | switch state { 24 | case let .fulfilled(value): 25 | let x: X = block(value) 26 | p.fulfill(x) 27 | case let .rejected(error): 28 | p.reject(error) 29 | case .dormant, .pending: 30 | blocks.success.append({ t in 31 | p.fulfill(block(t)) 32 | }) 33 | blocks.fail.append({ e in 34 | p.reject(e) 35 | }) 36 | blocks.progress.append({ f in 37 | p.setProgress(f) 38 | }) 39 | } 40 | } 41 | passAlongFirstPromiseStartFunctionAndStateTo(p) 42 | return p 43 | } 44 | 45 | @discardableResult func then(_ block: @escaping (T) -> Promise) -> Promise { 46 | tryStartInitialPromiseAndStartIfneeded() 47 | return registerThen(block) 48 | } 49 | 50 | @discardableResult func registerThen(_ block: @escaping (T) -> Promise) 51 | -> Promise { 52 | let p = Promise() 53 | 54 | synchronize { state, blocks in 55 | switch state { 56 | case let .fulfilled(value): 57 | registerNextPromise(block, result: value, 58 | resolve: p.fulfill, reject: p.reject) 59 | case let .rejected(error): 60 | p.reject(error) 61 | case .dormant, .pending: 62 | blocks.success.append({ [weak self] t in 63 | self?.registerNextPromise(block, result: t, resolve: p.fulfill, 64 | reject: p.reject) 65 | }) 66 | blocks.fail.append(p.reject) 67 | blocks.progress.append(p.setProgress) 68 | } 69 | } 70 | p.start() 71 | passAlongFirstPromiseStartFunctionAndStateTo(p) 72 | return p 73 | } 74 | 75 | @discardableResult func then(_ promise: Promise) -> Promise { 76 | return then { _ in promise } 77 | } 78 | 79 | @discardableResult func registerThen(_ promise: Promise) -> Promise { 80 | return registerThen { _ in promise } 81 | } 82 | 83 | fileprivate func registerNextPromise(_ block: (T) -> Promise, 84 | result: T, 85 | resolve: @escaping (X) -> Void, 86 | reject: @escaping ((Error) -> Void)) { 87 | let nextPromise: Promise = block(result) 88 | nextPromise.then { x in 89 | resolve(x) 90 | }.onError(reject) 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /Sources/Then/Promise+Timeout.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Promise+Timeout.swift 3 | // then 4 | // 5 | // Created by Sacha Durand Saint Omer on 10/08/2017. 6 | // Copyright © 2017 s4cha. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | extension Promise { 12 | 13 | public func timeout(_ time: TimeInterval) -> Promise { 14 | let timer: Promise = Promises.delay(time).then { 15 | return Promise.reject(PromiseError.timeout) 16 | } 17 | return Promises.race(timer, self) 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Sources/Then/Promise+Unwrap.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Promise+Unwrap.swift 3 | // then 4 | // 5 | // Created by Sacha Durand Saint Omer on 18/03/2017. 6 | // Copyright © 2017 s4cha. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public func unwrap(_ param: T?) -> Promise { 12 | if let param = param { 13 | return Promise.resolve(param) 14 | } else { 15 | return Promise.reject(PromiseError.unwrappingFailed) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Sources/Then/Promise+Validate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Promise+Validate.swift 3 | // then 4 | // 5 | // Created by Sacha Durand Saint Omer on 22/02/2017. 6 | // Copyright © 2017 s4cha. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | extension Promise { 12 | 13 | @discardableResult 14 | public func validate(withError: Error = PromiseError.validationFailed, 15 | _ assertionBlock:@escaping ((T) -> Bool)) -> Promise { 16 | let p = newLinkedPromise() 17 | syncStateWithCallBacks( 18 | success: { t in 19 | if assertionBlock(t) { 20 | p.fulfill(t) 21 | } else { 22 | p.reject(withError) 23 | } 24 | }, 25 | failure: p.reject, 26 | progress: p.setProgress) 27 | return p 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Sources/Then/Promise+Zip.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Promise+Zip.swift 3 | // then 4 | // 5 | // Created by Sacha Durand Saint Omer on 10/08/2017. 6 | // Copyright © 2017 s4cha. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import Dispatch 11 | 12 | extension Promises { 13 | 14 | public static func zip(_ p1: Promise, _ p2: Promise) -> Promise<(T, U)> { 15 | 16 | let p = Promise<(T, U)>() 17 | var t: T! 18 | var u: U! 19 | var error: Error? 20 | let group = DispatchGroup() 21 | 22 | // We run the promises concurrently on a concurent queue and go back 23 | // to a local queue to read/modify global variables. 24 | // .barrier blocks concurrency so that we can write values 25 | // without then beeing read at the same time. 26 | // It pauses reads until write are done 27 | let concurentQueue = DispatchQueue(label: "then.zip.concurrent", attributes: .concurrent) 28 | let localQueue = DispatchQueue(label: "then.zip.local", attributes: .concurrent) 29 | 30 | group.enter() 31 | concurentQueue.async { 32 | p1.then { aT in 33 | localQueue.async(flags: .barrier) { 34 | t = aT 35 | } 36 | }.onError { e in 37 | localQueue.async(flags: .barrier) { 38 | error = e 39 | } 40 | }.finally { 41 | localQueue.async { // barrier needed? 42 | group.leave() 43 | } 44 | } 45 | } 46 | 47 | group.enter() 48 | concurentQueue.async { 49 | p2.then { aU in 50 | localQueue.async(flags: .barrier) { 51 | u = aU 52 | } 53 | }.onError { e in 54 | localQueue.async(flags: .barrier) { 55 | error = e 56 | } 57 | }.finally { 58 | localQueue.async { 59 | group.leave() 60 | } 61 | } 62 | } 63 | 64 | let callingQueue = OperationQueue.current?.underlyingQueue 65 | let queue = callingQueue ?? DispatchQueue.main 66 | group.notify(queue: queue) { 67 | localQueue.async { 68 | if let e = error { 69 | p.reject(e) 70 | } else { 71 | p.fulfill((t, u)) 72 | } 73 | } 74 | } 75 | return p 76 | } 77 | 78 | // zip 3 79 | public static func zip(_ p1: Promise, _ p2: Promise, _ p3: Promise) -> Promise<(T, U, V)> { 80 | return zip(zip(p1, p2), p3).then { ($0.0, $0.1, $1) } 81 | } 82 | 83 | // zip 4 84 | public static func zip(_ p1: Promise, 85 | _ p2: Promise, 86 | _ p3: Promise, 87 | _ p4: Promise) -> Promise<(A, B, C, D)> { 88 | return zip(zip(p1, p2, p3), p4).then { ($0.0, $0.1, $0.2, $1) } 89 | } 90 | 91 | // zip 5 92 | public static func zip(_ p1: Promise, 93 | _ p2: Promise, 94 | _ p3: Promise, 95 | _ p4: Promise, 96 | _ p5: Promise) -> Promise<(A, B, C, D, E)> { 97 | return zip(zip(p1, p2, p3, p4), p5).then { ($0.0, $0.1, $0.2, $0.3, $1) } 98 | } 99 | 100 | // zip 6 swiftlint:disable function_parameter_count 101 | public static func zip(_ p1: Promise, 102 | _ p2: Promise, 103 | _ p3: Promise, 104 | _ p4: Promise, 105 | _ p5: Promise, 106 | _ p6: Promise) -> Promise<(A, B, C, D, E, F)> { 107 | return zip(zip(p1, p2, p3, p4, p5), p6 ).then { ($0.0, $0.1, $0.2, $0.3, $0.4, $1) } 108 | } 109 | 110 | // zip 7 111 | public static func zip(_ p1: Promise, 112 | _ p2: Promise, 113 | _ p3: Promise, 114 | _ p4: Promise, 115 | _ p5: Promise, 116 | _ p6: Promise, 117 | _ p7: Promise) -> Promise<(A, B, C, D, E, F, G)> { 118 | return zip(zip(p1, p2, p3, p4, p5, p6), p7).then { ($0.0, $0.1, $0.2, $0.3, $0.4, $0.5, $1) } 119 | } 120 | 121 | // zip 8 122 | public static func zip(_ p1: Promise, 123 | _ p2: Promise, 124 | _ p3: Promise, 125 | _ p4: Promise, 126 | _ p5: Promise, 127 | _ p6: Promise, 128 | _ p7: Promise, 129 | _ p8: Promise) -> Promise<(A, B, C, D, E, F, G, H)> { 130 | return zip(zip(p1, p2, p3, p4, p5, p6, p7), p8).then { ($0.0, $0.1, $0.2, $0.3, $0.4, $0.5, $0.6, $1) } 131 | } 132 | // swiftlint:enable function_parameter_count 133 | } 134 | -------------------------------------------------------------------------------- /Sources/Then/Promise+nil.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Promise+nil.swift 3 | // then 4 | // 5 | // Created by Sacha DSO on 31/01/2018. 6 | // Copyright © 2018 s4cha. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | extension Promise { 12 | public func convertErrorToNil() -> Promise { 13 | return Promise { resolve, _ in 14 | self.then { t in 15 | resolve(t) 16 | }.onError { _ in 17 | resolve(nil) 18 | } 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Sources/Then/Promise.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Promise.swift 3 | // then 4 | // 5 | // Created by Sacha Durand Saint Omer on 06/02/16. 6 | // Copyright © 2016 s4cha. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import Dispatch 11 | 12 | private class Locker { 13 | let lockQueueSpecificKey: DispatchSpecificKey 14 | let lockQueue: DispatchQueue 15 | init() { 16 | lockQueueSpecificKey = DispatchSpecificKey() 17 | lockQueue = DispatchQueue(label: "com.freshOS.then.lockQueue", qos: .userInitiated) 18 | lockQueue.setSpecific(key: lockQueueSpecificKey, value: ()) 19 | } 20 | 21 | var isOnLockQueue: Bool { 22 | return DispatchQueue.getSpecific(key: lockQueueSpecificKey) != nil 23 | } 24 | } 25 | 26 | public class Promise { 27 | 28 | // MARK: - Protected properties 29 | 30 | internal var numberOfRetries: UInt = 0 31 | 32 | private var threadUnsafeState: PromiseState 33 | 34 | private var threadUnsafeBlocks: PromiseBlocks = PromiseBlocks() 35 | 36 | private var initialPromiseStart:(() -> Void)? 37 | private var initialPromiseStarted = false 38 | 39 | internal typealias ProgressCallBack = (_ resolve: @escaping ((T) -> Void), 40 | _ reject: @escaping ((Error) -> Void), 41 | _ progress: @escaping ((Float) -> Void)) -> Void 42 | 43 | private var promiseProgressCallBack: ProgressCallBack? 44 | 45 | // MARK: - Lock 46 | 47 | private let locker = Locker() 48 | private var lockQueue: DispatchQueue { 49 | return locker.lockQueue 50 | } 51 | private func _synchronize(_ action: () -> U) -> U { 52 | if locker.isOnLockQueue { 53 | return action() 54 | } else { 55 | return lockQueue.sync(execute: action) 56 | } 57 | } 58 | 59 | private func _asynchronize(_ action: @escaping () -> Void) { 60 | lockQueue.async(execute: action) 61 | } 62 | 63 | // MARK: - Intializers 64 | 65 | public init() { 66 | threadUnsafeState = .dormant 67 | } 68 | 69 | public init(_ value: T) { 70 | threadUnsafeState = .fulfilled(value: value) 71 | } 72 | 73 | public init(error: Error) { 74 | threadUnsafeState = PromiseState.rejected(error: error) 75 | } 76 | 77 | public convenience init(callback: @escaping ( 78 | _ resolve: @escaping ((T) -> Void), 79 | _ reject: @escaping ((Error) -> Void)) -> Void) { 80 | self.init() 81 | promiseProgressCallBack = { resolve, reject, progress in 82 | callback(resolve, reject) 83 | } 84 | } 85 | 86 | public convenience init(callback: @escaping ( 87 | _ resolve: @escaping ((T) -> Void), 88 | _ reject: @escaping ((Error) -> Void), 89 | _ progress: @escaping ((Float) -> Void)) -> Void) { 90 | self.init() 91 | promiseProgressCallBack = { resolve, reject, progress in 92 | callback(resolve, reject, progress) 93 | } 94 | } 95 | 96 | // MARK: - Private atomic operations 97 | 98 | private func _updateFirstPromiseStartFunctionAndState(from startBody: @escaping () -> Void, isStarted: Bool) { 99 | _synchronize { 100 | initialPromiseStart = startBody 101 | initialPromiseStarted = isStarted 102 | } 103 | } 104 | 105 | // MARK: - Public interfaces 106 | 107 | public func start() { 108 | _synchronize({ return _start() })?() 109 | } 110 | 111 | public func fulfill(_ value: T) { 112 | _synchronize({ () -> (() -> Void)? in 113 | let action = _updateState(.fulfilled(value: value)) 114 | threadUnsafeBlocks = .init() 115 | promiseProgressCallBack = nil 116 | return action 117 | })?() 118 | } 119 | 120 | public func reject(_ anError: Error) { 121 | _synchronize({ () -> (() -> Void)? in 122 | let action = _updateState(.rejected(error: anError)) 123 | // Only release callbacks if no retries a registered. 124 | if numberOfRetries == 0 { 125 | threadUnsafeBlocks = .init() 126 | promiseProgressCallBack = nil 127 | } 128 | return action 129 | })?() 130 | } 131 | 132 | // MARK: - Internal interfaces 133 | 134 | internal func synchronize( 135 | _ action: (_ currentState: PromiseState, _ blocks: inout PromiseBlocks) -> U) -> U { 136 | return _synchronize { 137 | return action(threadUnsafeState, &threadUnsafeBlocks) 138 | } 139 | } 140 | 141 | internal func resetState() { 142 | _synchronize { 143 | threadUnsafeState = .dormant 144 | } 145 | } 146 | 147 | internal func passAlongFirstPromiseStartFunctionAndStateTo(_ promise: Promise) { 148 | let (startBlock, isStarted) = _synchronize { 149 | return (self.initialPromiseStart ?? self.start, self.initialPromiseStarted) 150 | } 151 | promise._updateFirstPromiseStartFunctionAndState(from: startBlock, isStarted: isStarted) 152 | } 153 | 154 | internal func tryStartInitialPromiseAndStartIfneeded() { 155 | var actions: [(() -> Void)?] = [] 156 | _synchronize { 157 | actions = [ 158 | _startInitialPromiseIfNeeded(), 159 | _start() 160 | ] 161 | } 162 | actions.forEach { $0?() } 163 | } 164 | 165 | internal func updateState(_ newState: PromiseState) { 166 | _synchronize({ return _updateState(newState) })?() 167 | } 168 | 169 | internal func setProgressCallBack(_ promiseProgressCallBack: @escaping ProgressCallBack) { 170 | _synchronize { 171 | self.promiseProgressCallBack = promiseProgressCallBack 172 | } 173 | } 174 | 175 | internal func newLinkedPromise() -> Promise { 176 | let p = Promise() 177 | passAlongFirstPromiseStartFunctionAndStateTo(p) 178 | return p 179 | } 180 | 181 | internal func syncStateWithCallBacks(success: @escaping ((T) -> Void), 182 | failure: @escaping ((Error) -> Void), 183 | progress: @escaping ((Float) -> Void)) { 184 | _synchronize { 185 | switch threadUnsafeState { 186 | case let .fulfilled(value): 187 | success(value) 188 | case let .rejected(error): 189 | failure(error) 190 | case .dormant, .pending: 191 | threadUnsafeBlocks.success.append(success) 192 | threadUnsafeBlocks.fail.append(failure) 193 | threadUnsafeBlocks.progress.append(progress) 194 | } 195 | } 196 | } 197 | 198 | // MARK: - Private non-atomic operations 199 | 200 | private func _startInitialPromiseIfNeeded() -> (() -> Void)? { 201 | guard !initialPromiseStarted else { return nil } 202 | initialPromiseStarted = true 203 | let body = self.initialPromiseStart 204 | return body 205 | } 206 | 207 | private func _start() -> (() -> Void)? { 208 | guard threadUnsafeState.isDormant else { return nil } 209 | 210 | let updateAction = _updateState(.pending(progress: 0)) 211 | guard let p = promiseProgressCallBack else { return updateAction } 212 | return { 213 | updateAction?() 214 | p(self.fulfill, self.reject, self.setProgress) 215 | } 216 | // promiseProgressCallBack = nil //Remove callba 217 | } 218 | 219 | private func _updateState(_ newState: PromiseState) -> (() -> Void)? { 220 | if threadUnsafeState.isPendingOrDormant { 221 | threadUnsafeState = newState 222 | } 223 | return launchCallbacksIfNeeded() 224 | } 225 | 226 | private func launchCallbacksIfNeeded() -> (() -> Void)? { 227 | switch threadUnsafeState { 228 | case .dormant: 229 | return nil 230 | case .pending(let progress): 231 | if progress != 0 { 232 | return threadUnsafeBlocks.updateProgress(progress) 233 | } else { 234 | return nil 235 | } 236 | case .fulfilled(let value): 237 | initialPromiseStart = nil 238 | return threadUnsafeBlocks.fulfill(value: value) 239 | case .rejected(let anError): 240 | initialPromiseStart = nil 241 | return threadUnsafeBlocks.reject(error: anError) 242 | } 243 | } 244 | } 245 | 246 | // MARK: - Helpers 247 | extension Promise { 248 | 249 | var isStarted: Bool { 250 | return synchronize { state, _ in 251 | switch state { 252 | case .dormant: 253 | return false 254 | default: 255 | return true 256 | } 257 | } 258 | } 259 | } 260 | -------------------------------------------------------------------------------- /Sources/Then/PromiseBlocks.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PromiseBlocks.swift 3 | // then 4 | // 5 | // Created by Sacha Durand Saint Omer on 26/10/16. 6 | // Copyright © 2016 s4cha. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | struct PromiseBlocks { 12 | 13 | typealias SuccessBlock = (T) -> Void 14 | typealias FailBlock = (Error) -> Void 15 | typealias ProgressBlock = (Float) -> Void 16 | typealias FinallyBlock = () -> Void 17 | 18 | var success = [SuccessBlock]() 19 | var fail = [FailBlock]() 20 | var progress = [ProgressBlock]() 21 | var finally = [FinallyBlock]() 22 | } 23 | 24 | extension PromiseBlocks { 25 | 26 | func updateProgress(_ progress: Float) -> () -> Void { 27 | let progressBlocks = self.progress 28 | return { 29 | progressBlocks.forEach { $0(progress) } 30 | } 31 | } 32 | 33 | func fulfill(value: T) -> () -> Void { 34 | let successBlocks = self.success 35 | let finallyBlocks = self.finally 36 | return { 37 | successBlocks.forEach { $0(value) } 38 | finallyBlocks.forEach { $0() } 39 | } 40 | } 41 | 42 | func reject(error: Error) -> () -> Void { 43 | let failureBlocks = self.fail 44 | let finallyBlocks = self.finally 45 | return { 46 | failureBlocks.forEach { $0(error) } 47 | finallyBlocks.forEach { $0() } 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /Sources/Then/PromiseError.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PromiseError.swift 3 | // then 4 | // 5 | // Created by Sacha Durand Saint Omer on 23/02/2017. 6 | // Copyright © 2017 s4cha. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public enum PromiseError: Error { 12 | case `default` 13 | case validationFailed 14 | case retryInvalidInput 15 | case unwrappingFailed 16 | case timeout 17 | } 18 | 19 | extension PromiseError: Equatable { } 20 | 21 | public func == (lhs: PromiseError, rhs: PromiseError) -> Bool { 22 | switch (lhs, rhs) { 23 | case (.default, .default): 24 | return true 25 | case (.validationFailed, .validationFailed): 26 | return true 27 | case (.retryInvalidInput, .retryInvalidInput): 28 | return true 29 | case (.unwrappingFailed, .unwrappingFailed): 30 | return true 31 | default: 32 | return false 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /Sources/Then/PromiseState.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PromiseState.swift 3 | // then 4 | // 5 | // Created by Sacha Durand Saint Omer on 08/08/16. 6 | // Copyright © 2016 s4cha. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public enum PromiseState { 12 | case dormant 13 | case pending(progress: Float) 14 | case fulfilled(value: T) 15 | case rejected(error: Error) 16 | } 17 | 18 | extension PromiseState { 19 | 20 | var value: T? { 21 | if case let .fulfilled(value) = self { 22 | return value 23 | } 24 | return nil 25 | } 26 | 27 | var error: Error? { 28 | if case let .rejected(error) = self { 29 | return error 30 | } 31 | return nil 32 | } 33 | 34 | var isDormant: Bool { 35 | if case .dormant = self { 36 | return true 37 | } 38 | return false 39 | } 40 | 41 | var isPendingOrDormant: Bool { 42 | return !isFulfilled && !isRejected 43 | } 44 | 45 | var isFulfilled: Bool { 46 | if case .fulfilled = self { 47 | return true 48 | } 49 | return false 50 | } 51 | 52 | var isRejected: Bool { 53 | if case .rejected = self { 54 | return true 55 | } 56 | return false 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /Sources/Then/VoidPromise.swift: -------------------------------------------------------------------------------- 1 | // 2 | // VoidPromise.swift 3 | // then 4 | // 5 | // Created by Sacha DSO on 27/09/2017. 6 | // Copyright © 2017 s4cha. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | extension Promise where T == Void { 12 | 13 | public convenience init(callback: @escaping ( 14 | _ resolve: @escaping (() -> Void), 15 | _ reject: @escaping ((Error) -> Void)) -> Void) { 16 | self.init() 17 | setProgressCallBack { resolve, reject, _ in 18 | let wrapped = { resolve(()) } 19 | callback(wrapped, reject) 20 | } 21 | } 22 | 23 | public convenience init(callback2: @escaping ( 24 | _ resolve: @escaping (() -> Void), 25 | _ reject: @escaping ((Error) -> Void), 26 | _ progress: @escaping ((Float) -> Void)) -> Void) { 27 | self.init() 28 | setProgressCallBack { resolve, reject, progress in 29 | let wrapped = { resolve(()) } 30 | callback2(wrapped, reject, progress) 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /Sources/Then/WhenAll.swift: -------------------------------------------------------------------------------- 1 | // 2 | // WhenAll.swift 3 | // then 4 | // 5 | // Created by Sacha Durand Saint Omer on 08/04/16. 6 | // Copyright © 2016 s4cha. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import Dispatch 11 | 12 | public class Promises {} 13 | 14 | extension Promises { 15 | 16 | public static func whenAll(_ promises: [Promise], callbackQueue: DispatchQueue? = nil) -> Promise<[T]> { 17 | return reduceWhenAll(promises, callbackQueue: callbackQueue) { (result, element) in 18 | result.append(element) 19 | } 20 | } 21 | 22 | public static func whenAll(_ promises: Promise..., callbackQueue: DispatchQueue? = nil) -> Promise<[T]> { 23 | return whenAll(promises, callbackQueue: callbackQueue) 24 | } 25 | 26 | public static func lazyWhenAll(_ promises: [Promise], callbackQueue: DispatchQueue? = nil) -> Promise<[T]> { 27 | return lazyReduceWhenAll(promises, callbackQueue: callbackQueue) { (result, element) in 28 | result.append(element) 29 | } 30 | } 31 | 32 | public static func lazyWhenAll(_ promises: Promise..., callbackQueue: DispatchQueue? = nil) -> Promise<[T]> { 33 | return lazyWhenAll(promises, callbackQueue: callbackQueue) 34 | } 35 | 36 | // Array version 37 | 38 | public static func whenAll(_ promises: [Promise<[T]>], callbackQueue: DispatchQueue? = nil) -> Promise<[T]> { 39 | return reduceWhenAll(promises, callbackQueue: callbackQueue) { (result, element) in 40 | result.append(contentsOf: element) 41 | } 42 | } 43 | 44 | public static func whenAll(_ promises: Promise<[T]>..., callbackQueue: DispatchQueue? = nil) -> Promise<[T]> { 45 | return whenAll(promises, callbackQueue: callbackQueue) 46 | } 47 | 48 | public static func lazyWhenAll(_ promises: [Promise<[T]>], callbackQueue: DispatchQueue? = nil) -> Promise<[T]> { 49 | return lazyReduceWhenAll(promises, callbackQueue: callbackQueue) { (result, element) in 50 | result.append(contentsOf: element) 51 | } 52 | } 53 | 54 | public static func lazyWhenAll( 55 | _ promises: Promise<[T]>..., 56 | callbackQueue: DispatchQueue? = nil) -> Promise<[T]> { 57 | return lazyWhenAll(promises, callbackQueue: callbackQueue) 58 | } 59 | 60 | // Private implementations 61 | 62 | private static func lazyReduceWhenAll( 63 | _ promises: [Promise], 64 | callbackQueue: DispatchQueue?, 65 | updatePartialResult: @escaping (_ result: inout [Result], _ element: Source) -> Void) -> Promise<[Result]> { 66 | return Promise { fulfill, reject in 67 | reducePromises( 68 | promises, 69 | callbackQueue: callbackQueue, 70 | fulfill: fulfill, 71 | reject: reject, 72 | updatePartialResult: updatePartialResult) 73 | } 74 | } 75 | 76 | private static func reduceWhenAll( 77 | _ promises: [Promise], 78 | callbackQueue: DispatchQueue?, 79 | updatePartialResult: @escaping (_ result: inout [Result], _ element: Source) -> Void) -> Promise<[Result]> { 80 | 81 | let p = Promise<[Result]>() 82 | reducePromises( 83 | promises, 84 | callbackQueue: callbackQueue, 85 | fulfill: p.fulfill, 86 | reject: p.reject, 87 | updatePartialResult: updatePartialResult) 88 | return p 89 | } 90 | 91 | private static func reducePromises( 92 | _ promises: [Promise], 93 | callbackQueue: DispatchQueue?, 94 | fulfill: @escaping ([Result]) -> Void, 95 | reject: @escaping (Error) -> Void, 96 | updatePartialResult: @escaping (_ result: inout [Result], _ element: Source) -> Void) { 97 | 98 | let ts = ArrayContainer() 99 | var error: Error? 100 | let group = DispatchGroup() 101 | for p in promises { 102 | group.enter() 103 | p.then { element in 104 | ts.updateArray({ updatePartialResult(&$0, element) }) 105 | } 106 | .onError { error = $0 } 107 | .finally { group.leave() } 108 | } 109 | let callingQueue = OperationQueue.current?.underlyingQueue 110 | let queue = callbackQueue ?? callingQueue ?? DispatchQueue.main 111 | group.notify(queue: queue) { 112 | if let e = error { 113 | reject(e) 114 | } else { 115 | fulfill(ts.array) 116 | } 117 | } 118 | } 119 | 120 | private class ArrayContainer { 121 | private var _array: [T] = [] 122 | private let lockQueue = DispatchQueue(label: "com.freshOS.then.whenAll.lockQueue", qos: .userInitiated) 123 | 124 | func updateArray(_ updates: @escaping (_ result: inout [T]) -> Void) { 125 | lockQueue.async { 126 | updates(&self._array) 127 | } 128 | } 129 | 130 | var array: [T] { 131 | return lockQueue.sync { 132 | _array 133 | } 134 | } 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /Tests/ThenTests/AsyncAwaitTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AsyncAwaitTests.swift 3 | // then 4 | // 5 | // Created by Sacha Durand Saint Omer on 13/03/2017. 6 | // Copyright © 2017 s4cha. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | import Then 11 | 12 | class AsyncAwaitTests: XCTestCase { 13 | 14 | func testAsyncAwaitChainWorks() { 15 | let exp = expectation(description: "") 16 | async { 17 | let userId = try awaitPromise(fetchUserId()) 18 | XCTAssertEqual(userId, 1234) 19 | let userName = try awaitPromise(fetchUserNameFromId(userId)) 20 | XCTAssertEqual(userName, "John Smith") 21 | let isFollowed = try awaitPromise(fetchUserFollowStatusFromName(userName)) 22 | XCTAssertFalse(isFollowed) 23 | exp.fulfill() 24 | } 25 | waitForExpectations(timeout: 0.5, handler: nil) 26 | } 27 | 28 | func testFailingAsyncAwait() { 29 | let exp = expectation(description: "") 30 | async { 31 | _ = try awaitPromise(failingFetchUserFollowStatusFromName("JohnDoe")) 32 | XCTFail("testFailingAsyncAwait failed") 33 | }.onError { _ in 34 | exp.fulfill() 35 | } 36 | waitForExpectations(timeout: 0.5, handler: nil) 37 | } 38 | 39 | func testCatchFailingAsyncAwait() { 40 | let exp = expectation(description: "") 41 | do { 42 | _ = try awaitPromise(failingFetchUserFollowStatusFromName("JohnDoe")) 43 | XCTFail("testCatchFailingAsyncAwait failed") 44 | } catch { 45 | exp.fulfill() 46 | } 47 | waitForExpectations(timeout: 0.3, handler: nil) 48 | } 49 | 50 | func testAsyncAwaitUnwrapAtYourOwnRisk() { 51 | let exp = expectation(description: "") 52 | let userId = try! awaitPromise(fetchUserId()) 53 | XCTAssertEqual(userId, 1234) 54 | let userName = try! awaitPromise(fetchUserNameFromId(userId)) 55 | XCTAssertEqual(userName, "John Smith") 56 | let isFollowed = try! awaitPromise(fetchUserFollowStatusFromName(userName)) 57 | XCTAssertFalse(isFollowed) 58 | exp.fulfill() 59 | waitForExpectations(timeout: 0.3, handler: nil) 60 | } 61 | 62 | func testAsyncBlockCanReturnAValue() { 63 | let exp = expectation(description: "") 64 | async { () -> Int in 65 | let userId = try awaitPromise(fetchUserId()) 66 | return userId 67 | }.then { userId in 68 | XCTAssertEqual(userId, 1234) 69 | exp.fulfill() 70 | } 71 | waitForExpectations(timeout: 0.3, handler: nil) 72 | } 73 | 74 | /// Operator .. 75 | 76 | func testAsyncAwaitChainWorksOperator() { 77 | let exp = expectation(description: "") 78 | async { 79 | let userId = try ..fetchUserId() 80 | XCTAssertEqual(userId, 1234) 81 | let userName = try ..fetchUserNameFromId(userId) 82 | XCTAssertEqual(userName, "John Smith") 83 | let isFollowed = try ..fetchUserFollowStatusFromName(userName) 84 | XCTAssertFalse(isFollowed) 85 | exp.fulfill() 86 | } 87 | waitForExpectations(timeout: 0.5, handler: nil) 88 | } 89 | 90 | func testFailingAsyncAwaitOperator() { 91 | let exp = expectation(description: "") 92 | async { 93 | _ = try ..failingFetchUserFollowStatusFromName("JohnDoe") 94 | XCTFail("testFailingAsyncAwait failed") 95 | }.onError { _ in 96 | exp.fulfill() 97 | } 98 | waitForExpectations(timeout: 0.5, handler: nil) 99 | } 100 | 101 | func testCatchFailingAsyncAwaitOperator() { 102 | let exp = expectation(description: "") 103 | do { 104 | _ = try ..failingFetchUserFollowStatusFromName("JohnDoe") 105 | XCTFail("testCatchFailingAsyncAwait failed") 106 | } catch { 107 | exp.fulfill() 108 | } 109 | waitForExpectations(timeout: 0.3, handler: nil) 110 | } 111 | 112 | func testAsyncAwaitUnwrapAtYourOwnRiskOperator() { 113 | let exp = expectation(description: "") 114 | let userId = try! ..fetchUserId() 115 | XCTAssertEqual(userId, 1234) 116 | let userName = try! ..fetchUserNameFromId(userId) 117 | XCTAssertEqual(userName, "John Smith") 118 | let isFollowed = try! ..fetchUserFollowStatusFromName(userName) 119 | XCTAssertFalse(isFollowed) 120 | exp.fulfill() 121 | waitForExpectations(timeout: 0.3, handler: nil) 122 | } 123 | 124 | func testAsyncBlockCanReturnAValueOperator() { 125 | let exp = expectation(description: "") 126 | async { () -> Int in 127 | let userId = try ..fetchUserId() 128 | return userId 129 | }.then { userId in 130 | XCTAssertEqual(userId, 1234) 131 | exp.fulfill() 132 | } 133 | waitForExpectations(timeout: 0.3, handler: nil) 134 | } 135 | 136 | /// Optional Promises 137 | 138 | func testOptionalPromises() { 139 | let exp = expectation(description: "") 140 | async { 141 | let optionalPromise: Promise? = fetchUserId() 142 | let userId = try ..optionalPromise 143 | XCTAssertEqual(userId, 1234) 144 | exp.fulfill() 145 | } 146 | waitForExpectations(timeout: 0.3, handler: nil) 147 | } 148 | 149 | func testNilOptionalPromisesFail() { 150 | let exp = expectation(description: "") 151 | async { 152 | let optionalPromise: Promise? = nil 153 | _ = try ..optionalPromise 154 | XCTFail("testFailingAsyncAwait failed") 155 | }.onError { _ in 156 | exp.fulfill() 157 | } 158 | waitForExpectations(timeout: 0.3, handler: nil) 159 | } 160 | 161 | /// Operator ..? - nils out instead of throwing 162 | 163 | func testAwaitNilingOperator() { 164 | let exp = expectation(description: "") 165 | async { 166 | let userId = ..?fetchUserId() 167 | XCTAssertEqual(userId, 1234) 168 | exp.fulfill() 169 | } 170 | waitForExpectations(timeout: 0.5, handler: nil) 171 | } 172 | 173 | func testAwaitNilingOperatorError() { 174 | let exp = expectation(description: "") 175 | async { 176 | let string = ..?(failingFetchUserFollowStatusFromName("JohnDoe")) 177 | XCTAssertNil(string) 178 | exp.fulfill() 179 | }.onError { _ in 180 | XCTFail("testFailingAsyncAwait failed") 181 | } 182 | waitForExpectations(timeout: 0.5, handler: nil) 183 | } 184 | 185 | /// Optional Operator ..? - nils out instead of throwing 186 | 187 | func testAwaitNilingOperatorOptional() { 188 | let exp = expectation(description: "") 189 | async { 190 | let promise: Promise? = fetchUserId() 191 | let userId = ..?promise 192 | XCTAssertEqual(userId, 1234) 193 | exp.fulfill() 194 | } 195 | waitForExpectations(timeout: 0.5, handler: nil) 196 | } 197 | 198 | func testAwaitNilingOperatorErrorOptinal() { 199 | let exp = expectation(description: "") 200 | async { 201 | let promise: Promise? = failingFetchUserFollowStatusFromName("JohnDoe") 202 | let string = ..?promise 203 | XCTAssertNil(string) 204 | exp.fulfill() 205 | }.onError { _ in 206 | XCTFail("testFailingAsyncAwait failed") 207 | } 208 | waitForExpectations(timeout: 0.5, handler: nil) 209 | } 210 | 211 | func testAwaitNilingOperatorErrorNilOptional() { 212 | let exp = expectation(description: "") 213 | async { 214 | let promise: Promise? = nil 215 | let string = ..?promise 216 | XCTAssertNil(string) 217 | exp.fulfill() 218 | }.onError { _ in 219 | XCTFail("testFailingAsyncAwait failed") 220 | } 221 | waitForExpectations(timeout: 0.3, handler: nil) 222 | } 223 | 224 | } 225 | -------------------------------------------------------------------------------- /Tests/ThenTests/BridgeErrorTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BridgeErrorTests.swift 3 | // then 4 | // 5 | // Created by Sacha Durand Saint Omer on 24/02/2017. 6 | // Copyright © 2017 s4cha. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | @testable import Then 11 | 12 | class BridgeErrorTests: XCTestCase { 13 | 14 | func testBridgeAllErrorsToMine() { 15 | let exp = expectation(description: "") 16 | Promise.reject() 17 | .bridgeError(to: MyError.defaultError) 18 | .then { _ in 19 | XCTFail("then shouldn't be called") 20 | }.onError { e in 21 | if let e = e as? MyError { 22 | XCTAssertTrue(e == .defaultError) 23 | } else { 24 | XCTFail("testBridgeAllErrorsToMine failed") 25 | } 26 | exp.fulfill() 27 | } 28 | waitForExpectations(timeout: 0.3, handler: nil) 29 | } 30 | 31 | func testBridgeAllErrorsNoError() { 32 | let exp = expectation(description: "") 33 | Promise.resolve(42) 34 | .bridgeError(to: MyError.defaultError) 35 | .then { _ in 36 | exp.fulfill() 37 | }.onError { _ in 38 | XCTFail("onError shouldn't be called") 39 | 40 | } 41 | waitForExpectations(timeout: 0.3, handler: nil) 42 | } 43 | 44 | func testBridgeASpecificErrorToMine() { 45 | let exp = expectation(description: "") 46 | Promise.reject(PromiseError.retryInvalidInput) 47 | .bridgeError(PromiseError.retryInvalidInput, to: MyError.defaultError) 48 | .then { _ in 49 | XCTFail("then shouldn't be called") 50 | }.onError { e in 51 | if let e = e as? MyError { 52 | XCTAssertTrue(e == .defaultError) 53 | } else { 54 | XCTFail("testBridgeASpecificErrorToMine failed") 55 | } 56 | exp.fulfill() 57 | } 58 | waitForExpectations(timeout: 0.3, handler: nil) 59 | } 60 | 61 | func testBridgeASpecificErrorToMineNotMatchingError() { 62 | let exp = expectation(description: "") 63 | Promise.reject(PromiseError.default) 64 | .bridgeError(PromiseError.retryInvalidInput, to: MyError.defaultError) 65 | .then { _ in 66 | XCTFail("then shouldn't be called") 67 | }.onError { e in 68 | if let e = e as? PromiseError { 69 | XCTAssertTrue(e == .default) 70 | } else { 71 | XCTFail("testBridgeASpecificErrorToMineNotMatchingError failed") 72 | } 73 | exp.fulfill() 74 | } 75 | waitForExpectations(timeout: 0.3, handler: nil) 76 | } 77 | 78 | func testBridgeErrorCanUseBlockAndThrow() { 79 | let exp = expectation(description: "") 80 | Promise.reject() 81 | .bridgeError { _ in 82 | throw MyError.defaultError 83 | } 84 | .then { _ in 85 | XCTFail("then shouldn't be called") 86 | }.onError { e in 87 | if let e = e as? MyError { 88 | XCTAssertTrue(e == .defaultError) 89 | } else { 90 | XCTFail("failed testBridgeErrorCanUseBlockAndThrow") 91 | } 92 | exp.fulfill() 93 | } 94 | waitForExpectations(timeout: 0.3, handler: nil) 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /Tests/ThenTests/ChainTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ChainTests.swift 3 | // then 4 | // 5 | // Created by Sacha Durand Saint Omer on 13/03/2017. 6 | // Copyright © 2017 s4cha. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | import Then 11 | 12 | class ChainTests: XCTestCase { 13 | 14 | func testChainSyncPromise() { 15 | let exp = expectation(description: "") 16 | Promise.resolve("Cool").chain { s in 17 | XCTAssertEqual(s, "Cool") 18 | exp.fulfill() 19 | }.then { _ in } 20 | waitForExpectations(timeout: 0.3, handler: nil) 21 | } 22 | 23 | func testChainASyncPromise() { 24 | let exp = expectation(description: "") 25 | fetchUserNameFromId(123).chain { s in 26 | XCTAssertEqual(s, "John Smith") 27 | exp.fulfill() 28 | }.then { _ in } 29 | waitForExpectations(timeout: 0.3, handler: nil) 30 | } 31 | 32 | func testChainNotCalledWhenSyncPromiseFails() { 33 | let exp = expectation(description: "") 34 | Promise.reject().chain { _ in 35 | XCTFail("testChainNotCalledWhenSyncPromiseFails failed") 36 | }.onError { _ in 37 | exp.fulfill() 38 | } 39 | waitForExpectations(timeout: 0.3, handler: nil) 40 | } 41 | 42 | func testChainNotCalledWhenAsyncPromiseFails() { 43 | let exp = expectation(description: "") 44 | failingFetchUserFollowStatusFromName("Tom").chain { _ in 45 | XCTFail("testChainNotCalledWhenAsyncPromiseFails failed") 46 | }.onError { _ in 47 | exp.fulfill() 48 | } 49 | waitForExpectations(timeout: 0.3, handler: nil) 50 | } 51 | 52 | func testChainKeepsProgress() { 53 | let progressExpectation = expectation(description: "thenExpectation") 54 | let thenExpectation = expectation(description: "thenExpectation") 55 | let chainExpectation = expectation(description: "chainExpectation") 56 | upload().chain { 57 | chainExpectation.fulfill() 58 | }.progress { p in 59 | XCTAssertEqual(p, 0.8) 60 | progressExpectation.fulfill() 61 | }.then { 62 | thenExpectation.fulfill() 63 | }.onError { _ in 64 | print("ERROR") 65 | } 66 | waitForExpectations(timeout: 0.5, handler: nil) 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /Tests/ThenTests/DelayTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DelayTests.swift 3 | // then 4 | // 5 | // Created by Sacha Durand Saint Omer on 09/08/2017. 6 | // Copyright © 2017 s4cha. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | import Then 11 | 12 | class DelayTests: XCTestCase { 13 | 14 | func testStaticDelay() { 15 | let e = expectation(description: "") 16 | var run = false 17 | Promises.delay(0.2).then { 18 | run = true 19 | e.fulfill() 20 | } 21 | waitTime(0.1) { 22 | XCTAssertFalse(run) 23 | } 24 | waitTime(0.3) { 25 | XCTAssertTrue(run) 26 | } 27 | waitForExpectations(timeout: 0.5, handler: nil) 28 | } 29 | 30 | func testDelay() { 31 | let e = expectation(description: "") 32 | var result: Int? 33 | Promise { resolve, _ in 34 | waitTime(0.1) { 35 | resolve(123) 36 | } 37 | }.delay(0.1).then { int in 38 | result = int 39 | e.fulfill() 40 | } 41 | 42 | waitTime(0.1) { 43 | XCTAssertNil(result) 44 | } 45 | waitTime(0.3) { 46 | XCTAssertEqual(result, 123) 47 | } 48 | waitForExpectations(timeout: 0.5, handler: nil) 49 | } 50 | 51 | func testChainDelays() { 52 | let e = expectation(description: "") 53 | var run = false 54 | Promises.delay(0.1).delay(0.1).delay(0.1).then { 55 | run = true 56 | e.fulfill() 57 | } 58 | waitTime(0.2) { 59 | XCTAssertFalse(run) 60 | } 61 | waitTime(0.4) { 62 | XCTAssertTrue(run) 63 | } 64 | waitForExpectations(timeout: 0.5, handler: nil) 65 | } 66 | 67 | func testDelayOnlyAppliesOnSuccessfulPromises() { 68 | let e = expectation(description: "") 69 | var done = false 70 | Promise { _, reject in 71 | waitTime(0.2) { 72 | reject(PromiseError.default) 73 | } 74 | }.delay(0.8).then { _ in 75 | XCTFail("testDelayOnlyAppliesOnSuccessfulPromises failed") 76 | }.onError { _ in 77 | done = true 78 | e.fulfill() 79 | } 80 | 81 | waitTime(0.3) { 82 | XCTAssertTrue(done) 83 | } 84 | waitForExpectations(timeout: 0.3, handler: nil) 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /Tests/ThenTests/FinallyTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FinallyTests.swift 3 | // then 4 | // 5 | // Created by Sacha Durand Saint Omer on 23/02/2017. 6 | // Copyright © 2017 s4cha. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | import Then 11 | 12 | class FinallyTests: XCTestCase { 13 | 14 | func testFinallyCalledWhenSynchronousSuccess() { 15 | let finallyblock = expectation(description: "finally block block called") 16 | Promise.resolve("Done").finally { 17 | finallyblock.fulfill() 18 | } 19 | waitForExpectations(timeout: 0.3, handler: nil) 20 | } 21 | 22 | func testFinallyCalledWhenSynchronousFail() { 23 | let finallyblock = expectation(description: "finally block block called") 24 | Promise.reject().finally { 25 | finallyblock.fulfill() 26 | } 27 | waitForExpectations(timeout: 0.3, handler: nil) 28 | } 29 | 30 | func testFinallyCalledWhenAsynchronousSuccess() { 31 | let finallyblock = expectation(description: "finally block block called") 32 | Promise { resolve, _ in 33 | waitTime(0.1) { 34 | resolve("Hello") 35 | } 36 | }.finally { 37 | finallyblock.fulfill() 38 | } 39 | waitForExpectations(timeout: 0.3, handler: nil) 40 | } 41 | 42 | func testFinallyCalledWhenAsynchronousFail() { 43 | let finallyblock = expectation(description: "finally block block called") 44 | Promise { _, reject in 45 | waitTime(0.1) { 46 | reject(PromiseError.default) 47 | } 48 | }.finally { 49 | finallyblock.fulfill() 50 | } 51 | waitForExpectations(timeout: 0.3, handler: nil) 52 | } 53 | 54 | func testMultipleFinallyBlockCanBeRegisteredOnSamePromise() { 55 | let finally1 = expectation(description: "finally called") 56 | let finally2 = expectation(description: "finally called") 57 | let finally3 = expectation(description: "finally called") 58 | let finally4 = expectation(description: "finally called") 59 | let p = failingFetchUserFollowStatusFromName("") 60 | p.finally { 61 | finally1.fulfill() 62 | } 63 | p.finally { 64 | finally2.fulfill() 65 | } 66 | p.finally { 67 | finally3.fulfill() 68 | } 69 | p.finally { 70 | finally4.fulfill() 71 | } 72 | waitForExpectations(timeout: 0.5, handler: nil) 73 | } 74 | 75 | func testRegisterFinallyDoesntStartThePromise() { 76 | let exp = expectation(description: "error block called") 77 | syncRejectionPromise().registerFinally { 78 | XCTFail("testRegisterFinallyDoesntStartThePromise failed") 79 | } 80 | waitTime(0.1) { 81 | exp.fulfill() 82 | } 83 | waitForExpectations(timeout: 0.2, handler: nil) 84 | } 85 | 86 | func testRegisterFinally() { 87 | let exp = expectation(description: "error block called") 88 | let p = syncRejectionPromise() 89 | p.registerFinally { 90 | exp.fulfill() 91 | } 92 | p.start() 93 | waitForExpectations(timeout: 0.3, handler: nil) 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /Tests/ThenTests/Helpers.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Helpers.swift 3 | // then 4 | // 5 | // Created by Sacha Durand Saint Omer on 08/08/16. 6 | // Copyright © 2016 s4cha. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | @testable import Then 11 | import Dispatch 12 | 13 | var globalCount = 0 14 | var blockPromiseCExpectation: XCTestExpectation! 15 | 16 | func promiseA() -> Promise { 17 | return Promise { resolve, _ in 18 | XCTAssertTrue(globalCount == 0) 19 | globalCount+=1 20 | resolve(globalCount) 21 | } 22 | } 23 | 24 | func promiseB() -> Promise { 25 | return Promise { resolve, _ in 26 | XCTAssertTrue(globalCount == 1) 27 | globalCount+=1 28 | resolve(globalCount) 29 | } 30 | } 31 | 32 | func promiseC() -> Promise { 33 | return Promise { resolve, _ in 34 | XCTAssertTrue(globalCount == 2) 35 | globalCount+=1 36 | resolve(globalCount) 37 | blockPromiseCExpectation.fulfill() 38 | 39 | } 40 | } 41 | 42 | func promise1() -> Promise { 43 | return Promise.resolve(1) 44 | } 45 | 46 | func promise2() -> Promise { 47 | return Promise.resolve(2) 48 | } 49 | 50 | func promise3() -> Promise { 51 | return Promise.resolve(3) 52 | } 53 | 54 | func promiseArray1() -> Promise<[Int]> { 55 | return Promise.resolve([1, 2, 3]) 56 | } 57 | 58 | func promiseArray2() -> Promise<[Int]> { 59 | return Promise.resolve([4, 5, 6]) 60 | } 61 | 62 | func promiseArray3() -> Promise<[Int]> { 63 | return Promise.resolve([7, 8, 9]) 64 | } 65 | 66 | func syncRejectionPromise() -> Promise { 67 | return Promise.reject(MyError.defaultError) 68 | } 69 | 70 | func fetchUserId() -> Promise { 71 | return Promise { resolve, _ in 72 | print("fetching user Id ...") 73 | waitTime { 74 | resolve(1234) 75 | print("GOT USER ID 1234") 76 | } 77 | } 78 | } 79 | 80 | func fetchUserNameFromId(_ identifier: Int) -> Promise { 81 | return Promise { resolve, _ in 82 | print("fetching UserName FromId : \(identifier) ...") 83 | waitTime { resolve("John Smith") } 84 | } 85 | } 86 | 87 | func fetchUserFollowStatusFromName(_ name: String) -> Promise { 88 | return Promise { resolve, _ in 89 | print("fetchUserFollowStatusFromName: \(name) ...") 90 | waitTime { resolve(false) } 91 | } 92 | } 93 | 94 | func failingFetchUserFollowStatusFromName(_ name: String) -> Promise { 95 | return Promise { _, reject in 96 | print("fetchUserFollowStatusFromName: \(name) ...") 97 | waitTime { reject(MyError.defaultError) } 98 | } 99 | } 100 | 101 | func waitTime(_ callback:@escaping () -> Void) { 102 | let delay = 0.01 * Double(NSEC_PER_SEC) 103 | let time = DispatchTime.now() + Double(Int64(delay)) / Double(NSEC_PER_SEC) 104 | DispatchQueue.global(qos: DispatchQoS.QoSClass.background).asyncAfter(deadline: time) { 105 | callback() 106 | } 107 | } 108 | 109 | func waitTime(_ time: Double, callback: @escaping () -> Void) { 110 | let delay = time * Double(NSEC_PER_SEC) 111 | let time = DispatchTime.now() + Double(Int64(delay)) / Double(NSEC_PER_SEC) 112 | DispatchQueue.main.asyncAfter(deadline: time) { 113 | callback() 114 | } 115 | } 116 | 117 | func upload() -> Promise { 118 | return Promise { (resolve: @escaping (() -> Void), _: @escaping ((Error) -> Void), progress) in 119 | waitTime { 120 | progress(0.8) 121 | waitTime { 122 | resolve() 123 | } 124 | } 125 | } 126 | } 127 | 128 | func failingUpload() -> Promise { 129 | return Promise { (_: @escaping (() -> Void), reject: @escaping ((Error) -> Void), progress) in 130 | waitTime { 131 | progress(0.8) 132 | waitTime { 133 | reject(NSError(domain: "", code: 1223, userInfo: nil)) 134 | } 135 | } 136 | } 137 | } 138 | 139 | enum MyError: Error { 140 | case defaultError 141 | } 142 | 143 | extension Promise { 144 | var blocks: PromiseBlocks { 145 | get { 146 | return synchronize { _, blocks in 147 | let temp = blocks 148 | return temp 149 | } 150 | } 151 | set { 152 | synchronize { _, blocks in 153 | blocks = newValue 154 | } 155 | } 156 | } 157 | } 158 | -------------------------------------------------------------------------------- /Tests/ThenTests/MemoryTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MemoryTests.swift 3 | // then 4 | // 5 | // Created by Sacha Durand Saint Omer on 09/08/2017. 6 | // Copyright © 2017 s4cha. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | @testable import Then 11 | 12 | class MemoryTests: XCTestCase { 13 | 14 | func testRaceConditionWriteState() { 15 | let p = Promise() 16 | 17 | func loopState() { 18 | for i in 0...10000 { 19 | p.updateState(PromiseState.fulfilled(value: "Test1-\(i)")) 20 | p.updateState(PromiseState.fulfilled(value: "Test2-\(i)")) 21 | p.updateState(PromiseState.fulfilled(value: "Test3-\(i)")) 22 | } 23 | } 24 | 25 | if #available(iOS 10.0, *) { 26 | let t1 = Thread { loopState() } 27 | let t2 = Thread { loopState() } 28 | let t3 = Thread { loopState() } 29 | let t4 = Thread { loopState() } 30 | t1.start() 31 | t2.start() 32 | t3.start() 33 | t4.start() 34 | } else { 35 | // Fallback on earlier versions 36 | } 37 | loopState() 38 | } 39 | 40 | func testRaceConditionReadState() { 41 | let p = Promise("Hello") 42 | 43 | func loopState() { 44 | for i in 0...10000 { 45 | p.updateState(PromiseState.fulfilled(value: "Test1-\(i)")) 46 | p.updateState(PromiseState.fulfilled(value: "Test2-\(i)")) 47 | p.updateState(PromiseState.fulfilled(value: "Test3-\(i)")) 48 | //Access Value 49 | let value = p.value 50 | print(value ?? "") 51 | } 52 | } 53 | 54 | if #available(iOS 10.0, *) { 55 | let t1 = Thread { loopState() } 56 | let t2 = Thread { loopState() } 57 | let t3 = Thread { loopState() } 58 | let t4 = Thread { loopState() } 59 | t1.start() 60 | t2.start() 61 | t3.start() 62 | t4.start() 63 | } else { 64 | // Fallback on earlier versions 65 | } 66 | loopState() 67 | } 68 | 69 | func testRaceConditionResigterBlocks() { 70 | let p = Promise() 71 | func loop() { 72 | for _ in 0...1000 { 73 | p.registerThen { _ in } 74 | p.registerOnError { _ in } 75 | p.registerFinally { } 76 | p.progress { _ in } 77 | } 78 | } 79 | 80 | if #available(iOS 10.0, *) { 81 | let t1 = Thread { loop() } 82 | let t2 = Thread { loop() } 83 | let t3 = Thread { loop() } 84 | let t4 = Thread { loop() } 85 | t1.start() 86 | t2.start() 87 | t3.start() 88 | t4.start() 89 | } else { 90 | // Fallback on earlier versions 91 | } 92 | loop() 93 | } 94 | 95 | func testRaceConditionWriteWriteBlocks() { 96 | let p = Promise() 97 | func loop() { 98 | for _ in 0...1000 { 99 | p.blocks.success.append({ _ in }) 100 | p.blocks.fail.append({ _ in }) 101 | p.blocks.progress.append({ _ in }) 102 | p.blocks.finally.append({ }) 103 | } 104 | } 105 | if #available(iOS 10.0, *) { 106 | let t1 = Thread { loop() } 107 | let t2 = Thread { loop() } 108 | let t3 = Thread { loop() } 109 | let t4 = Thread { loop() } 110 | t1.start() 111 | t2.start() 112 | t3.start() 113 | t4.start() 114 | } else { 115 | // Fallback on earlier versions 116 | } 117 | loop() 118 | } 119 | 120 | func testRaceConditionWriteReadBlocks() { 121 | let p = Promise() 122 | p.blocks.success.append({ _ in }) 123 | p.blocks.fail.append({ _ in }) 124 | p.blocks.progress.append({ _ in }) 125 | p.blocks.success.append({ _ in }) 126 | p.blocks.fail.append({ _ in }) 127 | p.blocks.progress.append({ _ in }) 128 | p.blocks.finally.append({ }) 129 | 130 | func loop() { 131 | for _ in 0...10000 { 132 | 133 | for sb in p.blocks.success { 134 | sb("YO") 135 | } 136 | 137 | for fb in p.blocks.fail { 138 | fb(PromiseError.default) 139 | } 140 | 141 | for p in p.blocks.progress { 142 | p(0.5) 143 | } 144 | 145 | for fb in p.blocks.finally { 146 | fb() 147 | } 148 | } 149 | } 150 | if #available(iOS 10.0, *) { 151 | let t1 = Thread { loop() } 152 | let t2 = Thread { loop() } 153 | let t3 = Thread { loop() } 154 | let t4 = Thread { loop() } 155 | t1.start() 156 | t2.start() 157 | t3.start() 158 | t4.start() 159 | } else { 160 | // Fallback on earlier versions 161 | } 162 | loop() 163 | } 164 | } 165 | -------------------------------------------------------------------------------- /Tests/ThenTests/NoMatterWhatTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NoMatterWhatTests.swift 3 | // then 4 | // 5 | // Created by Sacha Durand Saint Omer on 24/02/2017. 6 | // Copyright © 2017 s4cha. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | import Then 11 | 12 | class NoMatterWhatTests: XCTestCase { 13 | 14 | func testNoMatterWhatCalledOnSuccess() { 15 | let exp = expectation(description: "") 16 | var isLoading = true 17 | XCTAssertTrue(isLoading) 18 | Promise.resolve("Cool").noMatterWhat { 19 | isLoading = false 20 | }.finally { 21 | XCTAssertFalse(isLoading) 22 | exp.fulfill() 23 | } 24 | waitForExpectations(timeout: 0.3, handler: nil) 25 | } 26 | 27 | func testNoMatterWhatCalledOnError() { 28 | let exp = expectation(description: "") 29 | var isLoading = true 30 | XCTAssertTrue(isLoading) 31 | Promise.reject().noMatterWhat { 32 | isLoading = false 33 | }.finally { 34 | XCTAssertFalse(isLoading) 35 | exp.fulfill() 36 | } 37 | waitForExpectations(timeout: 0.3, handler: nil) 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /Tests/ThenTests/OnErrorTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // OnErrorTests.swift 3 | // then 4 | // 5 | // Created by Sacha Durand Saint Omer on 08/08/16. 6 | // Copyright © 2016 s4cha. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | import Then 11 | 12 | class OnErrorTests: XCTestCase { 13 | 14 | func testError() { 15 | let errorExpectation = expectation(description: "onError called") 16 | let finallyExpectation = expectation(description: "Finally called") 17 | fetchUserId() 18 | .then(fetchUserNameFromId) 19 | .then(failingFetchUserFollowStatusFromName) 20 | .then { _ in 21 | XCTFail("then block shouldn't be called") 22 | }.onError { e in 23 | XCTAssertTrue((e as? MyError) == MyError.defaultError) 24 | errorExpectation.fulfill() 25 | }.finally { 26 | finallyExpectation.fulfill() 27 | } 28 | 29 | waitForExpectations(timeout: 0.7, handler: nil) 30 | } 31 | 32 | func testOnErrorCalledWhenSynchronousRejects() { 33 | let errorblock = expectation(description: "error block called") 34 | promise1() 35 | .then(syncRejectionPromise()) 36 | .then(syncRejectionPromise()) 37 | .onError { _ in 38 | errorblock.fulfill() 39 | } 40 | waitForExpectations(timeout: 0.3, handler: nil) 41 | } 42 | 43 | func testThenAfterOnErrorWhenSynchronousResolves() { 44 | let thenblock = expectation(description: "then block called") 45 | promise1() 46 | .then(promise1()) 47 | .onError { _ in 48 | XCTFail("on Error shouldn't be called") 49 | }.then { _ in 50 | thenblock.fulfill() 51 | } 52 | waitForExpectations(timeout: 0.3, handler: nil) 53 | } 54 | 55 | func testMultipleErrorBlockCanBeRegisteredOnSamePromise() { 56 | let error1 = expectation(description: "error called") 57 | let error2 = expectation(description: "error called") 58 | let error3 = expectation(description: "error called") 59 | let error4 = expectation(description: "error called") 60 | let p = failingFetchUserFollowStatusFromName("") 61 | p.onError { _ in 62 | error1.fulfill() 63 | } 64 | p.onError { _ in 65 | error2.fulfill() 66 | } 67 | p.onError { _ in 68 | error3.fulfill() 69 | } 70 | p.onError { _ in 71 | error4.fulfill() 72 | } 73 | waitForExpectations(timeout: 0.3, handler: nil) 74 | } 75 | 76 | func testTwoConsecutivErrorBlocks2ndShouldNeverBeCalledOnFail() { 77 | let errorExpectation = expectation(description: "then called") 78 | failingFetchUserFollowStatusFromName("") 79 | .then { _ in 80 | XCTFail("on Error shouldn't be called") 81 | }.onError { _ in 82 | errorExpectation.fulfill() 83 | }.onError { _ in 84 | XCTFail("Second on Error shouldn't be called") 85 | } 86 | waitForExpectations(timeout: 0.3, handler: nil) 87 | } 88 | 89 | func testTwoConsecutivErrorBlocks2ndShouldNeverBeCalledOnSuccess() { 90 | let thenExpectation = expectation(description: "then called") 91 | fetchUserId() 92 | .then { _ in 93 | thenExpectation.fulfill() 94 | }.onError { _ in 95 | XCTFail("on Error shouldn't be called") 96 | }.onError { _ in 97 | XCTFail("on Error shouldn't be called") 98 | }.onError { _ in 99 | XCTFail("on Error shouldn't be called") 100 | }.onError { _ in 101 | XCTFail("on Error shouldn't be called") 102 | }.onError { _ in 103 | XCTFail("on Error shouldn't be called") 104 | }.onError { _ in 105 | XCTFail("on Error shouldn't be called") 106 | }.onError { _ in 107 | XCTFail("on Error shouldn't be called") 108 | }.onError { _ in 109 | XCTFail("on Error shouldn't be called") 110 | } 111 | waitForExpectations(timeout: 0.3, handler: nil) 112 | } 113 | 114 | func testRegisterOnErrorDoesntStartThePromise() { 115 | let exp = expectation(description: "error block called") 116 | syncRejectionPromise().registerOnError { _ in 117 | XCTFail("testRegisterOnErrorDoesntStartThePromise failed") 118 | } 119 | waitTime(0.1) { 120 | exp.fulfill() 121 | } 122 | waitForExpectations(timeout: 0.2, handler: nil) 123 | } 124 | 125 | func testRegisterOnError() { 126 | let exp = expectation(description: "error block called") 127 | let p = syncRejectionPromise() 128 | p.registerOnError { _ in 129 | exp.fulfill() 130 | } 131 | p.start() 132 | waitForExpectations(timeout: 0.3, handler: nil) 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /Tests/ThenTests/ProgressTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ProgressTests.swift 3 | // then 4 | // 5 | // Created by Sacha Durand Saint Omer on 08/08/16. 6 | // Copyright © 2016 s4cha. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | import Then 11 | 12 | class ProgressTests: XCTestCase { 13 | 14 | func testProgress() { 15 | let progressExpectation = expectation(description: "progressExpectation") 16 | let thenExpectation = expectation(description: "thenExpectation") 17 | upload().progress { p in 18 | print("PROGRESS \(p)") 19 | XCTAssertEqual(p, 0.8) 20 | progressExpectation.fulfill() 21 | }.then { 22 | print("Done") 23 | thenExpectation.fulfill() 24 | }.onError { _ in 25 | print("ERROR") 26 | } 27 | waitForExpectations(timeout: 0.3, handler: nil) 28 | } 29 | 30 | func testProgressFails() { 31 | let progressExpectation = expectation(description: "thenExpectation") 32 | let errorExpectation = expectation(description: "errorExpectation") 33 | failingUpload().progress { p in 34 | XCTAssertEqual(p, 0.8) 35 | progressExpectation.fulfill() 36 | }.then { 37 | XCTFail("testProgressFails failed") 38 | }.onError { _ in 39 | errorExpectation.fulfill() 40 | } 41 | waitForExpectations(timeout: 0.3, handler: nil) 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /Tests/ThenTests/RaceTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RaceTests.swift 3 | // then 4 | // 5 | // Created by Sacha Durand Saint Omer on 22/02/2017. 6 | // Copyright © 2017 s4cha. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | import Then 11 | 12 | class RaceTests: XCTestCase { 13 | 14 | func testAsyncRaceFirstArrivesFirst() { 15 | let e = expectation(description: "") 16 | let p1 = Promise { r, _ in 17 | waitTime(0.1) { 18 | r("1") 19 | } 20 | } 21 | let p2 = Promise { r, _ in 22 | waitTime(0.3) { 23 | r("2") 24 | } 25 | } 26 | Promises.race(p1, p2).then { s in 27 | e.fulfill() 28 | XCTAssertEqual(s, "1") 29 | } 30 | waitForExpectations(timeout: 0.3, handler: nil) 31 | } 32 | 33 | func testSyncRaceFirstArrivesFirst() { 34 | let e = expectation(description: "") 35 | let p1 = Promise("1") 36 | let p2 = Promise("2") 37 | Promises.race(p1, p2).then { s in 38 | e.fulfill() 39 | XCTAssertEqual(s, "1") 40 | } 41 | waitForExpectations(timeout: 0.3, handler: nil) 42 | } 43 | 44 | struct TestRaceError: Error {} 45 | 46 | func testAsyncRaceWithFirsFailingFails() { 47 | let e = expectation(description: "") 48 | let p1 = Promise.reject(TestRaceError()) 49 | let p2 = Promise { r, _ in 50 | waitTime(2) { 51 | r("2") 52 | } 53 | } 54 | Promises.race(p1, p2).onError { error in 55 | guard error as? TestRaceError != nil else { 56 | XCTFail("testRecoverCanThrowANewError failed") 57 | return 58 | } 59 | e.fulfill() 60 | } 61 | waitForExpectations(timeout: 0.3, handler: nil) 62 | } 63 | 64 | func testAsyncRaceWithSecondFailingSuceeds() { 65 | let e = expectation(description: "") 66 | let p1 = Promise("1") 67 | let p2 = Promise { r, _ in 68 | waitTime(2) { 69 | r("2") 70 | } 71 | } 72 | Promises.race(p1, p2).then { s in 73 | e.fulfill() 74 | XCTAssertEqual(s, "1") 75 | } 76 | waitForExpectations(timeout: 0.3, handler: nil) 77 | } 78 | 79 | // func testRaceFailsIfAllFail() { 80 | // let e = expectation(description: "") 81 | // let p1 = Promise.reject() 82 | // let p2 = Promise.reject() 83 | // Promises.race(p1, p2).then { _ in 84 | // XCTFail("testRaceFailsIfAllFail failed") 85 | // }.onError { _ in 86 | // e.fulfill() 87 | // } 88 | // waitForExpectations(timeout: 0.3, handler: nil) 89 | // } 90 | 91 | } 92 | -------------------------------------------------------------------------------- /Tests/ThenTests/RecoverTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RecoverTests.swift 3 | // then 4 | // 5 | // Created by Sacha Durand Saint Omer on 22/02/2017. 6 | // Copyright © 2017 s4cha. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | import Then 11 | 12 | class RecoverTests: XCTestCase { 13 | 14 | func testRecoverWithString() { 15 | let e = expectation(description: "") 16 | Promise.reject() 17 | .recover(with: "Banana") 18 | .then { s in 19 | XCTAssertEqual(s, "Banana") 20 | e.fulfill() 21 | } 22 | waitForExpectations(timeout: 0.3, handler: nil) 23 | } 24 | 25 | func testRecoverWithInt() { 26 | let e = expectation(description: "") 27 | Promise.reject() 28 | .recover(with: 12) 29 | .then { s in 30 | XCTAssertEqual(s, 12) 31 | e.fulfill() 32 | } 33 | waitForExpectations(timeout: 0.3, handler: nil) 34 | } 35 | 36 | func testRecoverWithPromise() { 37 | let e = expectation(description: "") 38 | Promise.reject() 39 | .recover(with: Promise.resolve(56)) 40 | .then { s in 41 | XCTAssertEqual(s, 56) 42 | e.fulfill() 43 | } 44 | waitForExpectations(timeout: 0.3, handler: nil) 45 | } 46 | 47 | func testRecoverWithFailablePromise() { 48 | let e = expectation(description: "") 49 | Promise.reject() 50 | .recover(with: Promise.reject()) 51 | .then { _ in 52 | XCTFail("then shouldn't be called") 53 | } 54 | .onError { _ in 55 | e.fulfill() 56 | } 57 | waitForExpectations(timeout: 0.3, handler: nil) 58 | } 59 | 60 | func testRecoverCanUseABlock() { 61 | let e = expectation(description: "") 62 | Promise.reject() 63 | .recover { _ in 64 | return 32 65 | } 66 | .then { s in 67 | XCTAssertEqual(s, 32) 68 | e.fulfill() 69 | } 70 | waitForExpectations(timeout: 0.3, handler: nil) 71 | } 72 | 73 | func testRecoverCanThrowANewError() { 74 | let exp = expectation(description: "") 75 | Promise.reject() 76 | .recover { e -> Int in 77 | if let e = e as? PromiseError, e == .default { 78 | throw MyError.defaultError 79 | } 80 | return 32 81 | } .then { _ in 82 | XCTFail("then shouldn't be called") 83 | }.onError { e in 84 | if let e = e as? MyError { 85 | XCTAssertTrue(e == .defaultError) 86 | } else { 87 | XCTFail("testRecoverCanThrowANewError failed") 88 | } 89 | exp.fulfill() 90 | } 91 | waitForExpectations(timeout: 0.3, handler: nil) 92 | } 93 | 94 | func testRecoverForSpecificError() { 95 | let exp = expectation(description: "") 96 | Promise.resolve(10) 97 | .validate { $0 > 100 } 98 | .recover(PromiseError.validationFailed, with: 123) 99 | .then { i in 100 | XCTAssertEqual(i, 123) 101 | exp.fulfill() 102 | } 103 | waitForExpectations(timeout: 0.3, handler: nil) 104 | } 105 | 106 | func testRecoverForSpecificErrorDoesNotRecoverWhenTypeNotMatching() { 107 | let exp = expectation(description: "") 108 | Promise.reject() 109 | .recover(PromiseError.validationFailed, with: 123) 110 | .then { _ in 111 | XCTFail("testRecoverForSpecificErrorDoesNotRecoverWhenTypeNotMatching failed") 112 | }.onError { _ in 113 | exp.fulfill() 114 | } 115 | waitForExpectations(timeout: 0.3, handler: nil) 116 | } 117 | 118 | func testEquatableError() { 119 | let exp = expectation(description: "") 120 | Promise.reject(SomeError()) 121 | .recover(SomeError(), with: 123) 122 | .then { _ in 123 | exp.fulfill() 124 | } 125 | waitForExpectations(timeout: 0.3, handler: nil) 126 | } 127 | 128 | func testRecoverPromiseBlockCanUseABlock() { 129 | let e = expectation(description: "") 130 | Promise.reject() 131 | .recover { _ in 132 | return Promise(32) 133 | } 134 | .then { s in 135 | XCTAssertEqual(s, 32) 136 | e.fulfill() 137 | } 138 | waitForExpectations(timeout: 0.3, handler: nil) 139 | } 140 | 141 | func testRecoverPromiseBlockCanThrowANewError() { 142 | let exp = expectation(description: "") 143 | Promise.reject() 144 | .recover { e -> Promise in 145 | if let e = e as? PromiseError, e == .default { 146 | throw MyError.defaultError 147 | } 148 | return Promise(32) 149 | } .then { _ in 150 | XCTFail("then shouldn't be called") 151 | }.onError { e in 152 | if let e = e as? MyError { 153 | XCTAssertTrue(e == .defaultError) 154 | } else { 155 | XCTFail("testRecoverCanThrowANewError failed") 156 | } 157 | exp.fulfill() 158 | } 159 | waitForExpectations(timeout: 0.3, handler: nil) 160 | } 161 | } 162 | 163 | struct SomeError: Error { } 164 | extension SomeError: Equatable { } 165 | func == (lhs: SomeError, rhs: SomeError) -> Bool { 166 | return true 167 | } 168 | -------------------------------------------------------------------------------- /Tests/ThenTests/RegisterThenTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RegisterThenTests.swift 3 | // then 4 | // 5 | // Created by Sacha Durand Saint Omer on 08/08/16. 6 | // Copyright © 2016 s4cha. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | import Then 11 | 12 | class RegisterThenTests: XCTestCase { 13 | 14 | func testRegisterThenChainedPromisesAreNeverCalledWithoutAThenBlock() { 15 | let timerExpectation = expectation(description: "timerExpectation") 16 | fetchUserId() 17 | .registerThen { _ in 18 | XCTFail("testRegisterThenChainedPromisesAreNeverCalledWithoutAThenBlock failed") 19 | }.registerThen {_ in 20 | XCTFail("testRegisterThenChainedPromisesAreNeverCalledWithoutAThenBlock failed") 21 | }.registerThen {_ in 22 | XCTFail("testRegisterThenChainedPromisesAreNeverCalledWithoutAThenBlock failed") 23 | } 24 | waitTime(0.3) { 25 | timerExpectation.fulfill() 26 | } 27 | waitForExpectations(timeout: 0.5, handler: nil) 28 | } 29 | 30 | func testRegisterThenPromiseChainedPromisesAreNeverCalledWithoutAThenBlock() { 31 | let timerExpectation = expectation(description: "timerExpectation") 32 | fetchUserId() 33 | .registerThen(fetchUserNameFromId(10)).registerThen { name in 34 | print(name) 35 | XCTFail("testRegisterThenPromiseChainedPromisesAreNeverCalledWithoutAThenBlock failed") 36 | }.registerThen {_ in 37 | XCTFail("testRegisterThenPromiseChainedPromisesAreNeverCalledWithoutAThenBlock failed") 38 | }.registerThen {_ in 39 | XCTFail("testRegisterThenPromiseChainedPromisesAreNeverCalledWithoutAThenBlock failed") 40 | } 41 | waitTime(0.3) { 42 | timerExpectation.fulfill() 43 | } 44 | waitForExpectations(timeout: 0.5, handler: nil) 45 | } 46 | 47 | func testRegisterThenPromise2ChainedPromisesAreNeverCalledWithoutAThenBlock() { 48 | let timerExpectation = expectation(description: "timerExpectation") 49 | fetchUserId().registerThen { id in 50 | return fetchUserNameFromId(id) 51 | }.registerThen { _ in 52 | XCTFail("testRegisterThenPromise2ChainedPromisesAreNeverCalledWithoutAThenBlock failed") 53 | }.registerThen { _ in 54 | XCTFail("testRegisterThenPromise2ChainedPromisesAreNeverCalledWithoutAThenBlock failed") 55 | } 56 | waitTime(0.3) { 57 | timerExpectation.fulfill() 58 | } 59 | waitForExpectations(timeout: 0.5, handler: nil) 60 | } 61 | 62 | func testRegisterThenChainedPromisesAreExecutedInOrder() { 63 | var count = 0 64 | 65 | let block1 = expectation(description: "block 1 called") 66 | let block2 = expectation(description: "block 2 called") 67 | let block3 = expectation(description: "block 3 called") 68 | 69 | let thenExpectation = expectation(description: "thenExpectation") 70 | fetchUserId() 71 | .registerThen { _ -> Void in 72 | XCTAssertTrue(count == 0) 73 | count+=1 74 | block1.fulfill() 75 | }.registerThen {_ -> Void in 76 | XCTAssertTrue(count == 1) 77 | count+=1 78 | block2.fulfill() 79 | }.registerThen {_ -> Void in 80 | XCTAssertTrue(count == 2) 81 | count+=1 82 | block3.fulfill() 83 | }.then { name in 84 | XCTAssertTrue(count == 3) 85 | count+=1 86 | print("name :\(name)") 87 | thenExpectation.fulfill() 88 | } 89 | waitForExpectations(timeout: 0.3, handler: nil) 90 | } 91 | 92 | func testRegisterThenPromiseFuncPointerNotCalled() { 93 | let timerExpectation = expectation(description: "thenExpectation") 94 | fetchUserId() 95 | .registerThen(fetchUserNameFromId) 96 | .registerThen { _ in 97 | XCTFail("testRegisterThenPromiseFuncPointerNotCalled failed") 98 | } 99 | waitTime(0.3) { 100 | timerExpectation.fulfill() 101 | } 102 | waitForExpectations(timeout: 0.5, handler: nil) 103 | } 104 | 105 | func testRegisterThenPromise2FuncPointerNotCalled() { 106 | let timerExpectation = expectation(description: "thenExpectation") 107 | fetchUserId().registerThen { id -> Promise in 108 | return fetchUserNameFromId(id) 109 | }.registerThen { _ in 110 | XCTFail("testRegisterThenPromise2FuncPointerNotCalled failed") 111 | } 112 | waitTime(0.3) { 113 | timerExpectation.fulfill() 114 | } 115 | waitForExpectations(timeout: 0.5, handler: nil) 116 | } 117 | 118 | func testRegisterThenPromiseFuncPointerCalledWithThenBlock() { 119 | let timerExpectation = expectation(description: "thenExpectation") 120 | fetchUserId() 121 | .registerThen(fetchUserNameFromId) 122 | .then { _ in 123 | timerExpectation.fulfill() 124 | } 125 | waitForExpectations(timeout: 0.3, handler: nil) 126 | } 127 | 128 | func testRegisterThenPromise2FuncPointerCalledWithThenBlock() { 129 | let timerExpectation = expectation(description: "thenExpectation") 130 | fetchUserId().registerThen { id -> Promise in 131 | return fetchUserNameFromId(id) 132 | }.then { _ in 133 | timerExpectation.fulfill() 134 | } 135 | waitForExpectations(timeout: 0.3, handler: nil) 136 | } 137 | 138 | func testRegisterThenPromiseFuncPointerCalledWithMultipleRegisterThenBlocks() { 139 | let timerExpectation = expectation(description: "thenExpectation") 140 | fetchUserId() 141 | .registerThen(fetchUserNameFromId) 142 | .registerThen(fetchUserFollowStatusFromName) 143 | .then { _ in 144 | timerExpectation.fulfill() 145 | } 146 | waitForExpectations(timeout: 0.7, handler: nil) 147 | } 148 | 149 | func testRegisterThenMultipleThenOnlyCallOriginalPromiseOnce() { 150 | var count = 0 151 | 152 | let block1 = expectation(description: "block 1 called") 153 | let block2 = expectation(description: "block 2 called") 154 | let block3 = expectation(description: "block 3 called") 155 | 156 | let thenExpectation = expectation(description: "thenExpectation") 157 | fetchUserId() 158 | .registerThen { _ -> Void in 159 | XCTAssertTrue(count == 0) 160 | count+=1 161 | block1.fulfill() 162 | }.registerThen {_ -> Void in 163 | XCTAssertTrue(count == 1) 164 | count+=1 165 | block2.fulfill() 166 | }.registerThen { _ -> Void in 167 | XCTAssertTrue(count == 2) 168 | count+=1 169 | block3.fulfill() 170 | } 171 | .then { name in 172 | XCTAssertTrue(count == 3) 173 | count+=1 174 | print("name :\(name)") 175 | thenExpectation.fulfill() 176 | } 177 | .then { _ -> Void in 178 | print("Just another then block") 179 | } 180 | waitForExpectations(timeout: 0.3, handler: nil) 181 | } 182 | 183 | } 184 | -------------------------------------------------------------------------------- /Tests/ThenTests/RetryTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RetryTests RetryTests.swift 3 | // then 4 | // 5 | // Created by Sacha Durand Saint Omer on 22/02/2017. 6 | // Copyright © 2017 s4cha. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | import Then 11 | 12 | class RetryTests: XCTestCase { 13 | 14 | var tryCount = 0 15 | 16 | func testRetryNumberWhenKeepsFailing() { 17 | let e = expectation(description: "") 18 | testPromise() 19 | .retry(5).then { 20 | XCTFail("testRetryNumberWhenKeepsFailing failed") 21 | }.onError { _ in 22 | e.fulfill() 23 | XCTAssertEqual(5, self.tryCount) 24 | } 25 | waitForExpectations(timeout: 3, handler: nil) 26 | } 27 | 28 | func testRetrySucceedsAfter3times() { 29 | let e = expectation(description: "") 30 | succeedsAfter3Times() 31 | .retry(10).then { 32 | e.fulfill() 33 | XCTAssertEqual(3, self.tryCount) 34 | }.onError { _ in 35 | XCTFail("testRetrySucceedsAfter3times failed") 36 | } 37 | waitForExpectations(timeout: 0.3, handler: nil) 38 | } 39 | 40 | func testRetryFailsIfNumberOfRetriesposisitethan1() { 41 | let e = expectation(description: "") 42 | testPromise() 43 | .retry(0).onError { _ in 44 | e.fulfill() 45 | } 46 | waitForExpectations(timeout: 0.3, handler: nil) 47 | } 48 | 49 | func testPromise() -> Promise { 50 | 51 | let callback: ((_ resolve: @escaping ((()) -> Void), _ reject: @escaping ((Error) -> Void)) -> Void) 52 | = { (resolve: @escaping ((()) -> Void), reject: @escaping ((Error) -> Void)) in 53 | self.tryCount += 1 54 | waitTime(0.1) { 55 | reject(ARandomError()) 56 | } 57 | } 58 | 59 | return Promise(callback: callback) 60 | } 61 | 62 | func succeedsAfter3Times() -> Promise { 63 | 64 | let callback: ((_ resolve: @escaping ((()) -> Void), _ reject: @escaping ((Error) -> Void)) -> Void) 65 | = { (resolve: @escaping ((()) -> Void), reject: @escaping ((Error) -> Void)) in 66 | self.tryCount += 1 67 | if self.tryCount == 3 { 68 | resolve(()) 69 | } else { 70 | reject(ARandomError()) 71 | } 72 | } 73 | 74 | return Promise(callback: callback) 75 | } 76 | } 77 | 78 | struct ARandomError: Error { } 79 | -------------------------------------------------------------------------------- /Tests/ThenTests/ThenTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ThenTests.swift 3 | // ThenTests 4 | // 5 | // Created by Sacha Durand Saint Omer on 06/02/16. 6 | // Copyright © 2016 s4cha. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | @testable import Then 11 | 12 | class ThenTests: XCTestCase { 13 | 14 | override func setUp() { super.setUp() } 15 | override func tearDown() { super.tearDown() } 16 | 17 | func testThen() { 18 | let thenExpectation = expectation(description: "then called") 19 | let finallyExpectation = expectation(description: "Finally called") 20 | fetchUserId() 21 | .then(fetchUserNameFromId) 22 | .then(fetchUserFollowStatusFromName) 23 | .then { isFollowed -> Void in 24 | XCTAssertFalse(isFollowed) 25 | thenExpectation.fulfill() 26 | }.onError { _ in 27 | XCTFail("on Error shouldn't be called") 28 | }.finally { 29 | finallyExpectation.fulfill() 30 | } 31 | waitForExpectations(timeout: 0.5, handler: nil) 32 | } 33 | 34 | func testChainedPromises() { 35 | let thenExpectation = expectation(description: "then called") 36 | fetchUserId() 37 | .then(fetchUserNameFromId(1)) 38 | .then(fetchUserNameFromId(2)) 39 | .then(fetchUserNameFromId(3)).then { name in 40 | print("name :\(name)") 41 | thenExpectation.fulfill() 42 | } 43 | waitForExpectations(timeout: 0.8, handler: nil) 44 | } 45 | 46 | func testChainedPromisesAreExecutedInOrder() { 47 | var count = 0 48 | 49 | let block1 = expectation(description: "block 1 called") 50 | let block2 = expectation(description: "block 2 called") 51 | let block3 = expectation(description: "block 3 called") 52 | 53 | let thenExpectation = expectation(description: "then called") 54 | fetchUserId() 55 | .then(fetchUserNameFromId(1)).then({ _ in 56 | XCTAssertTrue(count == 0) 57 | count+=1 58 | block1.fulfill() 59 | }) 60 | .then(fetchUserNameFromId(2)).then {_ in 61 | XCTAssertTrue(count == 1) 62 | count+=1 63 | block2.fulfill() 64 | } 65 | .then(fetchUserNameFromId(3)).then { _ in 66 | XCTAssertTrue(count == 2) 67 | count+=1 68 | block3.fulfill() 69 | } 70 | .then(fetchUserNameFromId(4)).then { name in 71 | XCTAssertTrue(count == 3) 72 | count+=1 73 | print("name :\(name)") 74 | thenExpectation.fulfill() 75 | } 76 | waitForExpectations(timeout: 0.7, handler: nil) 77 | } 78 | 79 | func testSynchronousChainsWorksProprely() { 80 | globalCount = 0 81 | blockPromiseCExpectation = expectation(description: "block C called") 82 | promiseA() 83 | .then(promiseB()) 84 | .then(promiseC()) 85 | waitForExpectations(timeout: 0.3, handler: nil) 86 | } 87 | 88 | func testClassicThenLaunchesPromise() { 89 | let thenExpectation = expectation(description: "then called") 90 | fetchUserId().then { id in 91 | XCTAssertEqual(id, 1234) 92 | thenExpectation.fulfill() 93 | } 94 | waitForExpectations(timeout: 0.3, handler: nil) 95 | } 96 | 97 | func testMultipleThenBlockCanBeRegisteredOnSamePromise() { 98 | let then1 = expectation(description: "then called") 99 | let then2 = expectation(description: "then called") 100 | let then3 = expectation(description: "then called") 101 | let then4 = expectation(description: "then called") 102 | let p = fetchUserId() 103 | p.then { _ in 104 | then1.fulfill() 105 | } 106 | p.then { _ in 107 | then2.fulfill() 108 | } 109 | p.then { _ in 110 | then3.fulfill() 111 | } 112 | p.then { _ in 113 | then4.fulfill() 114 | } 115 | waitForExpectations(timeout: 0.3, handler: nil) 116 | } 117 | 118 | func testThenWorksAfterErrorBlock() { 119 | let thenExpectation = expectation(description: "then called") 120 | fetchUserId() 121 | .then { _ in 122 | thenExpectation.fulfill() 123 | }.onError { _ in 124 | XCTFail("on Error shouldn't be called") 125 | }.then { 126 | print("Ok bro") 127 | } 128 | waitForExpectations(timeout: 0.3, handler: nil) 129 | } 130 | 131 | func testCanContinueWithThenAfterErrorBlock() { 132 | let thenExpectation = expectation(description: "then called") 133 | let errorExpectation = expectation(description: "Finally called") 134 | failingFetchUserFollowStatusFromName("").then { _ in 135 | XCTFail("testCanContinueWithThenAfterErrorBlock failed") 136 | }.onError { _ in 137 | errorExpectation.fulfill() 138 | }.then { 139 | thenExpectation.fulfill() 140 | } 141 | 142 | waitForExpectations(timeout: 0.5, handler: nil) 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /Tests/ThenTests/TimeoutTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TimeoutTests.swift 3 | // then 4 | // 5 | // Created by Sacha Durand Saint Omer on 10/08/2017. 6 | // Copyright © 2017 s4cha. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | import Then 11 | 12 | class TimeoutTests: XCTestCase { 13 | 14 | func testTimeOutTriggers() { 15 | let e = expectation(description: "") 16 | Promise { resolve, _ in 17 | waitTime(0.1) { 18 | resolve("Hello") 19 | } 20 | }.timeout(0.2).then { string in 21 | XCTAssertEqual(string, "Hello") 22 | e.fulfill() 23 | }.onError { _ in 24 | XCTFail("testTimeOutTriggers failed") 25 | } 26 | waitForExpectations(timeout: 0.3, handler: nil) 27 | } 28 | 29 | func testTimeOutFails() { 30 | let e = expectation(description: "") 31 | Promise { resolve, _ in 32 | waitTime(0.3) { 33 | resolve("Hello") 34 | } 35 | }.timeout(0.1).then { _ in 36 | XCTFail("testTimeOutFails failed") 37 | }.onError { error in 38 | if case PromiseError.timeout = error { 39 | // Good 40 | } else { 41 | XCTFail("testTimeOutFails failed") 42 | } 43 | e.fulfill() 44 | } 45 | 46 | waitForExpectations(timeout: 0.3, handler: nil) 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /Tests/ThenTests/UnwrapTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UnwrapTests.swift 3 | // then 4 | // 5 | // Created by Sacha Durand Saint Omer on 18/03/2017. 6 | // Copyright © 2017 s4cha. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | import Then 11 | 12 | class UnwrapTests: XCTestCase { 13 | 14 | func testUwrap() { 15 | let username: String? = "JohnDoe" 16 | unwrap(username).then { s in 17 | XCTAssertEqual(s, username) 18 | }.onError { _ in 19 | XCTFail("testUwrap failed") 20 | } 21 | } 22 | 23 | func testUwrapFails() { 24 | let username: String? = nil 25 | unwrap(username).then { _ in 26 | XCTFail("testUwrapFails failed") 27 | }.onError { e in 28 | if let pe = e as? PromiseError { 29 | XCTAssertTrue(pe == .unwrappingFailed) 30 | } else { 31 | XCTFail("testUwrapFails failed") 32 | } 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Tests/ThenTests/ValidateTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ValidateTests.swift 3 | // then 4 | // 5 | // Created by Sacha Durand Saint Omer on 22/02/2017. 6 | // Copyright © 2017 s4cha. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | import Then 11 | 12 | class ValidateTests: XCTestCase { 13 | 14 | func testValidateSucceeds() { 15 | let e = expectation(description: "") 16 | Promise.resolve(24) 17 | .validate { $0 > 18 } 18 | .then { _ in 19 | e.fulfill() 20 | } 21 | waitForExpectations(timeout: 0.3, handler: nil) 22 | } 23 | 24 | func testValidateFails() { 25 | let e = expectation(description: "") 26 | Promise.resolve(16) 27 | .validate { ($0 > 18) } 28 | .onError { error in 29 | if let pe = error as? PromiseError { 30 | XCTAssertTrue(pe == .validationFailed) 31 | } else { 32 | XCTFail("testValidateFails failed") 33 | } 34 | e.fulfill() 35 | } 36 | waitForExpectations(timeout: 0.3, handler: nil) 37 | } 38 | 39 | func testValidateWithCustomError() { 40 | let e = expectation(description: "") 41 | Promise.resolve(16) 42 | .validate(withError: MyError.defaultError, { $0 > 18 }) 43 | .onError { error in 44 | if let pe = error as? MyError { 45 | XCTAssertTrue(pe == MyError.defaultError) 46 | } else { 47 | XCTFail("testValidateWithCustomError failed") 48 | } 49 | e.fulfill() 50 | } 51 | waitForExpectations(timeout: 0.3, handler: nil) 52 | } 53 | 54 | func testValidateNotCalledOnError() { 55 | let e = expectation(description: "") 56 | Promise.reject().validate { 57 | XCTFail("testValidateNotCalledOnError failed") 58 | return true 59 | }.finally { 60 | e.fulfill() 61 | } 62 | waitForExpectations(timeout: 0.3, handler: nil) 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /Tests/ThenTests/WhenAllTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // WhenAllTests.swift 3 | // then 4 | // 5 | // Created by Sacha Durand Saint Omer on 08/08/16. 6 | // Copyright © 2016 s4cha. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | import Then 11 | 12 | class WhenAllTests: XCTestCase { 13 | 14 | func testWhenAllAllSynchronousPromises() { 15 | let block = expectation(description: "Block called") 16 | Promises.whenAll(Promise(1), Promise(2), Promise(3), Promise(4)).then { array in 17 | XCTAssertEqual(array, [1, 2, 3, 4]) 18 | block.fulfill() 19 | } 20 | waitForExpectations(timeout: 0.3, handler: nil) 21 | } 22 | 23 | func testWhenAll() { 24 | let block = expectation(description: "Block called") 25 | let promise4 = Promise { resolve, _ in 26 | waitTime(0.1) { 27 | resolve(4) 28 | } 29 | } 30 | Promises.whenAll(promise1(), promise2(), promise3(), promise4).then { array in 31 | XCTAssertEqual(array, [1, 2, 3, 4]) 32 | block.fulfill() 33 | } 34 | waitForExpectations(timeout: 0.3, handler: nil) 35 | } 36 | 37 | func testWhenAllEmpty() { 38 | let block = expectation(description: "Block called") 39 | Promises.whenAll([]).then { (array: [Int]) in 40 | XCTAssertEqual(array, []) 41 | block.fulfill() 42 | } 43 | waitForExpectations(timeout: 0.3, handler: nil) 44 | } 45 | 46 | func testWhenAllArray() { 47 | let block = expectation(description: "Block called") 48 | Promises.whenAll(promiseArray1(), promiseArray2(), promiseArray3()).then { array in 49 | XCTAssertEqual(array, [1, 2, 3, 4, 5, 6, 7, 8, 9]) 50 | block.fulfill() 51 | } 52 | waitForExpectations(timeout: 0.3, handler: nil) 53 | } 54 | 55 | func testLazyWhenAllLazyTrigger() { 56 | var array: [Int] = [] 57 | let block = expectation(description: "Block called") 58 | let promise = Promises.lazyWhenAll(promise1(), promise2()).registerThen { 59 | array = $0 60 | XCTAssertEqual(array, [1, 2]) 61 | } 62 | DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) { 63 | XCTAssertEqual(array, []) 64 | promise.then { 65 | block.fulfill() 66 | } 67 | } 68 | 69 | waitForExpectations(timeout: 0.6, handler: nil) 70 | } 71 | 72 | private let concurrentQueue = DispatchQueue( 73 | label: "then.whenAll.test.concurrent", 74 | qos: .userInitiated, 75 | attributes: .concurrent) 76 | func testWhenAllAllAsynchronous() { 77 | let values = (1...10).map { $0 } 78 | let promises: [Promise] = values.map { value in 79 | return Promise { fulfill, _ in 80 | self.concurrentQueue.async { 81 | fulfill(value) 82 | } 83 | } 84 | } 85 | let block = expectation(description: "Block called") 86 | Promises.whenAll(promises).then { array in 87 | XCTAssertEqual(Set(array), Set(values)) 88 | block.fulfill() 89 | } 90 | waitForExpectations(timeout: 3, handler: nil) 91 | } 92 | 93 | func testLazyWhenAllAllAsynchronous() { 94 | let values = (1...10).map { $0 } 95 | let promises: [Promise] = values.map { value in 96 | return Promise { fulfill, _ in 97 | self.concurrentQueue.async { 98 | fulfill(value) 99 | } 100 | } 101 | } 102 | let block = expectation(description: "Block called") 103 | Promises.lazyWhenAll(promises).then { array in 104 | XCTAssertEqual(Set(array), Set(values)) 105 | block.fulfill() 106 | } 107 | waitForExpectations(timeout: 3, handler: nil) 108 | } 109 | 110 | func testWhenAllCallsOnErrorWhenOneFailsSynchronous() { 111 | let block = expectation(description: "Block called") 112 | let finallyBlock = expectation(description: "Finally called") 113 | let promise1 = Promise { _, reject in 114 | reject(MyError.defaultError) 115 | } 116 | 117 | let callback: ((_ resolve: @escaping ((()) -> Void), _ reject: @escaping ((Error) -> Void)) -> Void) 118 | = { resolve, _ in 119 | resolve(()) 120 | } 121 | 122 | let promise2 = Promise.init(callback: callback) 123 | 124 | Promises.whenAll(promise1, promise2) 125 | .then { _ in 126 | XCTFail("testWhenAllCallsOnErrorWhenOneFailsSynchronous failed") 127 | }.onError { _ in 128 | block.fulfill() 129 | }.finally { 130 | finallyBlock.fulfill() 131 | } 132 | waitForExpectations(timeout: 0.3, handler: nil) 133 | } 134 | 135 | func testWhenAllCallsOnErrorWhenOneFailsAsynchronous() { 136 | let block = expectation(description: "Block called") 137 | let finallyBlock = expectation(description: "Finally called") 138 | let promise1 = Promise { _, reject in 139 | waitTime(0.2) { 140 | reject(MyError.defaultError) 141 | } 142 | } 143 | 144 | let callback: ((_ resolve: @escaping ((()) -> Void), _ reject: @escaping ((Error) -> Void)) -> Void) 145 | = { resolve, _ in 146 | waitTime(0.1) { 147 | _ = resolve(()) 148 | } 149 | } 150 | 151 | let promise2 = Promise.init(callback: callback) 152 | 153 | Promises.whenAll(promise1, promise2) 154 | .then { _ in 155 | XCTFail("testWhenAllCallsOnErrorWhenOneFailsAsynchronous failed") 156 | }.onError { _ in 157 | block.fulfill() 158 | }.finally { 159 | finallyBlock.fulfill() 160 | } 161 | waitForExpectations(timeout: 0.5, handler: nil) 162 | } 163 | } 164 | -------------------------------------------------------------------------------- /Tests/ThenTests/ZipTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ZipTests.swift 3 | // then 4 | // 5 | // Created by Sacha Durand Saint Omer on 10/08/2017. 6 | // Copyright © 2017 s4cha. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | import Then 11 | 12 | class ZipTests: XCTestCase { 13 | 14 | // 2 promises 15 | 16 | func testZipSynchronousPromises() { 17 | let block = expectation(description: "Block called") 18 | Promises.zip(Promise(1), Promise("Hello")).then { int, string in 19 | XCTAssertEqual(int, 1) 20 | XCTAssertEqual(string, "Hello") 21 | block.fulfill() 22 | } 23 | waitForExpectations(timeout: 0.3, handler: nil) 24 | } 25 | 26 | func testZipAsynchronousPromises() { 27 | let block = expectation(description: "Block called") 28 | let p1 = Promise { resolve, _ in 29 | waitTime(0.1) { resolve("Cool") } 30 | } 31 | let p2 = Promise { resolve, _ in 32 | waitTime(0.2) { resolve(23) } 33 | } 34 | Promises.zip(p1, p2).then { string, int in 35 | XCTAssertEqual(string, "Cool") 36 | XCTAssertEqual(int, 23) 37 | block.fulfill() 38 | } 39 | waitForExpectations(timeout: 0.3, handler: nil) 40 | } 41 | 42 | func testZipSynchronousPromisesFails() { 43 | let block = expectation(description: "Block called") 44 | Promises.zip(Promise.reject(), Promise("Hello")).then { _, _ in 45 | XCTFail("testZipSynchronousPromisesFails failed") 46 | }.onError { _ in 47 | block.fulfill() 48 | } 49 | waitForExpectations(timeout: 0.3, handler: nil) 50 | } 51 | 52 | func testZipAsynchronousPromisesFails() { 53 | let block = expectation(description: "Block called") 54 | let p1 = Promise { resolve, _ in 55 | waitTime(0.1) { resolve("Cool") } 56 | } 57 | let p2 = Promise { _, reject in 58 | waitTime(0.2) { reject(PromiseError.default) } 59 | } 60 | Promises.zip(p1, p2).then { _, _ in 61 | XCTFail("testZipAsynchronousPromisesFails failed") 62 | }.onError { _ in 63 | block.fulfill() 64 | } 65 | waitForExpectations(timeout: 0.3, handler: nil) 66 | } 67 | 68 | // 3 promises 69 | 70 | func testZip3SynchronousPromises() { 71 | let block = expectation(description: "Block called") 72 | Promises.zip(Promise(1), Promise("Hello"), Promise(0.45)).then { int, string, double in 73 | XCTAssertEqual(int, 1) 74 | XCTAssertEqual(string, "Hello") 75 | XCTAssertEqual(double, 0.45) 76 | 77 | block.fulfill() 78 | } 79 | waitForExpectations(timeout: 0.3, handler: nil) 80 | } 81 | 82 | func testZip3AsynchronousPromises() { 83 | let block = expectation(description: "Block called") 84 | let p1 = Promise { resolve, _ in 85 | waitTime(0.1) { resolve("Cool") } 86 | } 87 | let p2 = Promise { resolve, _ in 88 | waitTime(0.2) { resolve(23) } 89 | } 90 | let p3 = Promise { resolve, _ in 91 | waitTime(0.1) { resolve(0.45) } 92 | } 93 | Promises.zip(p1, p2, p3).then { string, int, double in 94 | XCTAssertEqual(string, "Cool") 95 | XCTAssertEqual(int, 23) 96 | XCTAssertEqual(double, 0.45) 97 | block.fulfill() 98 | } 99 | waitForExpectations(timeout: 0.3, handler: nil) 100 | } 101 | 102 | func testZip3SynchronousPromisesFails() { 103 | let block = expectation(description: "Block called") 104 | Promises.zip(Promise.reject(), Promise("Hello"), Promise.reject()).then { _, _, _ in 105 | XCTFail("testZip3SynchronousPromisesFails failed") 106 | }.onError { _ in 107 | block.fulfill() 108 | } 109 | waitForExpectations(timeout: 0.3, handler: nil) 110 | } 111 | 112 | func testZip3AsynchronousPromisesFails() { 113 | let block = expectation(description: "Block called") 114 | let p1 = Promise { resolve, _ in 115 | waitTime(0.2) { resolve("Cool") } 116 | } 117 | let p2 = Promise { _, reject in 118 | waitTime(0.1) { reject(PromiseError.default) } 119 | } 120 | let p3 = Promise { resolve, _ in 121 | waitTime(0.1) { resolve(0.45) } 122 | } 123 | Promises.zip(p1, p2, p3).then { _, _, _ in 124 | XCTFail("testZip3AsynchronousPromisesFails failed") 125 | }.onError { _ in 126 | block.fulfill() 127 | } 128 | waitForExpectations(timeout: 0.3, handler: nil) 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/freshOS/Then/709112d17ac9f61060b28c86d61d3f0f9b66c08a/banner.png --------------------------------------------------------------------------------