├── .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 | 
2 |
3 | # Then
4 | [](https://developer.apple.com/swift)
5 | 
6 | [](https://swift.org/package-manager/)
7 | [](https://app.bitrise.io/app/c6b39aea308618bf)
8 | [](https://github.com/freshOS/then/blob/master/LICENSE)
9 | 
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
--------------------------------------------------------------------------------