Compare commits

...

413 Commits

Author SHA1 Message Date
Max Howell
46451dde7d
Add new test 2020-10-04 19:48:23 -04:00
Max Howell
ef9b9ee63d
Fix Swift 5.x warnings 2020-10-04 12:01:43 -04:00
Max Howell
59997dd6d9
Fix NSError().isCancelled crashing 2020-10-04 12:00:25 -04:00
Max Howell
6c954ab704
Updates for Xcode 12 2020-10-04 12:00:24 -04:00
Max Howell
aea48ea185
Update PMKStoreKit to 3.1.4 2020-08-31 09:09:37 -04:00
Max Howell
f6b15b7626
Merge pull request #1154 from SixFiveSoftware/tech/remove_unused_variables
Remove unused index var in when.swift
2020-06-24 10:22:33 -04:00
BJ Miller
817ea8c3f4 Remove unused index var in when.swift 2020-06-19 09:51:43 -04:00
Bruno Virlet
953665d28b Minor correction to chaining sequence example 2020-05-24 18:22:01 -04:00
Luka Bratos
f9cc0d67dc Fix typo 2020-03-14 09:21:50 -04:00
Cong Liu
f95e7b2614 Fixed a typo
"excecutes" should be "executes".
2020-02-24 09:44:41 -05:00
Max Howell
f14f16cc26
Correct Travis labels 2020-01-16 10:29:09 -05:00
Max Howell
80053b1db5
Update PMKHomeKit to 1.1.0
Also syncs other submodules, but they have no source code changes.
2020-01-16 10:29:09 -05:00
Max Howell
34f74c1b8f
Merge pull request #1121 from RomanPodymov/master
More map by keyPath
2020-01-15 17:01:33 -05:00
Roman Podymov
87a0b0e30b
Tests for the functions that are using KeyPath require !swift(>=5.2) (Thenable) 2020-01-14 22:03:22 +01:00
Roman Podymov
98e3f1cd28
Tests for the functions that are using KeyPath require !swift(>=5.2) (Guarantee) 2020-01-14 22:02:07 +01:00
Roman Podymov
8fd461350f
Functions that are using KeyPath require !swift(>=5.2) (Thenable) 2020-01-14 22:01:01 +01:00
Roman Podymov
6652ad3d07
Functions that are using KeyPath require !swift(>=5.2) (Guarantee) 2020-01-14 21:59:42 +01:00
Roman Podymov
9ea7d3daed
Fixed tests ordering 2020-01-08 22:26:55 +01:00
Roman Podymov
bf293b7395
New items in GuaranteeTests and ThenableTests 2020-01-08 21:17:06 +01:00
Roman Podymov
15a73f95cd
Tests for new mapping functions 2020-01-08 21:15:05 +01:00
Roman Podymov
35379bb8a0
struct Person and tests for new mapping functions 2020-01-08 21:12:41 +01:00
Roman Podymov
5fb0714f47
New compactMap, mapValues, compactMapValues, filterValues 2020-01-08 21:10:42 +01:00
Roman Podymov
10c9e75f81
New mapValues, compactMapValues and filterValues 2020-01-08 21:07:59 +01:00
Roman Podymov
f8a593a320 Map by keyPath (#1118)
* Added func map<U>(_ keyPath: KeyPath<T, U>)

* Added swift version

* Added func map<U>(_ keyPath: KeyPath<T, U>)

* testMap

* testMap for Guarantee

* Added missing __allTests__GuaranteeTests

* Added all missing tests

* Removed #if swift

* Tests ordering

* Fixed tests
2020-01-07 17:02:16 +00:00
Max Howell
8b97eb2599
https://tidelift.com/security 2019-12-05 16:28:31 -05:00
Max Howell
586da3519a
Merge pull request #1113 from jfahrenkrug/clarify_faq_about_when_promises_start
Documentation: Clarify when a promise "starts" in the FAQ
2019-11-15 16:18:20 -05:00
Johannes Fahrenkrug
5e2e7e3a75
Documentation: Clarify when promise body gets executed 2019-11-15 11:57:38 -05:00
Johannes Fahrenkrug
8fbf6985b4
Documentation: Clarify when a promise "starts" in the FAQ
Provide a simpler exlanation and examples about when promises "start".
Make it clear that the promise's body executes after the promise is created,
without the need to call `then` or `done` on it. Also clarify that async
tasks that a being kicked off from a promise's body behave the same way
regardless of whether PromiseKit is being used or not.
2019-11-15 11:39:21 -05:00
Max Howell
e135aab767
Merge pull request #1112 from emrcftci/master
refactor(*): Change empty tuples to `Void`
2019-11-13 09:58:38 -05:00
emrcftci
c864814c1d refactor(*): Change empty tuples to Void 2019-11-13 08:54:47 +03:00
Max Howell
e36de47eac
Merge pull request #1097 from mxcl/swift-5.1-linux
Swift 5.1 release travis linux
2019-10-18 15:09:57 -04:00
Max Howell
f7e53175f2
Swift 5.1 release travis linux 2019-09-25 13:26:24 -04:00
Roman Podymov
10a10ed8bd Promise.value and Guarantee.value when T == Void (#1096)
* Promise.value when T == Void

* Guarantee.value where T == Void

* Replace class with static because Promise is final

* Optimised Guarantee.value where Value == Void

* Add comment

* Implementation with Void()

* Tests for Promise<Void>.value

* Tests for Guarantee<Void>.value

* Added tests for Promise<Void> and Guarantee<Void>
2019-09-25 13:24:39 -04:00
Max Howell
08fb076071
Update CommonPatterns.md 2019-09-04 14:01:49 -04:00
Max Howell
4d8d1287d2
Merge pull request #1090 from mxcl/xcode10.3
[ci] Xcode 10.2 -> 10.3
2019-08-23 10:04:30 -04:00
Max Howell
2136f01c21
Tidelift can ignore node 2019-08-23 08:58:05 -04:00
Max Howell
2305c38634
Only enable errors as warnings in Travis 2019-08-22 09:42:56 -04:00
Max Howell
b8c2dff1a7
Fix git submodules
Cannot push to CoreBluetooth since I archived it
2019-08-21 13:21:58 -04:00
Max Howell
346dd2c98d
[ci] Xcode 10.2 -> 10.3 2019-08-21 13:10:54 -04:00
Max Howell
bee0497eb9
Merge pull request #1086 from Skoti/feature/correcting_descriptions
correcting and aligning documentation regarding `resolve` and `fulfil…
2019-08-13 07:27:29 -04:00
Skoti
93a098f33b correcting and aligning documentation regarding resolve and fulfill usages 2019-08-10 16:56:26 +02:00
Max Howell
8818af27ba
Update Foundation extension with fix (ee06d95) 2019-07-24 10:50:13 -04:00
Max Howell
951993371a
Update gitsubmodules to LICENSE added extensions 2019-07-24 10:39:13 -04:00
Max Howell
8e5f5d0945
Merge pull request #1079 from mxcl/xcode11
Xcode 11 / Swift 5.1 support
2019-07-09 17:22:17 -04:00
Max Howell
c394b5231e
Xcode 11 / Swift 5.1 support 2019-07-09 15:07:50 -04:00
Max Howell
2fdb8c64a8
Merge pull request #1078 from RomanPodymov/master
Add AnyPromise.wait
2019-06-28 10:23:21 -04:00
Roman Podymov
6303e93089
Update AnyPromiseTests.m 2019-06-28 12:58:15 +02:00
Roman Podymov
9cd56c237d
Update AnyPromiseTests.m 2019-06-28 12:33:21 +02:00
Roman Podymov
ee961e1f59
Update AnyPromise.swift 2019-06-28 12:03:32 +02:00
Roman Podymov
0452552590
Update AnyPromise.m 2019-06-28 12:02:03 +02:00
Roman Podymov
fbe6decc6e
Update AnyPromise.h 2019-06-28 12:00:52 +02:00
Max Howell
a2cc02d3ac
Create FUNDING.yml 2019-06-18 10:32:14 -04:00
Max Howell
cfea84ff08
Merge pull request #1074 from Igor-Palaguta/GuaranteeSequenceOperators
Add guarantee sequence utils
2019-06-18 07:57:08 -04:00
Igor Palaguta
7d3028e0ee update test manifest 2019-06-17 23:36:58 +02:00
Igor Palaguta
9f9f1f9e89 Add guarantee sequence utils 2019-06-17 17:45:55 +02:00
Max Howell
bed5229ba7 Remove Xcode 11 warnings 2019-06-11 12:49:24 -04:00
Max Howell
1c296a8637
[travis] Use CocoaPods ~> 1.7.0; update submodules 2019-06-11 10:40:35 -04:00
Max Howell
fe1e9c5b62
Deployment updates 2019-04-07 13:03:15 -04:00
Max Howell
93d7906334
Update extensions 2019-04-07 13:03:14 -04:00
Max Howell
6cfdb23235 Use Swift 5 to generate linuxmain 2019-04-07 13:01:20 -04:00
Max Howell
74402dcaae Fix for Swift 5 in 4.2 mode on Linux 2019-04-07 12:56:40 -04:00
Max Howell
b4f87a57c5
Merge pull request #1041 from AccioSupport/master
[README] Add Accio to badge of package managers
2019-04-06 11:09:56 -04:00
Cihat Gündüz
286225ffcd
[Installation.md] Document installation via Accio 2019-04-06 11:09:55 -04:00
Cihat Gündüz
cf437b7010
[README] Update recommended installation guide
Note that since Accio is using Carthage for building, but has some additional features like improved caching,
it should be considered as recommendable as Carthage is. Also it is designed towards SwiftPM.
2019-04-06 11:09:55 -04:00
Cihat Gündüz
c6c0d4b97d
[README] Add Accio to badge of package managers 2019-04-06 11:09:54 -04:00
Max Howell
e0176e7886
Merge pull request #1030 from mxcl/travis-Swift-5.0-GM
[travis] Swift 5 GM
2019-03-25 22:45:29 -04:00
Max Howell
60215282b6
[travis] Swift 5 GM; Fixes Linux build 2019-03-25 21:37:03 -04:00
Max Howell
91f002f6f6
mxcl.github.io|promisekit.org -> mxcl.dev
* Fixes broken links
* Corrects spelling

[skip ci]
2019-03-01 00:02:04 -05:00
Max Howell
b4176bd4a3
Merge pull request #1015 from mxcl/cocoapods-1.7.0-beta.1
Cocoapods 1.7.0-beta supports `swift_versions`
2019-02-28 15:05:06 -05:00
Max Howell
d215487cbd
Merge pull request #1019 from NextFaze/fix-typo
Fix typo
2019-02-27 17:56:38 -05:00
Ricardo Santos
662ce62593 Fix typo 2019-02-28 09:25:11 +10:30
Max Howell
17210905c9
Cocoapods 1.7.0-beta supports swift_versions 2019-02-25 08:56:49 -05:00
Max Howell
322bf3ed3c
[ci skip] Document better 2019-02-13 15:31:40 -05:00
Max Howell
ae9b3103bc
Merge pull request #1011 from mxcl/ci-swift5
[ci] pod lib lint swift 5
2019-02-12 17:48:52 -05:00
Max Howell
40ffd806f4 Update documentation links
[skip ci]
2019-02-12 17:46:50 -05:00
Max Howell
8658196da7
[ci] pod lib lint swift 5 2019-02-12 15:13:51 -05:00
Max Howell
69b112b28e
Deprecate promisekit.org
[ci skip]
2019-02-11 19:44:11 -05:00
Max Howell
cdc7f7dfaa [skip ci] Use CocoaPods 1.6.0 2019-02-06 19:39:21 -05:00
Max Howell
716f9c2d24 Update documentation links
[skip ci]

Fixes #1008
2019-02-05 15:41:58 -05:00
Max Howell
62dd9b29fd
Badge updates; Deploy fixes
[skip ci]
2019-02-02 12:56:32 -05:00
Max Howell
db3b2aaf0b
Tag 6.8.3 2019-02-01 18:32:41 -05:00
Max Howell
adb7de9d36
Merge pull request #1005 from mxcl/reduce-package-swift-deployment-targets
Only pod has these higher deployment requirements
2019-02-01 16:57:17 -05:00
Max Howell
55a65c55d5 Only pod has these higher deployment requirements 2019-02-01 15:53:45 -05:00
Max Howell
b91033b26d
Merge branch 'xcode-10.2-beta1' 2019-02-01 15:30:28 -05:00
Max Howell
c94097db08
+pipefail 2019-02-01 15:30:15 -05:00
Max Howell
ace34902a5
Test Swift 5 on Linux 2019-01-29 13:34:21 -05:00
Max Howell
c525a6bb4e
Travis tweaks 2019-01-29 13:34:21 -05:00
Max Howell
7f4cc6ddb7
Remove Xcode tests for Swift 3.x
We still do the `pod lint` and `swift build`
2019-01-29 13:34:21 -05:00
Max Howell
65c42e2e13
Xcode 10.2 / Swift 5 support
Note we had to bump the Xcodeproj to Swift 4, which is thew new minimum that is supported for Xcode 10.2.

This will affect Carthage users.
2019-01-29 13:34:21 -05:00
Max Howell
5e80127dce
Build Jazzy docs when deploying 2019-01-29 13:34:21 -05:00
Max Howell
e2b50a0e35
Add Swift 5 manifest 2019-01-29 13:32:26 -05:00
Max Howell
83bb0f1ae7
Add release script
Update travis cocoapods version we install

Add tagging to ranger configuration.
2019-01-29 13:31:06 -05:00
Max Howell
be6666f4f6 Tag 6.8.2 2019-01-28 19:43:53 -05:00
repo-ranger[bot]
d513112d77
Merge pull request #1002 from mxcl/fixes-1001
Fixes 1001
2019-01-28 21:26:39 +00:00
Max Howell
6264f6f41e Fixes #1001
The box can outlive its Guarantee, and usually this is what happens. Thus we have to log in the box and not in the Guarantee itself.
2019-01-28 15:39:57 -05:00
Max Howell
55c762b96a
Update PromiseKit/EventKit to v4 2019-01-27 18:40:27 -05:00
Max Howell
cc89b4fe34
Update PromiseKit/EventKit to v4 2019-01-27 16:16:44 -05:00
Max Howell
71ec2c22c0
Merge pull request #1000 from mxcl/fix-travis
The distinction between these two is vital
2019-01-27 16:06:41 -05:00
Max Howell
6204855078 The distinction between these two is vital 2019-01-27 14:07:34 -05:00
Max Howell
acf70ac56c
Merge pull request #996 from mxcl/swift-5-ci
Start running CI against Swift 5 snapshots
2019-01-24 13:47:10 -05:00
Max Howell
c87c8c9ba6
Start running CI against Swift 5 snapshots 2019-01-24 11:45:41 -05:00
Max Howell
6a65a39d50
Merge pull request #994 from mxcl/travis-tweak
Reduce № Travis Jobs for standard builds
2019-01-24 11:38:26 -05:00
Max Howell
b9866dc906
Master builds have less jobs 2019-01-24 11:33:21 -05:00
Max Howell
82acba4636
Merge pull request #993 from mxcl/validate-linux-coverage
Validate that Linux has full test coverage
2019-01-24 01:48:14 -05:00
Max Howell
7d5d5352e6
Validate that Linux has full test coverage 2019-01-23 22:46:11 -05:00
Max Howell
bef8971416 Tag 6.8.0 2019-01-23 22:44:42 -05:00
repo-ranger[bot]
c531710216
Merge pull request #991 from mxcl/fixes-990
Reverts #963 due to ambiguity (see #990)
2019-01-23 23:01:25 +00:00
Max Howell
1c958cfdb6
Reverts #963 due to ambiguity (see #990) 2019-01-23 17:19:00 -05:00
Neal Lester
ecb719a441 Log when unfulfilled guarantee is deallocated
* Fixed bug where pendingPromiseDeallocated event was incorrectly reported as a waitOnMainThread event.
* Now logs if a Guarantee is deallocated before it has been fulfilled.
2019-01-23 14:04:53 -05:00
Max Howell
dfb8cfcbb7
Add other donation/tipping options 2019-01-19 19:55:24 -05:00
Max Howell
898d7cda93
Merge pull request #988 from imzyf/master
docs: fix 'after' docs position
2019-01-18 23:57:20 -05:00
yifans
3bb8e86c27 docs: fix 'after' docs position 2019-01-19 11:18:24 +08:00
Max Howell
9641b208c0
Merge pull request #986 from VFUC/patch-1
Use PackageDescription API v4 in Installation.md
2019-01-17 15:09:47 -05:00
Jonas
e209fcbb7d
Use PackageDescription API v4 in Installation.md
Updates Installation.md to use PackageDescription API v4
2019-01-17 21:08:06 +01:00
Max Howell
c2fe0272fc
[ci skip] Add detweet example 2019-01-15 13:24:16 -05:00
Max Howell
8218b667bf
Fixes #984 2019-01-15 11:49:36 -05:00
Max Howell
381348ba1d
Use Patreon image button 2019-01-14 21:54:27 -05:00
Max Howell
557593f258
Update ISSUE_TEMPLATE.md 2019-01-06 16:24:14 -05:00
Max Howell
4a069a6af8
Add TideLift link 2019-01-04 18:36:58 -05:00
Max Howell
30dd840f40
v4 Supports Xcode 10 2018-12-27 10:11:05 +00:00
Max Howell
8054c287cf Warnings fixes for Swift 5 2018-12-23 10:44:52 +00:00
Max Howell
485131b92d Tag 6.7.1 2018-12-20 19:26:47 +00:00
Max Howell
02e84ce4b1
Update ObjectiveC.md 2018-12-14 22:18:26 +00:00
Neal Lester
5bb2aae8cc Fixed bug where pendingPromiseDeallocated event was incorrectly repor… (#972)
* Fixed bug where pendingPromiseDeallocated event was incorrectly reported as a waitOnMainThread event. Added test which should have caught this bug in the first place.

* Kick Travis CI
2018-12-14 16:47:17 +00:00
Max Howell
358ed97c6d
Update Logging FAQ 2018-12-04 18:52:56 -05:00
Max Howell
80d16f9471 Tag 6.7.0 2018-12-04 17:28:50 -05:00
Max Howell
3ae22b0d54
Merge pull request #966 from mxcl/tidy
Tidy
2018-12-04 17:28:46 -05:00
Max Howell
5b72d28396 Rename loggingClosure to logHandler
This is more consistent with Apple API naming.
2018-12-04 15:01:48 -05:00
Max Howell
e9f3df1cab Tidy 2018-12-04 15:01:07 -05:00
Max Howell
b0d3223d98 Rename 2018-12-04 15:00:43 -05:00
Max Howell
0fe0366145 Delete spurious source file 2018-12-04 15:00:29 -05:00
Neal Lester
a5ab1ea5b3 Added facilities for controlling console output
Refs #961
2018-12-04 14:48:58 -05:00
Max Howell
019d21dbbf 6.6.1: Integrate HealthKit.HKActivitySummaryQuery 2018-12-02 12:43:43 -05:00
Max Howell
3d544c4f5b
Update CommonPatterns.md 2018-11-29 20:23:15 -05:00
Max Howell
8dd5f7b15c Tag 6.6.0 2018-11-29 14:00:37 -05:00
Max Howell
4e78705bac Keep code-coverage up; Keep docs coverage up 2018-11-29 12:54:13 -05:00
Max Howell
16f9347232
Merge pull request #963 from LarsStegman/add-void-fulfill
Create void fulfill function
2018-11-29 12:50:25 -05:00
Lars Stegman
33e7eba60b Create fulfill function for void promises
I find it annoying to have to add a Void value every time I need to fulfill a Void promise. It can also be confusing how to fulfill Void promises for people who are not familiar with how to instantiate a Void value.
2018-11-29 10:14:24 +01:00
Max Howell
5c8516a1ce Try to make Travis deploy work 2018-11-26 14:15:56 -05:00
Max Howell
283fd55f34 Tidy up bad additions 2018-11-26 13:23:27 -05:00
Max Howell
a7fea1ba9f
Update Configuration.swift 2018-11-24 17:57:27 -05:00
Max Howell
1f09d72bb7 v6.5.3 2018-11-16 14:34:12 -05:00
Max Howell
4772f165c9
Merge pull request #958 from mxcl/update-extensions
Update QuartzCore (sync other extensions)
2018-11-16 14:34:00 -05:00
Max Howell
7d2588dd47 Update QuartzCore (sync other extensions) 2018-11-16 14:05:41 -05:00
Max Howell
321d22b600
Ambiguous reference to firstly 2018-11-14 14:14:39 -05:00
Max Howell
ff97aa9dcb
Merge pull request #956 from mxcl/final
Add `final` where possible
2018-11-07 13:32:25 -05:00
Max Howell
41699e222d Use final where possible
Allows the optimizer to remove dynamic dispatch, being `public` rather than `open` is not enough.

https://twitter.com/mxcl/status/1060222414829838336
2018-11-07 13:04:01 -05:00
Max Howell
e9db9cfdb3 [ci] Xcode 10.1; Linux Swift 4.2.1 2018-11-07 13:02:11 -05:00
Max Howell
a4b4de83fb
Include more information for not resolving 2018-11-05 13:32:35 -05:00
Max Howell
4ccb1c8930
Move up the donation request, per user-feedback 2018-10-26 20:00:55 -04:00
Max Howell
006640cc6b
Merge pull request #946 from djmixroman/master
Fixed mistake with barrier on serial queue
2018-10-24 13:22:07 -04:00
Roman Mogutnov
3e7f15672b qos removed 2018-10-24 13:41:45 +03:00
Roman Mogutnov
18e7cf024f Changed on concurrent queue 2018-10-23 12:00:52 +03:00
Roman Mogutnov
b744c04456 Fixed mistake with barrier on serial queue 2018-10-22 19:15:21 +03:00
Max Howell
a0f880dd4a
Merge pull request #942 from allending/patch-1
Fix typo in GettingStarted.md
2018-10-17 11:26:43 -04:00
Allen Ding
035ccea865
Fix typo in GettingStarted.md 2018-10-17 14:12:33 +08:00
Max Howell
eba24dd36b
Add Patreon Link 2018-10-16 12:25:26 -04:00
Max Howell
289b6a8e37
More and more Travis tweaks
* Attempting to get Travis to deploy
* Minimize Travis matrix. I believe this tests everything that matters, including older SDK and deployment targets while being kinder to Travis’s free infrastructure.
* Only run the JS/A+ tests for that test group
* Re-enable uploading to codecov
* Use xcpretty
2018-10-13 17:37:44 -04:00
Max Howell
fbe14e6bec Demonstrate how to sequence [Promise] 2018-10-12 13:55:42 -04:00
Max Howell
66bcc38616 Tag 6.5.2
Attempts deploy based on: 

https://github.com/travis-ci/travis-ci/issues/8518
2018-10-10 17:11:00 -04:00
Max Howell
c5deb32c1b
Add Agnosti.tech link 2018-10-08 11:54:20 -04:00
Max Howell
050061aaec Document how to remove Xcode warning due to pod 2018-10-08 10:58:45 -04:00
Max Howell
4bb3cc2e81
Document how shit CocoaPods is 2018-10-08 10:41:59 -04:00
Max Howell
d15b6d7418 Synchronize extensions; Tag 6.5.1 2018-10-07 17:33:45 -04:00
Max Howell
55c311a672
Attempt to deploy with Travis when tags are made
Rewrite with YAML aliases and anchors.
2018-10-05 18:26:13 -04:00
Max Howell
9e5bc43281 Do a quick lint of CorePromise for all PLAT=macOS (#934)
* Verifies that we support CocoaPods for all Xcodes we claim
* Will fail for any warnings on any platform/Xcode combination
* Removes the `swift_version` from the Podspec as this makes CocoaPods unable to actually support all the Swift versions we support as well as refuse to even lint in CI for our chosen Swift version overrides
2018-10-05 10:51:42 -04:00
Max Howell
cdfeef252b
Document creating Guarantees 2018-10-04 13:34:20 -04:00
Max Howell
1706cefd6c Deployment would be too hard due to our matrix
Re-enabling master builds, but only for master, and should only happen for pushes apparently.

I disabled the other stuff due to overburdening Travis.
2018-10-03 19:10:39 -04:00
Max Howell
82d3d5f71a Tag 6.5.0 2018-10-03 18:46:02 -04:00
Max Howell
48a81780aa
Merge pull request #932 from mxcl/health-kit
Add PMKHealthKit
2018-10-03 18:40:41 -04:00
Max Howell
98e896207d Wrong iOS deployment target for HealthKit 2018-10-03 18:02:24 -04:00
Max Howell
3f54058f4d Add PMKHealthKit
Closes #920
2018-10-03 18:02:24 -04:00
Max Howell
8b625a4ae3
[ci] Run pod lib lint (#933)
Do `pod lib lint` for one variant

This will prevent us getting to the release stage and finding we have issues. Also it is a very thorough set of tests.
2018-10-03 18:02:01 -04:00
Max Howell
72f3581d68 Fix travis 2018-10-03 13:49:25 -04:00
Max Howell
4ff9962082 SKError still is not a real Error, hard-code these
Not ideal, but there’s also no harm in it.
2018-10-02 17:08:38 -04:00
Max Howell
41422856c1 Our Travis image already comes with node
However, it is old and doesn’t have the `ci` command, which stinks.
2018-10-02 17:08:28 -04:00
Max Howell
4d8e0d852f Travis stinks
Green builds when actually things failed? Thanks Travis!
2018-10-02 17:08:28 -04:00
Max Howell
5585c0e24e Change codecov settings 2018-10-02 17:08:28 -04:00
Max Howell
3a212e7275 Merge warnings fixes from extensions 2018-10-02 15:30:34 -04:00
Max Howell
3a281ac8e0 Generate build.js on CI; rm -rf from sources 2018-09-28 14:51:51 -04:00
Max Howell
d3dfe22243 Increase number of sequential thens in this test
Refs #930
2018-09-28 09:53:29 -04:00
Max Howell
1f3564887e
Clarify when(resolved:) behavior 2018-09-27 13:37:40 -04:00
Max Howell
ad0c4d3c24
Merge pull request #927 from chrisscholly/bump-version
Bump version to `v6.4.1` to match latest release
2018-09-24 17:24:52 +02:00
Chris Scholly
fc5a4b9acd Bump version to v6.4.1 to match last release 2018-09-24 16:56:07 +02:00
Victor Hugo Barros
4cd8a0d94b Update Package.swift (#923)
* Update Package.swift

Updating Package.swift to add support for Swift 4.2

* adding support for Swift 4.2 without break support for older versions of Swift 4
2018-09-23 11:21:43 +02:00
Max Howell
f7e4db86a1 Add HomeKit extension; Tag 6.4.0 2018-08-22 12:01:01 -04:00
Max Howell
c3f6bdca01 Tag 6.3.5 2018-08-21 13:41:17 -04:00
Max Howell
16320376c4 Xcode 10 / Swift 4.2 fixes and tweaks 2018-08-20 16:27:40 -04:00
Max Howell
1049e645bb
PromiseKit/OMGHTTPURLRQ is deprecated 2018-08-19 23:01:23 -04:00
Max Howell
a9a7c23fba
Test 3.2 on Linux (we weren't actually) (#914) 2018-08-19 20:49:40 -04:00
Max Howell
c7dad79d1b
Fix travis (#912)
Actually run the tests for the ones we want them
2018-08-19 14:44:46 -04:00
Max Howell
5355e13321 Synchronize Foundation 2018-08-19 12:11:53 -04:00
Max Howell
783684fd3d Add Xcode 10 to Travis matrix 2018-08-19 12:11:53 -04:00
Max Howell
d6f24e2b0d
Update server-side suggestions 2018-08-15 16:12:49 -04:00
Max Howell
bf8cc0bf3d
Test more Swift versions on Linux (#909) 2018-08-09 13:13:47 -04:00
Max Howell
6e8c3aa189 Merge 6.3.4 2018-08-09 10:19:47 -04:00
Max Howell
ca0c4e6369
[FAQ] How do I create a fulfilled Void promise?
[ci skip]
2018-07-23 12:12:53 -04:00
Max Howell
e50702aa12
[FAQ] How do I early return?
[ci skip]
2018-07-23 12:11:25 -04:00
Max Howell
ebc1991999
Add .validate() 2018-07-18 11:42:52 -04:00
Max Howell
cd4734cd7b
Merge pull request #897 from DimaRU/docfix
Update documentation referencing `.allErrorsIncludingCancellation`
2018-07-18 11:41:21 -04:00
Dmitriy Borovikov
1238d4e764 Update documentation referencing .allErrorsIncludingCancellation 2018-07-18 11:11:29 +03:00
Max Howell
f70416ab7b Tag 6.3.4 2018-06-28 12:00:28 -04:00
Max Howell
cad1c1f230
Merge pull request #891 from GarthSnyder/docfix
Doc headings to title case, no Oxford commas
2018-06-27 14:50:53 -04:00
Garth Snyder
2b88cac8e0 Doc headings to title case, no Oxford commas 2018-06-27 10:50:56 -07:00
Garth Snyder
caa3322070 Minor copy editing of RxSwift section 2018-06-27 10:50:56 -07:00
Max Howell
f035696eb4
Merge pull request #890 from dermaaarkus/889-guarantee-get-returns-promise
implement guarantee get returns guarantee
2018-06-27 10:53:38 -04:00
markus.fassbender
2c44682f44 implement guarantee get 2018-06-27 14:12:09 +02:00
Max Howell
b367153edd Merge “Tap on queue” 2018-06-25 10:06:05 -04:00
Max Howell
fa9dc3ad6a Fix tests on Linux for Swift 4.2
Can't run the tests on 4.2 because #if swift doesn't seem to work and we need that because for some reason #line has become `Int`.
2018-06-21 12:14:44 -04:00
Igor Palaguta
e88d30e150 fix tap documentation comments 2018-06-21 16:41:16 +03:00
Igor Palaguta
59fb55e886 Tap should perform body on specified queue, or on queue from configuration 2018-06-21 16:11:02 +03:00
Max Howell
2e44168aba Attempt to build Linux against 4.2-dev-snapshot 2018-06-21 01:44:25 -04:00
Max Howell
7159cf8484 Update troubleshooting and issue template
I get so many tickets that are answered in the GD Troubleshooting guide it makes me want to quit open source.

[ci skip]
2018-06-21 01:22:30 -04:00
Max Howell
20570d3a0f Sync all subspecs (no changes to libs, just tests)
Ever more Travis tweaks
2018-06-21 01:10:31 -04:00
Max Howell
d0f17302da
Merge pull request #881 from GarthSnyder/master
Documentation copy editing, add more RxSwift info, fix asVoid() example
2018-06-19 08:53:17 -04:00
Garth Snyder
fecf4f8430 Documentation copy editing, add more RxSwift info, fix asVoid() example 2018-06-17 15:37:09 -07:00
Max Howell
264ba1f22e Only run tests for latest SDKs; Tag 6.3.3
Really doing it for all other variants is gratitous.

Travis doesn't actually provide all tvOS and watchOS (older SDK) simulators like they claim.
2018-06-17 16:46:33 -04:00
Max Howell
b96bb7be4b Add firstValue(where:) 2018-06-17 14:10:51 -04:00
Max Howell
6d75d00791 Return the PMKFinalizer from cauterize() 2018-06-17 14:10:51 -04:00
Max Howell
cee308837f
We meant return 2018-06-17 11:23:24 -04:00
Max Howell
b71c725d64 Update Travis to Xcode 9.4 2018-06-16 11:34:46 -04:00
Max Howell
efcccf58a0 More tests for finally(on:); Tag 6.3.2 2018-06-15 12:26:20 -04:00
Max Howell
a917914b6b
Merge pull request #876 from mayurdzk/mayur/accept-queue-in-finally
Accept a queue in finally()
2018-06-15 12:18:45 -04:00
mayurdzk
4b4b30e19a Accept a queue in finally() 2018-06-15 21:17:48 +05:30
Max Howell
aa815656b7 Tag 6.3.1 2018-06-14 11:43:01 -04:00
Max Howell
b25670748b
Merge pull request #877 from bitwit/master
Update 'Core' test target name to 'CorePromise'
2018-06-14 11:41:59 -04:00
Kyle Newsome
9d70282857 Update 'Core' test target name to 'CorePromise'
More descriptive name avoids target conflicts during swift build process
when depending on multiple packages that also have a 'Core' target

- Fixes Test: LinuxMain import of Core
2018-06-13 13:34:46 -04:00
Max Howell
6b9aa80db5 Make Travis build tests against the latest SDKs
The tests often use newer features, so let’s let the Xcode project itself make the decision about which SDK it is built against and deployed to.

This is set to “latest” (sort of–at least–that is what is picked)

wip
2018-06-11 15:08:18 -04:00
Max Howell
da3ace8319 [skip ci] Ugh, move this back again
CocoaPods insists that this file be in the root, which is dumb. So I blame their inflexible and shit tooling.
2018-06-11 14:08:28 -04:00
Max Howell
d5472e4711 Bump PMKFoundation to 3.1.0 2018-06-11 13:27:31 -04:00
Max Howell
f846058c3a Fix tests on Linux 2018-06-10 17:57:14 -04:00
Max Howell
851723fbc8
Merge pull request #867 from amedakedo/updateDocs
Update Objective-C Documentation
2018-06-10 14:49:21 -04:00
Max Howell
ceda083298 Bump podspec deployments for subspecs
Due to CorePromise having 10.10, subspecs must specify at least this, even though, you know, CocoaPods could give a shit about my DX and automatically infer this.

SIGH, here comes another 40 minute attempt to push.
2018-06-10 14:26:34 -04:00
Max Howell
d427b29dd7 Allow specifying DispatchWorkItemFlags; Tag 6.3.0
This has recently become useful to me, though this is for experienced users only.
2018-06-10 13:24:26 -04:00
Max Howell
fd5d23c43d Fix doc comment 2018-06-10 12:32:25 -04:00
Max Howell
4779f3aab1
Merge pull request #872 from drekka/feature/Guarantee-thenMap
Guarantee.thenMap which returns a Guarantee
2018-06-10 12:28:32 -04:00
Derek Clarkson
bb3b294434 Converting to use a fatalError if an error is actually thrown. 2018-06-10 18:38:27 +10:00
Derek Clarkson
5f7f09f56f Updating test to wait for results 2018-06-10 18:28:04 +10:00
Derek Clarkson
35cdf54a81 Removing throws support as not needed. 2018-06-10 00:01:20 +10:00
Derek Clarkson
89c58298a1 Guarantee then map 2018-06-09 22:27:00 +10:00
Amedakedo
98f89110ce Update Objective-C Documentation
Throwing errors in objective-c could lead to memory leaks. Updated the
documentation regarding this problem.
2018-06-07 00:08:53 +09:00
Max Howell
3e6763e3ae Bump CloudKit deployment targets 2018-06-04 23:42:27 -04:00
Max Howell
4c69e228aa Confirmed that we support Swift 4.2 & Xcode 10 2018-06-04 19:18:17 -04:00
Max Howell
be56acdce9 Fix the playground (been broken a while now!) 2018-06-04 19:18:17 -04:00
Max Howell
e2d8a05dab FAQ about who retains what 2018-06-01 12:04:28 -04:00
Max Howell
fc06b42e24
Merge pull request #865 from GarthSnyder/master
Copy editing for FAQ.md
2018-05-30 22:29:14 -04:00
Garth Snyder
1af7599572 Copy editing for FAQ.md 2018-05-30 17:46:32 -07:00
Max Howell
d39d3371e5
Merge pull request #863 from nickmshelley/example-update
Update example to use JSONEncoder.
2018-05-24 19:08:24 -04:00
Nick Shelley
db3c60ac34 Update example to use JSONEncoder. 2018-05-24 13:24:51 -06:00
Max Howell
804409d125 Tag 6.2.8 2018-05-24 14:50:36 -04:00
Max Howell
d858c66b0b
Merge pull request #859 from evgenich95/fix-cocoapods-1.5
Fixed build error in AnyPromise.m
2018-05-24 12:06:06 -04:00
Max Howell
9d3b310f3a
Merge pull request #860 from johannesd/master
Added link to CancellablePromiseKit
2018-05-19 17:54:59 -04:00
Johannes Dörr
84f1f29005 Added link to CancellablePromiseKit 2018-05-19 21:05:57 +02:00
Ivanov Anton
2d2729f508
Fixed build error in AnyPromise.m
When installing PromiseKit 6 as a static library via CocoaPods.
The problem is described in the following issue - https://github.com/mxcl/PromiseKit/issues/825.
2018-05-16 17:25:21 +07:00
Max Howell
7dba562194 Bump to 6.2.7 2018-05-15 17:03:42 -04:00
Max Howell
ec82fac9c7
Merge pull request #856 from ephemer/patch-1
Fix build issue in hang.swift
2018-05-15 17:01:30 -04:00
Geordie J
f31ee94ed6
Fix build issue in hang.swift 2018-05-15 17:58:07 +02:00
Max Howell
356af9ca05 Update PromiseKit/CoreLocation@3.0.6 2018-05-08 21:54:34 -04:00
Max Howell
18b2591915
Update Troubleshooting.md 2018-05-08 19:32:38 -04:00
Max Howell
52c1d2e26d Complete documentation 2018-05-04 22:25:34 -04:00
Max Howell
b4131f9d11
Refer to the API Reference 2018-05-03 23:28:21 -05:00
Max Howell
dbee4124f4 Add API Reference; remove outdated translations 2018-05-03 23:14:15 -05:00
Max Howell
69459cfe7e
Update Appendix.md 2018-05-03 22:46:05 -05:00
Max Howell
2e562d4b12
NSError.cancelledError is no longer provided. 2018-05-03 22:27:02 -05:00
Max Howell
c1be79b3e9 Update PromiseKit/CoreLocation@3.0.5 2018-04-27 15:16:52 -04:00
Max Howell
b2d72f6dd3 Document Void tuplegate (again)
Somehow this part of the troubleshooting guide vanished.
2018-04-27 15:14:29 -04:00
Max Howell
14b8506adb Fix ensureThen documentation; Tag 6.2.5 2018-04-20 15:59:52 -04:00
Max Howell
e9e85a37a7
Merge pull request #840 from mxcl/pr6.2.5
v6.2.5
2018-04-20 15:57:57 -04:00
Max Howell
157a241132 Update PromiseKit/CoreLocation@3.0.4 2018-04-20 13:44:36 -04:00
Max Howell
cafee2499a Update PromiseKit/StoreKit@3.1.0 2018-04-20 13:38:17 -04:00
Max Howell
1c373d02ec Enable treat warnings as errors 2018-04-20 13:38:17 -04:00
Max Howell
954b9fee55 [travis] Switch most of the matrix to 9.3 2018-04-20 13:38:17 -04:00
Max Howell
8992b4f4ce
Merge pull request #841 from mxcl/ensureThen
Add ensureThen
2018-04-20 13:27:00 -04:00
Max Howell
c667ec78cc Add ensureThen
Couldn’t have two variants of `ensure` without the dreaded ambiguity issues.

Sadly Swift cannot infer the type for multiline closures where there are two possible functions for it to match. :(
2018-04-19 21:43:12 -04:00
Max Howell
98eaf180ed
Merge pull request #839 from HarrisHao/master
[ObjC] Fix error leak and add test case
2018-04-19 11:28:25 -04:00
zhuhao
6a0d8ceeba [ObjC] Fix error leak 2018-04-19 17:21:35 +08:00
Max Howell
990a9f1c26 Update Travis Xcode to 9.3 (from 9.3-beta) 2018-04-15 21:39:39 -04:00
Max Howell
560ef78a9b
Merge pull request #830 from bryansum/patch-1
Update documentation referencing `flatMap`
2018-04-09 13:26:40 -04:00
Bryan Summersett
f0056bdb1e
Update documentation referencing flatMap
Updating references to `flatMap` to `compactMap` universally throughout the documentation.
2018-04-09 13:24:25 -04:00
Max Howell
3173728d52 Tag 6.2.4 2018-04-04 23:48:51 -04:00
Colin T.A. Gray
6f20a190c3 Allow setting nil for conf.Q 2018-04-04 23:21:26 -04:00
Max Howell
54eaddaf65 Tweak ISSUE_TEMPLATE
[ci skip]
2018-03-28 13:26:18 -04:00
Max Howell
afe3849846 Sincerely recommend Carthage
We still only show `pod install` instructions because they are the most popular tool.

But I wasted 2 hours this morning because CocoaPods don’t care about their DX so I’m done recommending it any capacity.

[ci skip]
2018-03-28 13:18:58 -04:00
Max Howell
3749a340c6 Move podspec into .github directory
[ci skip]
2018-03-28 13:18:54 -04:00
Max Howell
186439d480 This doesn’t lint when Foundation subspec inc’d
Because `Promise<(Pipe, Pipe)>.print` confuses the compiler for Swift.print usage. This obv. is completely daft, but whatever.
2018-03-28 12:14:21 -04:00
Max Howell
a37e91a0fa Include PMKRace in CorePromise subspec 2018-03-28 11:51:59 -04:00
Max Howell
88070d4bc6 [ci skip] Troubleshooting++ 2018-03-27 11:04:25 -04:00
Max Howell
12544eb1b8
[ci skip] Remove spurious double backslash 2018-03-24 13:46:49 -04:00
Max Howell
b33da6274f
These should be map and not compactMap 2018-03-24 13:45:49 -04:00
Max Howell
45c89e2d0e
Merge pull request #815 from NathanLi/patch-2
Remove advertisement
2018-03-19 10:27:59 -04:00
Max Howell
b024061082
Merge pull request #814 from NathanLi/patch-1
Remove advertisement
2018-03-19 10:27:38 -04:00
听榆大叔
22ea68ee84
Remove advertisement 2018-03-19 18:32:33 +08:00
听榆大叔
675ec14e16
Remove advertisement 2018-03-19 18:32:00 +08:00
Max Howell
38fb14314a
Merge pull request #810 from cristik/patch-1
Fixed typo
2018-03-14 11:36:45 -04:00
Cristian Kocza
a38d553531
Fixed typo 2018-03-14 17:03:58 +02:00
Max Howell
3dfd1165c6
Merge pull request #809 from LinusU/patch-1
Fix date typo in installation guide
2018-03-14 09:36:00 -04:00
Linus Unnebäck
21a60747d7
Fix date typo in installation guide 2018-03-14 12:26:10 +00:00
Max Howell
62c61dd712 Import PromiseKit/Foundation@3.0.4 2018-03-09 10:47:13 -05:00
Max Howell
6066eaa9aa Provide an example for Troubleshooting 2018-03-09 09:37:37 -05:00
Max Howell
aa649f6153 Import PromiseKit/Alamofire@3.2.1; Update README
Tag 6.2.2
2018-03-09 09:37:37 -05:00
Loïs Di Qual
65f020801e Add official Promises/A+ compliance test suite (#805)
* Library exports properly

* A bit of progress

* Try emitting mocha events

* Progress

* Progress is real

* All tests now show

* All tests run

* Proper formatting

* Proper XCTFail

* Some progress with adapters

* Not sure what's up

* Tests run with new adapter

* Add then function

* Good progress on interface

* Lots of tests now pass

* Validate onFulfilled / onRejected before calling them

* Cleanup test file

* Add todo

* Move MockNodeEnvironment to proper folder

* Use invokeMethod to call pure functions

* Fix timers

* Add some debugging

* Fix timeouts

* Catch errors when calling handlers

* Move JSPromise to own file

* Refactor JSPromise to use then/catch

* Add a bit of doc

* Catch exceptions thrown by handlers, the proper way

* Consolidate stacktrace printing

* Add availability checks

* Fully comply to 2.2.7.1

* Allow only running one test

* Add isFunction checks

* Enable 2.3.1 only for now

* Add ignored tests, handle promises as return value

* Report failures back to swift layer

* Move adapter to own file

* Use dev mode in webpack config

* More doc

* Refactor a bit

* More stuff in JSUtils

* More doc

* Add readme

* Remove some whitespace

* Add typeError func

* Use on:nil in second then

* Throw TypeError when needed

* Comment fix

* Make test suite iOS 8+ compatible

* Fix Swift 4 compile

* Fix Swift 3.1 compile

* Use babel to transpile to ES5, adds support to iOS 8/9

* Move JSUtils to proper folder

* Fix warning

* Simplify format printing

* Add 3.2 split support back

* Slight improvement

* Revert console.log format solution

* Use a reasonable timeout

* Add omittingEmptySubsequences support for 3.1
2018-03-07 09:13:00 -05:00
Max Howell
c18ad3a403 Tag 6.2.1 2018-03-04 13:17:09 -05:00
Max Howell
19010e78de Import Alamofire extension 3.2.0
[ci skip]
2018-03-02 23:21:20 -05:00
Max Howell
70970c3104
Merge pull request #803 from endash/docs-bug
Fix example in Common Patterns
2018-03-01 11:58:13 -05:00
Max Howell
215de89d20 [ci skip] Zeroth troubleshooter: check handler 2018-03-01 11:55:35 -05:00
Christopher Swasey
ecfce1aba0 Fix example in Common Patterns 2018-03-01 11:16:17 -05:00
Max Howell
a90d49496d [ci skip] Document finally; Refs #802 2018-03-01 09:29:22 -05:00
Max Howell
0ea38acd5a [ci skip] Reference Alamofire extension more times 2018-02-28 13:23:57 -05:00
Max Howell
d64eef2c91 Fed up answering questions the docs answer
Refs #801
2018-02-28 11:57:02 -05:00
Max Howell
13cc51d155 [ci skip] Fix in README 2018-02-27 21:31:17 -05:00
Max Howell
c538a244b1
Merge pull request #800 from timbms/master
Just a typo correction
2018-02-27 16:07:13 -05:00
timbms
e11b950cc5 Typo 2018-02-27 21:55:14 +01:00
Max Howell
660c913111 Fix throwing in init(resolver:) => warning
The Resolver deallocated pending, so it led to the pending promise warning.

Aside: this of course means that we don’t warn for pending Guarantees or AnyPromises.

Fixes #799
2018-02-27 11:41:43 -05:00
Max Howell
6fd76750f4 Import UIKit-3.0.1 extension 2018-02-27 11:38:02 -05:00
Max Howell
ac716cc234 [ci skip] Tweaks to Common-Patterns 2018-02-26 12:59:36 -05:00
Max Howell
fadfa48278
Merge pull request #796 from ldiqual/lois-finally
Document 'finally'
2018-02-26 11:19:19 -05:00
Lois Di Qual
cd996e7542 Move 'finally' section as part of the 'ensure' one 2018-02-25 19:55:53 -08:00
Max Howell
2803fb73c4 [ci skip] Add reference about starting in background
Refs #743
2018-02-25 20:26:48 -05:00
Max Howell
de854628c7 [ci skip] Add the unused warning to troubleshooting 2018-02-25 20:18:43 -05:00
Max Howell
06ff4333b9 Documentation++ 2018-02-25 13:40:00 -05:00
Max Howell
30530b0125 Remove map and flatMap deprecations
Closes #795
2018-02-25 13:40:00 -05:00
Max Howell
a7e4c3e510 Import CoreLocation 3.0.3; Tag 6.1.2 2018-02-25 12:17:06 -05:00
Max Howell
c470d72ef4
Merge pull request #797 from ldiqual/lois-doc
Partially document v6 source code
2018-02-25 01:44:09 -05:00
Lois Di Qual
47b6db3a50 Fix param names 2018-02-24 20:18:24 -08:00
Lois Di Qual
cbbd35e75b Add 'ensure' 2018-02-24 20:17:43 -08:00
Lois Di Qual
6e82a6c444 Fix recover docs 2018-02-24 20:15:18 -08:00
Lois Di Qual
a6178c5fa0 Document catch/recover 2018-02-24 20:09:39 -08:00
Lois Di Qual
97de95190b Document then/map/done/get 2018-02-24 20:00:25 -08:00
Lois Di Qual
2f6eb6ebc1 Document 'finally' 2018-02-24 19:41:08 -08:00
Max Howell
eb6a6e0436
Merge pull request #794 from mxcl/nathanhosselton-GettingStarted-update
Update GettingStarted.md
2018-02-23 20:54:59 -05:00
Nathan Hosselton
a99d082dbc
Update GettingStarted.md
Fix DispatchGroup example under #when
2018-02-23 19:19:40 -05:00
Max Howell
5840fa998d [ci skip] Add wait to the Appendix 2018-02-23 15:23:16 -05:00
Max Howell
846222fdb5 [ci skip] My Promise Never Resolves 2018-02-23 13:19:23 -05:00
Max Howell
a9689bb5df [ci skip] Update this troubleshooting example for PMK6 2018-02-23 00:49:31 -05:00
Max Howell
3b8c29f646 [ci skip] Title second section 2018-02-23 00:45:53 -05:00
Max Howell
3075d99710 [ci skip] Add common use of asVoid() to appendix 2018-02-23 00:40:14 -05:00
Max Howell
a20df65b74 Tag 6.1.1 2018-02-22 17:59:22 -05:00
Max Howell
b971ff75b6 This route is unused (proved by code-coverage)
However since it is a public function, the user could use it, but they shouldn’t, so mark it internal.
2018-02-22 16:17:51 -05:00
Max Howell
63a57e4039 Increase Thenable coverage all the way up 2018-02-22 16:17:51 -05:00
Max Howell
dcd44ef1e2 [ci skip] Chaining animations 2018-02-22 12:35:33 -05:00
Max Howell
8481a40da7 Consolidate this single use function 2018-02-22 12:20:40 -05:00
Max Howell
9152012f56 Test AnyPromise.result 2018-02-22 11:59:22 -05:00
Max Howell
7819eefcf8 Update PMKFoundation to 3.0.3 2018-02-22 11:59:22 -05:00
Max Howell
9acd098b49 Rename some tests 2018-02-22 11:59:22 -05:00
Max Howell
81633ff9a4
Merge pull request #793 from mxcl/installation-guide-typo
Update Installation.md
2018-02-22 10:14:55 -05:00
Nathan Hosselton
4b5ba51913
Update Installation.md
⌘c+⌘v error
2018-02-22 09:58:17 -05:00
Max Howell
b93ef65b1f Let’s use swift-env instead of docker
Both end up downloading tonnes of data, but this way we have more control over how commands are executed and it’s more direct.
2018-02-21 16:52:14 -05:00
Max Howell
1564558918 Document that firstly runs immediately 2018-02-21 16:11:18 -05:00
Max Howell
a00f9d1286 Package.swift: supports both Swift 3 & 4 2018-02-21 16:11:10 -05:00
Max Howell
b1640df9fd Run CI for Linux Swift 3.2 & Swift 4.0 2018-02-21 16:10:33 -05:00
Max Howell
0f538f2684 Fix tests on Linux 2018-02-21 14:36:29 -05:00
Max Howell
13fcf1d2e9 Update TroubleShooting for PMK6 2018-02-21 14:33:31 -05:00
Max Howell
efbc9c2d5a [ci skip] Ignore Tests for code coverage 2018-02-20 20:15:45 -05:00
Max Howell
ee1063703d Increase code coverage for AnyPromise 2018-02-20 20:08:26 -05:00
Max Howell
c18f2d83fb Delete unused tests; Support swift test
Get travis to runs tests.

Means we no longer support Swift 3.1 on Linux. Sorry. Progress, etc.
2018-02-20 20:08:25 -05:00
Max Howell
4c1e1ca29a [ci skip] Mention that we validate against Promises/A+ 2018-02-20 18:05:54 -05:00
Max Howell
9c5d6ecab9 Do whole module optimization in release mode 2018-02-20 17:54:59 -05:00
Max Howell
c8f747f861 Code coverage improvements
Also enables warnings in tests again and silences the warnings.

Also stops us testing code coverage for tests during CI.
2018-02-20 17:54:59 -05:00
Max Howell
da2cffc711 Add missing policy for this recover variant 2018-02-20 17:11:11 -05:00
Max Howell
ca845042f7 [ci skip] flatMap -> compactMap 2018-02-19 23:37:26 -05:00
Max Howell
171cc2fadf Tag 6.1.0 2018-02-19 17:25:34 -05:00
Max Howell
5875f8f5a3 @available(deprecated: x.y) is for Swift x.y
Not our library version. Which makes no sense at all of course, so thanks for that Swift-core.
2018-02-19 17:12:29 -05:00
Max Howell
6be1e3564f Fix Carthage builds with Xcode 8.3
In order to build with Xcode 8.3 & Carthage we must have the SWIFT_VERSION setting set to 3.0.

So with Xcode 10 we’ll shove this up to 4 I guess.
2018-02-19 15:56:29 -05:00
Max Howell
7f7bb7f52d Fix: builds failing, but leaving green lights
Well great. This series of fixes, fixes builds failing but looking like they passed, and fixes the issues that were failing.

Fortunately it was just compile issues in the tests, no tests were failing.

For me this proves that YAML sucks, BASH sucks and TRAVIS sucks.
2018-02-19 15:56:29 -05:00
Max Howell
d2f37908a3 Provide specific error for this condition 2018-02-19 10:26:32 -05:00
Max Howell
5e14f2d7f8 Subsequent handlers should dispatch to nil 2018-02-19 10:26:31 -05:00
Max Howell
deb2cbffe0 Remove redundant Promise.asVoid 2018-02-19 10:26:31 -05:00
Max Howell
daf93820f7 Include responseDecodable; Refs #786
Refs PromiseKit/Alamofire-@0eff960.
2018-02-19 10:26:31 -05:00
Max Howell
5cd9fd05a0
Rename functional functions; Refs #773
Rename functional functions; Refs #773

See the discussion in #773 and #782 for details.

| Form | Currently | Proposed |
|-------------------------------------------|---------|------------|
| `Promise<T> => ((T) -> U?) => Promise<U>` | flatMap | compactMap |
| `Promise<[T]> => ((T) -> U) => Promise<[U]>` | map | mapValues |
| `Promise<[T]> => ((T) -> [U]) => Promise<[U]>)` | flatMap | flatMapValues |
| `Promise<[T]> => ((T) => U?)` | compactMap | compactMapValues |
| `Promise<[T]> => ((T) -> Promise<U>) => Promise<[U]>` | thenMap | *unchanged* |
| `Promise<[T]> => ((T) -> Promise<[U]>) => Promise<[U]>` | thenFlatMap | *unchanged* |
| `Promise<[T]> => ((T) -> Bool) => Promise<[T]>` | filter | filterValues |
| `Promise<[T]> => Promise<T>` | last | lastValue |
2018-02-19 10:24:59 -05:00
Lukas Schmidt
d94e3be9bb Adds missing comma to the FAQ (#785)
Adds missing comma…

…in the list of all supported platforms mentioned in the FAQ
2018-02-18 17:29:15 -05:00
Max Howell
b585f7cdc7 [ci skip] Update FAQ 2018-02-18 17:29:10 -05:00
Wayne Hartman
a9cf4b71a6 FAQ spelling and grammar corrections 2018-02-18 15:10:50 -05:00
Max Howell
393632db90 Include fix for PromiseKit/CoreLocation#4 2018-02-18 12:40:20 -05:00
Max Howell
4bfbab51ad Include PromiseKit/CoreLocation#10; Tag 6.0.3
Note this updates our Bolts requirement to 1.9.0 because Facebook don’t understand how semantic versioning works.
2018-02-18 11:18:19 -05:00
Max Howell
59847871ba Xcode 9.3 / Swift 4.1 / Swift 3.3 updates
Strictly I’d feel better to test all configurations, but PromiseKit only uses Foundation, so the chance that for some reason there is an API break or worse some kind of runtime issue is pretty low and I feel that we have to be kinder to Travis’s free infrastructure.

Tag 6.0.2
2018-02-16 15:26:52 -05:00
Max Howell
9cc1cc9aa8 Mark always as deprecated; Fixes #775 2018-02-15 16:09:23 -05:00
Max Howell
b23f26462e Update Alamofire extension
The rest are just README updates
2018-02-15 16:09:23 -05:00
Max Howell
762e322d9f
Merge pull request #774 from nathanhosselton/master
Update GettingStarted.md
2018-02-13 22:32:20 -05:00
Nathan Hosselton
83516e1e12
Update GettingStarted.md
Typos, formatting, etc
2018-02-13 21:18:33 -05:00
Max Howell
673ce2d4f9 [ci skip] Docs++ WIP 2018-02-13 16:44:01 -05:00
Max Howell
8f4cb85636 Fix SwiftPM 2018-02-13 16:44:01 -05:00
Max Howell
a8616e5669 Provide wrap but deprecated 2018-02-13 16:24:35 -05:00
Max Howell
8a45494745 Support macOS 10.9 2018-02-13 16:24:35 -05:00
Max Howell
b9fb8aef4f Provide PMKRace(); Closes #696 2018-02-13 15:50:52 -05:00
Max Howell
5db87f0792 Support macOS 10.9; Fixes #728 2018-02-13 15:40:59 -05:00
Max Howell
f6a448fb86 Add rejected & fulfilled to AnyPromise; Fixes #731 2018-02-12 22:07:31 -05:00
Max Howell
54a99ba7f2 Fixes for CocoaPods; --warning 2018-02-12 21:33:36 -05:00
Max Howell
1339ca0695 Rewrite AnyPromise to use composition
Our existing hack broke the new Xcode build system (see #724).

Also, CocoaPods 1.4 didn’t like it.

So I had to rewrite with composition instead adding additional methods via an objc category.

The cause of the issue was that the framework required that the -Swift.h file be generated before the objc portions were built (since the objc extended the swift portion), and neither build system guarantees this.

All tests pass. However it is not as efficient as before. Could use some optimizations.
2018-02-12 15:27:50 -05:00
Max Howell
17587be4ac PromiseKit 6.0.0
This because `Promise(value:)` is selected for PMK-4’s pending initializer due to trailing closure syntax and the fact that `T` could be anything. I guess this isn’t a Swift bug, but geez.

This is a real problem for people migrating to 5 from 4, such that I have decided 5 must be deprecated.

The severity is due to Swift giving a very misleading error diagnostic when it uses `Promise(value:)`, because as you can guess you end up with:

    Promise<(T->Void, Error->Void)>

As your inferred type.

Because we no longer have ambiguity between `Promise { seal in` and `Promise(value:` I have removed the `.pending` parameter for that usage which will also aid migration.

In some cases this leads to less nice code, but mostly it’s fine due to being able to do `.value(1)` where `Promise` can be inferred. The trade off is worth it though as PromiseKit should be delightful to use and as it stands, it is a pain due to how Swift is.
2018-02-10 17:23:27 -05:00
Max Howell
b87b7ba1f0 Update StoreKit 2018-02-09 13:22:06 -05:00
Max Howell
642d8c0bf0
Merge pull request #769 from ss18/faq-update
Updated FAQ
2018-02-05 09:36:25 -05:00
Semen Zhydenko
102fe659e4 heirarchy -> hierarchy 2018-02-03 14:07:05 +01:00
Max Howell
eb5413cf14
Document no extensions here
Since nobody reads anything but the README apparently.
2018-01-25 14:05:00 -05:00
Max Howell
e0b9506e28
fulfill(()); Refs #763 2018-01-18 11:45:10 -05:00
Max Howell
cd150744ed Catch directly; avoid as NSError which can crash 2018-01-16 13:20:47 -05:00
Max Howell
a66f046f41
Merge pull request #762 from vineetshah/cancellation-syntax
Fix syntax errors in Cancellation documentation
2018-01-16 11:49:44 -05:00
Vineet Shah
322b67b41c Fix syntax errors in Cancellation documentation 2018-01-16 09:37:58 -05:00
Max Howell
8253bf2453 Update Extensions/CoreLocation 2018-01-08 09:21:36 -05:00
Max Howell
11ac1b42eb Update extensions 2018-01-05 13:57:54 -05:00
Max Howell
2df8580c92 Travis deprecated Xcode 8.1/8.2; Bump to 9.2
Update all extensions (just travis updates for each also).
2017-12-10 13:18:29 -05:00
Alexander
4742e2ec0f Pure-swift hang() and tests. (#741)
* Pure-swift hang() and tests.

* Tweak hang() to assume Apple-y platforms
2017-12-08 17:16:50 -05:00
Max Howell
ca17677756 Provide localizedDescription for our errors
Commit minor changes to Social and OMG extensions.
2017-12-04 10:52:28 -05:00
Max Howell
3ee3fe0cac
Update Troubleshooting.md 2017-11-30 14:43:22 -05:00
Max Howell
d01c2d1df2 Talk more about retain-cyles and side-effects 2017-11-30 11:44:49 -05:00
Max Howell
77f2c90b53 Clarify servers-side usage; Refs #627 2017-11-30 11:38:03 -05:00
Max Howell
7d0c92afb0 Update Extensions/CoreLocation 2017-11-30 11:23:35 -05:00
Max Howell
c8beb53c01 Update Copyright in LICENSE 2017-11-30 11:23:23 -05:00
Max Howell
5321a44527 Mention when(concurrently:) under sequencing 2017-11-30 11:23:15 -05:00
Max Howell
ddc626d1b9
Update when.swift 2017-11-29 09:49:58 -05:00
Max Howell
8939afbf9e
Big ourselves up somewhat 2017-11-27 13:47:40 -05:00
Max Howell
f7ff96dc7d Update UIKit extension to 2.0.1 2017-11-19 13:27:25 -05:00
Kara Valentine
75117687e5 Update CommonPatterns.md (#735)
Fixed a typo
2017-11-16 20:10:18 -05:00
Max Howell
47367f1df6 5.0.0 2017-11-06 09:27:11 -05:00
162 changed files with 16404 additions and 5050 deletions

2
.github/FUNDING.yml vendored Normal file
View File

@ -0,0 +1,2 @@
tidelift: "cocoapods/PromiseKit"
patreon: "mxcl"

View File

@ -1,6 +1,11 @@
* PromiseKit version: **major version is enough unless you have a build issue**
* Xcode version: **only required for build issues**
* CocoaPods version: **only required for build issues**
* Carthage version: **only required for build issues**
[PLEASE READ THE TROUBLESHOOTING GUIDE](https://github.com/mxcl/PromiseKit/blob/master/Documentation/Troubleshooting.md).
> Please format your code in triple backticks and delete this line before submitting your ticket. Failure to remove this line may result in mockery.
---
You read the guide but it didnt help? OK, were here to help.
* Please specify the PromiseKit major version you are using
* [Please format your code in triple backticks and ensure readable indentation](https://help.github.com/articles/creating-and-highlighting-code-blocks/)
* Please specify how you installed PromiseKit, ie. Carthage, CocoaPods, SwiftPM or other. If other provide DETAILED information about how you are integrating PromiseKit.
If you ignore this template we will close your ticket and link to this template until you provide this necessary information. We cannot help you without it.

19
.github/LinuxMain.stencil vendored Normal file
View File

@ -0,0 +1,19 @@
@testable import Core
@testable import A_
import XCTest
//TODO get this to run on CI and dont have it committed
//NOTE problem is Sourcery doesnt support Linux currently
//USAGE: cd PromiseKit/Sources/.. && sourcery --config .github/sourcery.yml
{% for type in types.classes|based:"XCTestCase" %}
extension {{ type.name }} {
static var allTests = [
{% for method in type.methods %}{% if method.parameters.count == 0 and method.shortName|hasPrefix:"test" %} ("{{ method.shortName }}", {{type.name}}.{{ method.shortName }}),
{% endif %}{% endfor %}]
}
{% endfor %}
XCTMain([
{% for type in types.classes|based:"XCTestCase" %}{% if not type.annotations.excludeFromLinuxMain %} testCase({{ type.name }}.allTests),
{% endif %}{% endfor %}])

View File

@ -1,10 +1,7 @@
Pod::Spec.new do |s|
s.name = "PromiseKit"
`xcodebuild -project PromiseKit.xcodeproj -showBuildSettings` =~ /CURRENT_PROJECT_VERSION = ((\d\.)+\d)/
abort("No version detected") if $1.nil?
abort("Not tagged") unless `git tag`.split.include? $1
s.version = $1
s.version = '0.0.1'
s.source = {
:git => "https://github.com/mxcl/#{s.name}.git",
@ -14,21 +11,27 @@ Pod::Spec.new do |s|
s.license = 'MIT'
s.summary = 'Promises for Swift & ObjC.'
s.homepage = 'http://promisekit.org'
s.homepage = 'http://mxcl.dev/PromiseKit/'
s.description = 'A thoughtful and complete implementation of promises for iOS, macOS, watchOS and tvOS with first-class support for both Objective-C and Swift.'
s.social_media_url = 'https://twitter.com/mxcl'
s.authors = { 'Max Howell' => 'mxcl@me.com' }
s.documentation_url = 'http://promisekit.org/docs/'
s.default_subspecs = 'Foundation', 'UIKit', 'QuartzCore'
s.documentation_url = 'http://mxcl.dev/PromiseKit/reference/v6/Classes/Promise.html'
s.default_subspecs = 'CorePromise', 'UIKit', 'Foundation'
s.requires_arc = true
s.swift_versions = ['3.1', '3.2', '3.3', '3.4', '4.0', '4.1', '4.2', '4.3', '4.4', '5.0', '5.1']
# CocoaPods requires us to specify the root deployment targets
# even though for us it is nonsense. Our root spec has no
# sources.
s.ios.deployment_target = '8.0'
s.osx.deployment_target = '10.11'
s.osx.deployment_target = '10.10'
s.watchos.deployment_target = '2.0'
s.tvos.deployment_target = '9.0'
s.pod_target_xcconfig = {
'OTHER_SWIFT_FLAGS' => '-DPMKCocoaPods',
}
s.subspec 'Accounts' do |ss|
ss.ios.source_files = ss.osx.source_files = 'Extensions/Accounts/Sources/*'
@ -72,7 +75,7 @@ Pod::Spec.new do |s|
s.subspec 'Bolts' do |ss|
ss.source_files = 'Extensions/Bolts/Sources/*'
ss.dependency 'PromiseKit/CorePromise'
ss.dependency 'Bolts', '~> 1.6.0'
ss.dependency 'Bolts', '~> 1.9.0'
ss.ios.deployment_target = '8.0'
ss.osx.deployment_target = '10.10'
ss.watchos.deployment_target = '2.0'
@ -83,9 +86,9 @@ Pod::Spec.new do |s|
ss.source_files = 'Extensions/CloudKit/Sources/*'
ss.frameworks = 'CloudKit'
ss.dependency 'PromiseKit/CorePromise'
ss.ios.deployment_target = '8.0'
ss.osx.deployment_target = '10.10'
ss.tvos.deployment_target = '9.0'
ss.ios.deployment_target = '10.0'
ss.osx.deployment_target = '10.12'
ss.tvos.deployment_target = '10.0'
ss.watchos.deployment_target = '3.0'
end
@ -102,7 +105,7 @@ Pod::Spec.new do |s|
hh = Dir['Sources/*.h'] - Dir['Sources/*+Private.h']
cc = Dir['Sources/*.swift'] - ['Sources/SwiftPM.swift']
cc << 'Sources/{after,AnyPromise,GlobalState,dispatch_promise,hang,join,PMKPromise,when}.m'
cc << 'Sources/{after,AnyPromise,GlobalState,dispatch_promise,hang,join,PMKPromise,when,race}.m'
cc += hh
ss.source_files = cc
@ -147,7 +150,24 @@ Pod::Spec.new do |s|
ss.watchos.deployment_target = '2.0'
ss.tvos.deployment_target = '9.0'
end
s.subspec 'HealthKit' do |ss|
ss.source_files = Dir['Extensions/HealthKit/Sources/*']
ss.dependency 'PromiseKit/CorePromise'
ss.frameworks = 'HealthKit'
ss.ios.deployment_target = '9.0'
ss.watchos.deployment_target = '2.0'
end
s.subspec 'HomeKit' do |ss|
ss.source_files = Dir['Extensions/HomeKit/Sources/*']
ss.dependency 'PromiseKit/CorePromise'
ss.frameworks = 'HomeKit'
ss.ios.deployment_target = '8.0'
ss.watchos.deployment_target = '3.0'
ss.tvos.deployment_target = '9.0'
end
s.subspec 'MapKit' do |ss|
ss.ios.source_files = ss.osx.source_files = ss.tvos.source_files = 'Extensions/MapKit/Sources/*'
ss.ios.frameworks = ss.osx.frameworks = ss.tvos.frameworks = 'MapKit'

29
.github/codecov.yml vendored Normal file
View File

@ -0,0 +1,29 @@
ignore:
- "Tests"
- "README.md"
- "Documentation"
- ".travis.yml"
codecov:
notify:
require_ci_to_pass: yes
coverage:
precision: 1
round: up
range: "70...100"
status:
project: yes
patch: yes
changes: no
parsers:
gcov:
branch_detection:
conditional: yes
loop: yes
method: no
macro: no
comment: off

18
.github/jazzy.yml vendored Normal file
View File

@ -0,0 +1,18 @@
module: PromiseKit
custom_categories:
- name: Core Components
children:
- Promise
- Guarantee
- Thenable
- CatchMixin
- Resolver
xcodebuild_arguments:
- UseModernBuildSystem=NO
output:
../output
# output directory is relative to config file… ugh
readme:
Documentation/README.md
theme:
fullwidth

2
.github/ranger.yml vendored Normal file
View File

@ -0,0 +1,2 @@
merges:
- action: delete_branch

17
.github/release vendored Executable file
View File

@ -0,0 +1,17 @@
#!/bin/sh
set -euxo pipefail
if [ -z "$1" ]; then
echo "No argument supplied"
exit 1
fi
Vn=$1
Vo=$(git tag | tail -1)
sed -i "" "s/CURRENT_PROJECT_VERSION = $Vo;/CURRENT_PROJECT_VERSION = $Vn;/" PromiseKit.xcodeproj/project.pbxproj
git add PromiseKit.xcodeproj/project.pbxproj
git commit -m "Tag $Vn"
git tag $Vn
git push origin $Vn
open "https://github.com/mxcl/PromiseKit/releases/tag/$Vn"

12
.github/sourcery.yml vendored Normal file
View File

@ -0,0 +1,12 @@
sources:
include:
- ../Tests/A+
- ../Tests/CorePromise
exclude:
- ../Tests/A+/0.0.0.swift
- ../Tests/CorePromise/Utilities.swift
templates:
include:
- LinuxMain.stencil
output:
../Tests/LinuxMain.swift

5
.gitignore vendored
View File

@ -1,6 +1,9 @@
*.xcodeproj/**/xcuserdata/
*.xcscmblueprint
/Carthage
/Cartfile.resolved
/.build
.DS_Store
DerivedData
/PromiseKit.podspec
/Extensions/Carthage
/Tests/JS-A+/build

6
.gitmodules vendored
View File

@ -61,3 +61,9 @@
[submodule "Extensions/AVFoundation"]
path = Extensions/AVFoundation
url = https://github.com/PromiseKit/AVFoundation
[submodule "Extensions/HomeKit"]
path = Extensions/HomeKit
url = https://github.com/PromiseKit/HomeKit.git
[submodule "Extensions/HealthKit"]
path = Extensions/HealthKit
url = https://github.com/PromiseKit/PMKHealthKit

1
.tidelift.yml Normal file
View File

@ -0,0 +1 @@
extra_ignore_directories: [ Tests ]

View File

@ -1,129 +1,214 @@
matrix:
os: osx
osx_image: xcode10.3
language: swift
branches:
only:
- v7
- master
- v4
- legacy-1.x
- /^\d+\.\d+\.\d+$/
stages:
- name: pretest
if: type != push OR branch =~ /^\d+\.\d+\.\d+$/
- name: lint
if: NOT branch =~ ^\d+\.\d+\.\d+$
- name: carthage
if: type != push OR branch =~ /^\d+\.\d+\.\d+$/
- name: swiftpm
if: type != push OR branch =~ /^\d+\.\d+\.\d+$/
- name: test
if: type != push OR branch =~ /^\d+\.\d+\.\d+$/
- name: deploy
if: branch =~ ^\d+\.\d+\.\d+$
jobs:
include:
- os: osx
language: objective-c
osx_image: xcode9.1
env: SWFT=4.0 PLAT=macOS
- os: osx
language: objective-c
osx_image: xcode9.1
env: SWFT=4.0 PLAT=iOS
- os: osx
language: objective-c
osx_image: xcode9.1
env: SWFT=4.0 PLAT=tvOS
- os: osx
language: objective-c
osx_image: xcode9.1
env: SWFT=4.0 PLAT=watchOS
- os: osx
language: objective-c
osx_image: xcode9.1
env: SWFT=3.2 PLAT=macOS
- os: osx
language: objective-c
osx_image: xcode9.1
env: SWFT=3.2 PLAT=iOS
- os: osx
language: objective-c
osx_image: xcode9.1
env: SWFT=3.2 PLAT=tvOS
- os: osx
language: objective-c
osx_image: xcode9.1
env: SWFT=3.2 PLAT=watchOS
- stage: pretest
name: Validate Linux test coverage completeness
install: swift test --generate-linuxmain -Xswiftc -target -Xswiftc x86_64-apple-macosx10.12
script: git diff --exit-code
- os: osx
language: objective-c
osx_image: xcode8.3
env: SWFT=3.1 PLAT=macOS
- os: osx
language: objective-c
osx_image: xcode8.3
env: SWFT=3.1 PLAT=iOS
- os: osx
language: objective-c
osx_image: xcode8.3
env: SWFT=3.1 PLAT=tvOS
- os: osx
language: objective-c
osx_image: xcode8.3
env: SWFT=3.1 PLAT=watchOS
- &carthage
stage: carthage
osx_image: xcode9
before_script: |
sed -i '' "s/SWIFT_TREAT_WARNINGS_AS_ERRORS = NO;/SWIFT_TREAT_WARNINGS_AS_ERRORS = YES;/" *.xcodeproj/project.pbxproj
sed -i '' "s/GCC_TREAT_WARNINGS_AS_ERRORS = NO;/GCC_TREAT_WARNINGS_AS_ERRORS = YES;/" *.xcodeproj/project.pbxproj
script: carthage build --no-skip-current --configuration Release
name: Swift 4.0.0 / Xcode 9.0
- <<: *carthage
osx_image: xcode9.1
name: Swift 4.0.2 / Xcode 9.1
- <<: *carthage
osx_image: xcode9.2
name: Swift 4.0.3 / Xcode 9.2
- <<: *carthage
osx_image: xcode9.3
name: Swift 4.1.0 / Xcode 9.3.1
- <<: *carthage
osx_image: xcode9.4
name: Swift 4.1.2 / Xcode 9.4.1
- <<: *carthage
osx_image: xcode10.1
name: Swift 4.1.50 / Xcode 10.1
- <<: *carthage
osx_image: xcode10.3
name: Swift 4.1.51 / Xcode 10.3
- <<: *carthage
osx_image: xcode11
name: Swift 4.1.52 / Xcode 11
- os: osx
language: objective-c
osx_image: xcode8.2
env: SWFT=3.0 PLAT=macOS
- os: osx
language: objective-c
osx_image: xcode8.2
env: SWFT=3.0 PLAT=iOS
- os: osx
language: objective-c
osx_image: xcode8.2
env: SWFT=3.0 PLAT=tvOS
- os: osx
language: objective-c
osx_image: xcode8.2
env: SWFT=3.0 PLAT=watchOS
- &pod
stage: lint
osx_image: xcode8.3
env: SWIFT=3.1
cache: cocoapods
before_install: mv .github/PromiseKit.podspec .
install: gem install cocoapods -v '~> 1.7.0'
script: pod lib lint --subspec=PromiseKit/CorePromise --fail-fast --swift-version=$SWIFT
- <<: *pod
osx_image: xcode9.2
env: SWIFT=3.2
- <<: *pod
osx_image: xcode9.4
env: SWIFT=3.3
- <<: *pod
osx_image: xcode10.1
env: SWIFT=3.4
- <<: *pod
osx_image: xcode9.2
env: SWIFT=4.0
- <<: *pod
osx_image: xcode9.4
env: SWIFT=4.1
- <<: *pod
osx_image: xcode10.1
env: SWIFT=4.2
- <<: *pod
osx_image: xcode10.3
env: SWIFT=4.3
- <<: *pod
osx_image: xcode10.3
env: SWIFT=5.0
- <<: *pod
osx_image: xcode11
env: SWIFT=5.1
- os: osx
language: objective-c
osx_image: xcode8.1
env: SWFT=3.0 PLAT=macOS
- os: osx
language: objective-c
osx_image: xcode8.1
env: SWFT=3.0 PLAT=iOS
- os: osx
language: objective-c
osx_image: xcode8.1
env: SWFT=3.0 PLAT=tvOS
- os: osx
language: objective-c
osx_image: xcode8.1
env: SWFT=3.0 PLAT=watchOS
- os: linux
- &linux
stage: swiftpm
env: SWIFT_BUILD_VERSION=3 SWIFT_VERSION=4.0.3
name: Linux / Swift 3.2
os: linux
dist: trusty
sudo: required
services: docker
env: DOCKER_IMAGE=swift:3.1
#TODO please help us test Linux with Swift 3.0, 3.2 and 4.0
sudo: false
language: generic
before_install: eval "$(curl -sL https://swiftenv.fuller.li/install.sh)"
install: swift build -Xswiftc -swift-version -Xswiftc $SWIFT_BUILD_VERSION
script: "true"
osx_image: null
- <<: *linux
env: SWIFT_BUILD_VERSION=3 SWIFT_VERSION=4.1.3
name: Linux / Swift 3.3
- <<: *linux
env: SWIFT_BUILD_VERSION=3 SWIFT_VERSION=4.2.4
name: Linux / Swift 3.4
- <<: *linux
env: SWIFT_BUILD_VERSION=4 SWIFT_VERSION=4.0.3
name: Linux / Swift 4.0.3
- <<: *linux
env: SWIFT_BUILD_VERSION=4 SWIFT_VERSION=4.1.3
name: Linux / Swift 4.1.3
- <<: *linux
env: SWIFT_BUILD_VERSION=4 SWIFT_VERSION=4.2.4
name: Linux / Swift 4.1.50
- <<: *linux
env: SWIFT_BUILD_VERSION=4.2 SWIFT_VERSION=4.2.4
name: Linux / Swift 4.2.4
- <<: *linux
env: SWIFT_BUILD_VERSION=4.2 SWIFT_VERSION=5.0
name: Linux / Swift 4.3
- <<: *linux
env: SWIFT_BUILD_VERSION=5 SWIFT_VERSION=5.0
name: 'Linux / Swift 5.0 / +Tests'
script: swift test -Xswiftc -swift-version -Xswiftc $SWIFT_BUILD_VERSION
- <<: *linux
env: SWIFT_BUILD_VERSION=5 SWIFT_VERSION=5.1
name: Linux / Swift 5.1
before_install:
- if [ "$TRAVIS_OS_NAME" == "linux" ]; then
docker pull $DOCKER_IMAGE;
fi;
case $PLAT in
iOS)
NAME="iPhone SE";;
tvOS)
NAME="Apple TV 1080p";;
watchOS)
NAME="Apple Watch - 38mm";;
esac;
if [ -n "$NAME" ]; then
export UUID=$(instruments -s | ruby -e "ARGF.each_line{ |ln| ln =~ /$NAME .* \[(.*)\]/; if \$1; puts(\$1); exit; end }");
fi
script:
- set -o pipefail;
case $PLAT in
macOS)
xcodebuild -scheme PromiseKit -quiet build SWIFT_VERSION=$SWFT -enableCodeCoverage YES | xcpretty;
xcodebuild -scheme PromiseKit -quiet test;;
iOS|tvOS)
open -b com.apple.iphonesimulator --args -CurrentDeviceUDID "$UUID";
xcodebuild -scheme PromiseKit -quiet -destination "id=$UUID" build SWIFT_VERSION=$SWFT -enableCodeCoverage YES | xcpretty;
xcodebuild -scheme PromiseKit -quiet -destination "id=$UUID" test;;
watchOS)
xcodebuild -scheme PromiseKit -quiet -destination "id=$UUID" -quiet clean build SWIFT_VERSION=$SWFT | xcpretty;;
*)
docker-compose run PromiseKit;;
esac
after_success:
- if [ "$TRAVIS_OS_NAME" == "osx" ]; then
bash <(curl -s https://codecov.io/bash);
fi
- &test
stage: test
name: macOS / Xcode 10.2
xcode_scheme: PromiseKit
xcode_project: PromiseKit.xcodeproj
xcode_destination: 'platform=macOS'
after_success: bash <(curl -s https://codecov.io/bash)
- <<: *test
name: iOS / Xcode 10.2
xcode_destination: 'platform=iOS Simulator,OS=12.2,name=iPhone SE'
- <<: *test
name: tvOS / Xcode 10.2
xcode_destination: 'platform=tvOS Simulator,OS=12.2,name=Apple TV'
- name: Promises/A+ (via WebKit JavaScript Bridge)
before_install:
set -exo pipefail
install:
bash -c "cd Tests/JS-A+; npm ci &>/dev/null && npm run --hide-modules build"
script:
xcodebuild -scheme PromiseKit -target PMKJSA+Tests -enableCodeCoverage NO -only-testing:PMKJSA+Tests test | xcpretty
cache.directories:
- Tests/JS-A+/build
- Tests/JS-A+/node_modules
- &swiftpm
stage: swiftpm
name: 'macOS / swift-tools-version: 4.0 / Swift 4.1.2'
osx_image: xcode9.4
script: swift build
- <<: *swiftpm
osx_image: xcode10.1
name: 'macOS / swift-tools-version: 4.2 / Swift 4.2.1'
- <<: *swiftpm
osx_image: xcode10.3
name: 'macOS / swift-tools-version: 5.0 / Swift 5.0.0'
- <<: *swiftpm
osx_image: xcode11
name: 'macOS / swift-tools-version: 5.0 / Swift 5.1.0'
- name: '`pod trunk push`'
stage: deploy
install: gem install cocoapods -v '~> 1.7.0'
before_script: |
mv .github/PromiseKit.podspec .
sed -i '' "s/s.version = '0.0.1'/s.version = '$TRAVIS_TAG'/g" PromiseKit.podspec
script: |
set -exo pipefail
pod trunk push --verbose --allow-warnings | tee pod.log | ruby -e 'ARGF.each{ print "." }'
# ^^ pipe because Travis times us out if there is no output
# AND `pod` defaults to hardly any output
# BUT `--verbose` generates so much output that Travis kills our script due to *too much* output!
# --allow-warnings because Bolts generates warnings and CocoaPods fails you even if your deps emit warnings
after_failure: cat pod.log | grep error
- name: Generate Documentation
git.depth: false
install: gem install jazzy
script: |
set -exo pipefail
jazzy --config .github/jazzy.yml \
--github_url "https://github.com/$TRAVIS_REPO_SLUG" \
--module-version "$TRAVIS_TAG"
git remote update
git fetch origin gh-pages:gh-pages --depth 1
git checkout gh-pages
rm -rf reference/v6
mv output reference/v6
git add reference/v6
git config user.name "Travis"
git config user.email "jazzy@travis-ci.com"
git commit -m "Updated docs for v$TRAVIS_TAG"
git remote add secure-origin https://${GITHUB_TOKEN}@github.com/$TRAVIS_REPO_SLUG.git
git push secure-origin gh-pages

View File

@ -1,21 +1,21 @@
# Common Misusage
## Doubling Up Promises
## Doubling up Promises
Dont do this:
```swift
func toggleNetworkSpinnerWithPromise<T>(funcToCall: () -> Promise<T>) -> Promise<T> {
return Promise { fulfill, reject in
return Promise { seal in
firstly {
setNetworkActivityIndicatorVisible(true)
return funcToCall()
}.then { result in
fulfill(result)
seal.fulfill(result)
}.always {
setNetworkActivityIndicatorVisible(false)
}.catch { err in
reject(err)
seal.reject(err)
}
}
}
@ -39,7 +39,7 @@ You already *had* a promise, you dont need to wrap it in another promise.
## Optionals in Promises
Mostly when we see `Promise<Item?>` it implies a misuse of promises, for
When we see `Promise<Item?>`, it usually implies a misuse of promises. For
example:
```swift
@ -47,23 +47,165 @@ return firstly {
getItems()
}.then { items -> Promise<[Item]?> in
guard !items.isEmpty else {
return Promise(value: nil)
return .value(nil)
}
return Promise(value: items)
}
```
The second `then` chooses to return `nil` in some circumstances. This imposes
the `nil` check on the consumer of this promise. Instead create an specific
The second `then` chooses to return `nil` in some circumstances. This choice
imposes the need to check for `nil` on the consumer of the promise.
It's usually better to shunt these sorts of exceptions away from the
happy path and onto the error path. In this case, we can create a specific
error type for this condition:
```swift
return firstly {
getItems()
}.then { items -> Promise<[Item]> in
}.map { items -> [Item]> in
guard !items.isEmpty else {
throw MyError.emptyItems
}
return items
}
```
> *Note*: Use `compactMap` when an API outside your control returns an Optional and you want to generate an error instead of propagating `nil`.
# Tips n Tricks
## Background-Loaded Member Variables
```swift
class MyViewController: UIViewController {
private let ambience: Promise<AVAudioPlayer> = DispatchQueue.global().async(.promise) {
guard let asset = NSDataAsset(name: "CreepyPad") else { throw PMKError.badInput }
let player = try AVAudioPlayer(data: asset.data)
player.prepareToPlay()
return player
}
}
```
## Chaining Animations
```swift
firstly {
UIView.animate(.promise, duration: 0.3) {
self.button1.alpha = 0
}
}.then {
UIView.animate(.promise, duration: 0.3) {
self.button2.alpha = 1
}
}.then {
UIView.animate(.promise, duration: 0.3) {
adjustConstraints()
self.view.layoutIfNeeded()
}
}
```
## Voiding Promises
It is often convenient to erase the type of a promise to facilitate chaining.
For example, `UIView.animate(.promise)` returns `Guarantee<Bool>` because UIKits
completion API supplies a `Bool`. However, we usually dont need this value and
can chain more simply if it is discarded (that is, converted to `Void`). We can use
`asVoid()` to achieve this conversion:
```swift
UIView.animate(.promise, duration: 0.3) {
self.button1.alpha = 0
}.asVoid().done(self.nextStep)
```
For situations in which we are combining many promises into a `when`, `asVoid()`
becomes essential:
```swift
let p1 = foo()
let p2 = bar()
let p3 = baz()
//…
let p10 = fluff()
when(fulfilled: p1.asVoid(), p2.asVoid(), /*…*/, p10.asVoid()).then {
let value1 = p1.value! // safe bang since all the promises fulfilled
// …
let value10 = p10.value!
}.catch {
//…
}
```
You normally don't have to do this explicitly because `when` does it for you
for up to 5 parameters.
## Blocking (Await)
Sometimes you have to block the main thread to await completion of an asynchronous task.
In these cases, you can (with caution) use `wait`:
```swift
public extension UNUserNotificationCenter {
var wasPushRequested: Bool {
let settings = Guarantee(resolver: getNotificationSettings).wait()
return settings != .notDetermined
}
}
```
The task under the promise **must not** call back onto the current thread or it
will deadlock.
## Starting a Chain on a Background Queue/Thread
`firstly` deliberately does not take a queue. A detailed rationale for this choice
can be found in the ticket tracker.
So, if you want to start a chain by dispatching to the background, you have to use
`DispatchQueue.async`:
```swift
DispatchQueue.global().async(.promise) {
return value
}.done { value in
//…
}
```
However, this function cannot return a promise because of Swift compiler ambiguity
issues. Thus, if you must start a promise on a background queue, you need to
do something like this:
```swift
Promise { seal in
DispatchQueue.global().async {
seal(value)
}
}.done { value in
//…
}
```
Or more simply (though with caveats; see the documentation for `wait`):
```swift
DispatchQueue.global().async(.promise) {
return try fetch().wait()
}.done { value in
//…
}
```
However, you shouldn't need to do this often. If you find yourself wanting to use
this technique, perhaps you should instead modify the code for `fetch` to make it do
its work on a background thread.
Promises abstract asynchronicity, so exploit and support that model. Design your
APIs so that consumers dont have to care what queue your functions run on.

View File

@ -1,13 +1,13 @@
# Common Patterns
One feature of promises that makes them so useful is that thet are composable;
enabling complex, yet safe asynchronous patterns that would otherwise be quite
intimidating with traditional methods.
One feature of promises that makes them particularly useful is that they are composable.
This fact enables complex, yet safe asynchronous patterns that would otherwise be quite
intimidating when implemented with traditional methods.
## Chaining
The most common pattern with promises is chaining:
The most common pattern is chaining:
```swift
firstly {
@ -18,44 +18,50 @@ firstly {
set($0)
return animate()
}.ensure {
cleanup()
// something that should happen whatever the outcome
}.catch {
handle(error: $0)
}
```
If you return a promise in a `then` the next `then` *waits* on that promise
If you return a promise in a `then`, the next `then` *waits* on that promise
before continuing. This is the essence of promises.
Composing promises is easy, and they thus encourage you to develop great apps
without fear for the typical spaghetti (and associated refactoring pains) of
Promises are easy to compose, so they encourage you to develop highly asynchronous
apps without fear of the spaghetti code (and associated refactoring pains) of
asynchronous systems that use completion handlers.
## APIs That Use Promises
Promises are composable, return them instead of providing completion blocks:
Promises are composable, so return them instead of accepting completion blocks:
```swift
class MyRestAPI {
func user() -> Promise<User> {
return URLSession.shared.dataTask(url).asDictionary().then { dict in
return User(dict: dict)
return firstly {
URLSession.shared.dataTask(.promise, with: url)
}.compactMap {
try JSONSerialization.jsonObject(with: $0.data) as? [String: Any]
}.map { dict in
User(dict: dict)
}
}
func avatar() -> Promise<UIImage> {
return user().then { user in
URLSession.shared.dataTask(user.imageUrl)
}.then {
UIImage(data: $0)
URLSession.shared.dataTask(.promise, with: user.imageUrl)
}.compactMap {
UIImage(data: $0.data)
}
}
}
```
This way, your asynchronous systems can easily be engaged in chains all over
your apps.
This way, asynchronous chains can cleanly and seamlessly incorporate code from all over
your app without violating architectural boundaries.
> *Note*: We provide [promises for Alamofire](https://github.com/PromiseKit/Alamofire-) too!
## Background Work
@ -64,31 +70,39 @@ your apps.
class MyRestAPI {
func avatar() -> Promise<UIImage> {
let bgq = DispatchQueue.global(qos: .userInitiated)
return user().then(on: bgq) { user in
URLSession.shared.dataTask(user.imageUrl)
}.then(on: bgq) {
return firstly {
user()
}.then(on: bgq) { user in
URLSession.shared.dataTask(.promise, with: user.imageUrl)
}.compactMap(on: bgq) {
UIImage(data: $0)
}
}
}
```
All PromiseKit handlers take an `on` parameter allowing you to choose the queue
the handler executes upon. The default is always the main queue.
All PromiseKit handlers take an `on` parameter that lets you designate the dispatch queue
on which to run the handler. The default is always the main queue.
PromiseKit is *entirely* thread safe.
> *Tip*: With caution, you can have all `then`, `map`, `compactMap`, etc., run on
a background queue. See `PromiseKit.conf`. Note that we suggest only changing
the queue for the `map` suite of functions, so `done` and `catch` will
continue to run on the main queue, which is *usually* what you want.
## Failing Chains
If an error occurs mid chain, simply throw:
If an error occurs mid-chain, simply throw an error:
```swift
foo().then { baz in
return bar(baz)
firstly {
foo()
}.then { baz in
bar(baz)
}.then { result in
if result.isBad { throw MyError.myIssue }
guard !result.isBad else { throw MyError.myIssue }
//…
return doOtherThing()
}
@ -96,22 +110,28 @@ foo().then { baz in
The error will surface at the next `catch` handler.
Thus if you call a throwing function, you don't have to wrap it in a `do`:
Since promises handle thrown errors, you don't have to wrap calls to throwing functions
in a `do` block unless you really want to handle the errors locally:
```swift
foo().then { baz in
return bar(baz)
bar(baz)
}.then { result in
return try doOtherThing()
try doOtherThing()
}.catch { error in
// if doOtherThing() throws, we end up here
}
```
> *Tip*: Swift lets you define an inline `enum Error` inside the function you
are working on. This isnt *great* coding practice, but it's better than
avoiding throwing an error because you couldn't be bothered to define a good global
`Error` `enum`.
## Abstracting Away Asychronicity
```switch
## Abstracting Away Asynchronicity
```swift
var fetch = API.fetch()
override func viewDidAppear() {
@ -126,9 +146,9 @@ func buttonPressed() {
}
}
func refresh() {
func refresh() -> Promise {
// ensure only one fetch operation happens at a time
if fetch.isResolved {
startSpinner()
fetch = API.fetch().ensure {
@ -139,11 +159,11 @@ func refresh() {
}
```
With promises you dont need to worry about *when* your asynchronous operation
finishes: act like it already has.
With promises, you dont need to worry about *when* your asynchronous operation
finishes. Just act like it already has.
> Above we can see that you can call `then` as many times on a promise as you
> like, they will all be executed in the order they were added.
Above, we see that you can call `then` as many times on a promise as you
like. All the blocks will be executed in the order they were added.
## Chaining Sequences
@ -153,23 +173,40 @@ When you have a series of tasks to perform on an array of data:
```swift
// fade all visible table cells one by one in a “cascading” effect
let fade = Promise()
var fade = Guarantee()
for cell in tableView.visibleCells {
fade = fade.then {
UIView.promise(animateWithDuration:0.1) {
UIView.animate(.promise, duration: 0.1) {
cell.alpha = 0
}
}
}
fade.then {
//finish
fade.done {
// finish
}
```
Note *usually* you want `when()` since `when` executes all the promises in
parallel and thus is much faster to complete. Use the above pattern in
situations where tasks *must* be done sequentially; animation is a good example.
Or if you have an array of closures that return promises:
```swift
var foo = Promise()
for nextPromise in arrayOfClosuresThatReturnPromises {
foo = foo.then(nextPromise)
// ^^ you rarely would want an array of promises instead, since then
// they have all already started, you may as well use `when()`
}
foo.done {
// finish
}
```
> *Note*: You *usually* want `when()`, since `when` executes all of its
component promises in parallel and so completes much faster. Use the pattern
shown above in situations where tasks *must* be run sequentially; animation
is a good example.
> We also provide `when(concurrently:)`, which lets you schedule more than
one promise at a time if you need to.
## Timeout
@ -182,96 +219,120 @@ race(when(fulfilled: fetches).asVoid(), timeout).then {
}
```
`race` continues as soon as one of the promises it watches finishes.
`race` continues as soon as one of the promises it is watching finishes.
> Common pitfalls: ensure the promises you pass to `race` are the same type.
> The easiest way to ensure this is using `asVoid()`.
Make sure the promises you pass to `race` are all of the same type. The easiest way
to ensure this is to use `asVoid()`.
> Please note if any promise you pass rejects, then `race` will be rejected.
Note that if any component promise rejects, the `race` will reject, too.
# Minimum Duration
Sometimes you need a task to take *at least* a certain amount of time. (For example,
you want to show a progress spinner, but if it shows for less than 0.3 seconds, the UI
appears broken to the user.)
```swift
let waitAtLeast = after(seconds: 0.3)
firstly {
foo()
}.then {
waitAtLeast
}.done {
//…
}
```
The code above works because we create the delay *before* we do work in `foo()`. By the
time we get to waiting on that promise, either it will have already timed out or we will wait
for whatever remains of the 0.3 seconds before continuing the chain.
## Cancellation
Promises dont have a `cancel` function, but they do support cancellation via a
Promises dont have a `cancel` function, but they do support cancellation through a
special error type that conforms to the `CancellableError` protocol.
```swift
func foo() -> (Promise<Void>, cancel: () -> Void) {
let task = Task(…)
var cancelme = false
let promise = Promise<Void> { fulfill, reject in
let task = Task(…)
let cancel = {
cancelme = true
task.cancel()
reject(NSError.cancelledError)
}
let promise = Promise<Void> { seal in
task.completion = { value in
guard !cancelme else { reject(NSError.cancelledError) }
fulfill(value)
guard !cancelme else { return reject(PMKError.cancelled) }
seal.fulfill(value)
}
task.start()
}
let cancel = {
cancelme = true
task.cancel()
}
return (promise, cancel)
}
```
> Promises dont have a cancel function because you dont want code outside of
> your control to be able to cancel your operations *unless* you explicitly want
> that. In cases where you want it, then it varies how it should work depending
> on how the underlying task supports cancellation. Thus we have provided
> primitives but not concrete API.
Promises dont have a `cancel` function because you dont want code outside of
your control to be able to cancel your operations--*unless*, of course, you explicitly
want to enable that behavior. In cases where you do want cancellation, the exact way
that it should work will vary depending on how the underlying task supports cancellation.
PromiseKit provides cancellation primitives but no concrete API.
Cancelled chains do not call a `catch` handler by default. However you can
Cancelled chains do not call `catch` handlers by default. However you can
intercept cancellation if you like:
```swift
foo.then {
//…
}.catch(policy: .allErrorsIncludingCancellation) {
}.catch(policy: .allErrors) {
// cancelled errors are handled *as well*
}
```
**Important**, canceling the chain is *not* the same as canceling the underlying
asynchronous task. Promises are a wrapper around asynchronicity but they have no
control over the underlying tasks. If you need to cancel the underlying task you
**Important**: Canceling a promise chain is *not* the same as canceling the underlying
asynchronous task. Promises are wrappers around asynchronicity, but they have no
control over the underlying tasks. If you need to cancel an underlying task, you
need to cancel the underlying task!
> The library [CancellablePromiseKit](https://github.com/johannesd/CancellablePromiseKit) extends the concept of Promises to fully cover cancellable tasks.
## Retry / Polling
```swift
func attempt<T>(interdelay: DispatchTimeInterval = .seconds(2), maxRepeat: Int = 3, body: @escaping () -> Promise<T>) -> Promise<T> {
func attempt<T>(maximumRetryCount: Int = 3, delayBeforeRetry: DispatchTimeInterval = .seconds(2), _ body: @escaping () -> Promise<T>) -> Promise<T> {
var attempts = 0
func attempt() -> Promise<T> {
attempts += 1
return body().recover { error -> Promise<T> in
guard attempts < maxRepeat else { throw error }
return after(interval: interdelay).then {
return attempt()
}
guard attempts < maximumRetryCount else { throw error }
return after(delayBeforeRetry).then(on: nil, attempt)
}
}
return attempt()
}
attempt{ flakeyTask() }.then {
attempt(maximumRetryCount: 3) {
flakeyTask(parameters: foo)
}.then {
//…
}.catch { _ in
// we attempted three times but still failed
}
```
Probably you should supplement the above so that you only re-attempt for
In most cases, you should probably supplement the code above so that it re-attempts only for
specific error conditions.
## Wrapping Delegate Systems
Be careful with Promises and delegate systems as they are not always suited.
Promises complete *once* where most delegate systems call their callbacks many
Be careful with Promises and delegate systems, as they are not always compatible.
Promises complete *once*, whereas most delegate systems may notify their delegate many
times. This is why, for example, there is no PromiseKit extension for a
`UIButton`.
@ -286,7 +347,7 @@ extension CLLocationManager {
}
class PMKCLLocationManagerProxy: NSObject, CLLocationManagerDelegate {
private let (promise, fulfill, reject) = Promise<[CLLocation]>.pending()
private let (promise, seal) = Promise<[CLLocation]>.pending()
private var retainCycle: PMKCLLocationManagerProxy?
private let manager = CLLocationManager()
@ -294,19 +355,19 @@ class PMKCLLocationManagerProxy: NSObject, CLLocationManagerDelegate {
super.init()
retainCycle = self
manager.delegate = self // does not retain hence the `retainCycle` property
promise.ensure {
// ensure we break the retain cycle
self.retainCycle = nil
}
}
@objc fileprivate func locationManager(_: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
fulfill(locations)
seal.fulfill(locations)
}
@objc func locationManager(_: CLLocationManager, didFailWithError error: Error) {
reject(error)
seal.reject(error)
}
}
@ -319,49 +380,49 @@ CLLocationManager.promise().then { locations in
}
```
> Please note, we provide this promise with our CoreLocation extensions at
> Please note: we provide this promise with our CoreLocation extensions at
> https://github.com/PromiseKit/CoreLocation
## Recovery
Sometimes you dont want an error to cascade, instead you have a default value:
Sometimes you dont want an error to cascade. Instead, you want to supply a default result:
```
CLLocationManager.promise().recover { error -> CLLocation in
```swift
CLLocationManager.requestLocation().recover { error -> Promise<CLLocation> in
guard error == MyError.airplaneMode else {
throw error
}
return CLLocation.savannah
}.then { location in
return .value(CLLocation.savannah)
}.done { location in
//…
}
```
Be careful not to ignore all errors; recover only those errors that make sense.
Be careful not to ignore all errors, though! Recover only those errors that make sense to recover.
## Promises for modal view-controllers
## Promises for Modal View Controllers
```swift
class ViewController: UIViewController {
private let (promise, seal) = Promise<…>.pending()
private let (promise, seal) = Guarantee<…>.pending() // use Promise if your flow can fail
func show(in: UIViewController) -> Promise<…> {
in.show(self, sender: in)
return promise
}
func done() {
dismiss(animated: true)
seal(…)
seal.fulfill(…)
}
}
// use:
ViewController().show(in: self).then {
ViewController().show(in: self).done {
//…
}.catch { error in
//…
@ -369,13 +430,13 @@ ViewController().show(in: self).then {
```
This is the best approach we have found, which is a pity as it requires the
presentee to control the presentation and the presentee to be dismiss itself
presentee to control the presentation and requires the presentee to dismiss itself
explicitly.
Nothing seemingly can beat Storyboard segues for decoupling an app's router.
Nothing seems to beat storyboard segues for decoupling an app's controllers.
## Saving previous results
## Saving Previous Results
Lets say you have:
@ -383,49 +444,51 @@ Lets say you have:
```swift
login().then { username in
fetch(avatar: username)
}.then { image in
}.done { image in
//…
}
```
In the second `then` how can you access `username` as well as `image`?
What if you want access to both `username` and `image` in your `done`?
The most obvious way is with nesting:
The most obvious way is to use nesting:
```swift
login().then { username in
fetch(avatar: username).then { image in
// we have image and username
fetch(avatar: username).done { image in
// we have access to both `image` and `username`
}
}.then {
}.done {
// the chain still continues as you'd expect
}
```
However you could instead use Swift tuples:
However, such nesting reduces the clarity of the chain. Instead, we could use Swift
tuples:
```swift
login().then { username in
fetch(avatar: username).then { ($0, username) }
fetch(avatar: username).map { ($0, username) }
}.then { image, username in
//…
}
```
The above is a quick transforming `then` that simply maps the `Promise<String>`
into `Promise<(UIImage, String)>`.
The code above simply maps `Promise<String>` into `Promise<(UIImage, String)>`.
## Waiting on multiple promises whatever their result
## Waiting on Multiple Promises, Whatever Their Result
Use `when(resolved:)`:
```swift
when(resolved: a, b).then { (results: [Result<T>]) in
when(resolved: a, b).done { (results: [Result<T>]) in
// `Result` is an enum of `.fulfilled` or `.rejected`
}
// ^^ cannot call `catch` as `when(resolved:)` returns a `Guarantee`
```
Generally you don't want this, people ask for it a lot, but usually they
actually just want to use `recover` on one of the promises. Usually you don't
want to ignore errors. Errors happen, they should be handled.
Generally, you don't want this! People ask for it a lot, but usually because
they are trying to ignore errors. What they really need is to use `recover` on one of the
promises. Errors happen, so they should be handled; you usually don't want to ignore them.

View File

@ -15,7 +15,7 @@ import PromiseKit
* Completely _ignores_ server caching headers!
*/
private let q = DispatchQueue(label: "org.promisekit.cache.image")
private let q = DispatchQueue(label: "org.promisekit.cache.image", attributes: .concurrent)
private var active: [URL: Promise<Data>] = [:]
private var cleanup = Promise()
@ -29,7 +29,7 @@ public func fetch(image url: URL) -> Promise<Data> {
return promise
}
q.sync(flags: .barrier) {
q.sync {
promise = Promise(.start) {
let dst = try url.cacheDestination()

View File

@ -0,0 +1,102 @@
#!/usr/bin/swift sh
import Foundation
import PromiseKit // @mxcl ~> 6.5
import Swifter // @mattdonnelly == b27a89
let swifter = Swifter(
consumerKey: "FILL",
consumerSecret: "ME",
oauthToken: "IN",
oauthTokenSecret: "https://developer.twitter.com/en/docs/basics/apps/overview.html"
)
extension JSON {
var date: Date? {
guard let string = string else { return nil }
let formatter = DateFormatter()
formatter.dateFormat = "EEE MMM dd HH:mm:ss Z yyyy"
return formatter.date(from: string)
}
}
let twoMonthsAgo = Date() - 24*60*60*30*2
print("Deleting qualifying tweets before:", twoMonthsAgo)
func deleteTweets(maxID: String? = nil) -> Promise<Void> {
return Promise { seal in
swifter.getTimeline(for: "mxcl", count: 200, maxID: maxID, success: { json in
if json.array!.count <= 1 {
// if we get one result for a requested maxID, we're done
return seal.fulfill()
}
for item in json.array! {
let date = item["created_at"].date!
guard date < twoMonthsAgo, item["favorite_count"].integer! < 2 else {
continue
}
swifter.destroyTweet(forID: id, success: { _ in
print("D:", item["text"].string!)
}, failure: seal.reject)
}
let next = json.array!.last!["id_str"].string!
deleteTweets(maxID: next).pipe(to: seal.resolve)
}, failure: seal.reject)
}
}
func deleteFavorites(maxID: String? = nil) -> Promise<Void> {
return Promise { seal in
swifter.getRecentlyFavoritedTweets(count: 200, maxID: maxID, success: { json in
if json.array!.count <= 1 {
return seal.fulfill()
}
for item in json.array! {
guard item["created_at"].date! < twoMonthsAgo else { continue }
swifter.unfavoriteTweet(forID: item["id_str"].string!, success: { _ in
print("D❤:", item["text"].string!)
}, failure: seal.reject)
}
let next = json.array!.last!["id_str"].string!
deleteFavorites(maxID: next).pipe(to: seal.resolve)
}, failure: seal.reject)
}
}
func unblockPeople(cursor: String? = nil) -> Promise<Void> {
return Promise { seal in
swifter.getBlockedUsersIDs(stringifyIDs: "true", cursor: cursor, success: { json, prev, next in
for id in json.array! {
print("Unblocking:", id)
swifter.unblockUser(for: .id(id.string!))
}
if let next = next, !next.isEmpty, next != prev, next != "0" {
unblockPeople(cursor: next).pipe(to: seal.resolve)
} else {
seal.fulfill()
}
}, failure: seal.reject)
}
}
firstly {
when(fulfilled: deleteTweets(), deleteFavorites(), unblockPeople())
}.done {
exit(0)
}.catch {
print("error:", $0)
exit(1)
}
RunLoop.main.run()

View File

@ -1,31 +1,87 @@
# FAQ
## Why should I use PromiseKit over X-Promises-Foo?
* PromiseKit has a heavy focus on **developer experience**. Youre a developer; do you care about your experience? Yes? Then pick PromiseKit.
* Do you care about having any bugs you find fixed? Then pick PromiseKit.
* Do you care about having your input heard and reacted to in a fast fashion? Then pick PromiseKit.
* Do you want a library that has been maintained continuously and passionately for 6 years? Then pick PromiseKit.
* Do you want a library that the community has chosen to be their №1 Promises/Futures library? Then pick PromiseKit.
* Do you want to be able to use Promises with Apples SDKs rather than having to do all the work of writing the Promise implementations yourself? Then pick PromiseKit.
* Do you want to be able to use Promises with Swift 3.x, Swift 4.x, ObjC, iOS, tvOS, watchOS, macOS, Android & Linux? Then pick PromiseKit.
* PromiseKit verifies its correctness by testing against the entire [Promises/A+ test suite](https://github.com/promises-aplus/promises-tests).
## How do I create a fulfilled `Void` promise?
```swift
let foo = Promise()
// or:
let bar = Promise.value(())
```
## How do I “early `return`”?
```swift
func foo() -> Promise<Void> {
guard thingy else {
return Promise()
}
//…
}
func bar() -> Promise<SomethingNotVoid> {
guard thingy else {
return .value(instanceOfSomethingNotVoid)
}
//…
}
```
## Do I need to worry about retain cycles?
Generally no, provided the promise completes then all handlers are released thus
Generally, no. Once a promise completes, all handlers are released and so
any references to `self` are also released.
This does mean that if your chain contains side-effects that you would typically
not want to happen after, say, a view controller is popped then you should still
use `weak self` (and check for `self == nil`) to prevent any such side-effects.
However, if your chain contains side effects that you would typically
not want to happen after, say, a view controller is popped, then you should still
use `weak self` (and check for `self == nil`) to prevent any such side effects.
[This stackoverflow question](https://stackoverflow.com/questions/39281214/should-i-use-weak-self-in-promisekit-blocks)
has some good discussion on the topic.
*However*, in our experience most things that developers consider side effects that
should be protected against are in fact *not* side effects.
Side effects include changes to global application state. They *do not* include
changing the display state of a view-controller. So, protect against setting `UserDefaults` or
modifying the application database, and don't bother protecting against changing
the text in a `UILabel`.
[This StackOverflow question](https://stackoverflow.com/questions/39281214/should-i-use-weak-self-in-promisekit-blocks)
has some good discussion on this topic.
## Do I need to retain my promises?
No. Every promise handler retains its promise until the handler is executed. Once
all handlers have been executed, the promise is deallocated. So you only need to retain
the promise if you need to refer to its final value after its chain has completed.
## Where should I put my `catch`?
`catch` deliberately terminates the chain, you should place low in your promise
heirarchy: at as root a point as possible. Typically this would be your view
controllers where your `catch` can then display a message to the user.
`catch` deliberately terminates the chain. You should put it low in your promise
hierarchy at a point as close to the root as possible. Typically, this would be
somewhere such as a view controller, where your `catch` can then display a message
to the user.
This means you should be writing one catch for many `then`s and be returning
promises without there being `catch` handlers.
This means you should be writing one catch for many `then`s and returning
promises that do not have internal `catch` handlers of their own.
This is obviously a guideline, do what is necessary.
This is obviously a guideline; do what is necessary.
## How do branched chains work?
If you have a promise:
Suppose you have a promise:
```
let promise = foo()
@ -43,12 +99,12 @@ promise.then {
}
```
You now have a branched chain. When `promise` resolves both chains receive its
value. However the two chains are entirely separate and Swift will prompt you
to ensure both have `catch` handlers.
You now have a branched chain. When `promise` resolves, both chains receive its
value. However, the two chains are entirely separate and Swift will prompt you
to ensure that both have `catch` handlers.
Probably however you can ignore the catch for one, but be careful in these
situations as Swift cannot help you ensure your chains are error handled.
You can most likely ignore the `catch` for one of these branches, but be careful:
in these situations, Swift cannot help you ensure that your chains are error-handled.
```
promise.then {
@ -80,22 +136,23 @@ when(fulfilled: p1, p2).catch { error in
}
```
> It's worth noting that you can add multiple `catch` handlers to a promise too,
> and indeed, both will be called if the chain is rejected.
> It's worth noting that you can add multiple `catch` handlers to a promise, too.
> And indeed, both will be called if the chain is rejected.
## Is PromiseKit “heavy”?
No, PromiseKit is hardly any sources in fact, it is “light-weight”. Any
“weight” relative to other promise implementations is 6 years of bug fixes or
important things like [Zalgo prevention](http://blog.izs.me/post/59142742143/designing-apis-for-asynchrony)
No. PromiseKit contains hardly any source code. In fact, it is quite lightweight. Any
“weight” relative to other promise implementations derives from 6 years of bug fixes
and tuning, from the fact that we have *stellar* Objective-C-to-Swift bridging and
from important things such as [Zalgo prevention](http://blog.izs.me/post/59142742143/designing-apis-for-asynchrony)
that hobby-project implementations dont consider.
## Why is debugging hard?
Because promises always execute via `dispatch` the backtraces you get have less
information than is often required to trace the path of execution.
Because promises always execute via dispatch, the backtrace you see at the point of
an error has less information than is usually required to trace the path of execution.
One solution is (during debugging) to turn off the dispatch:
One solution is to turn off dispatch during debugging:
```swift
// Swift
@ -105,19 +162,22 @@ DispatchQueue.default = zalgo
PMKSetDefaultDispatchQueue(zalgo)
```
Dont leave this is on, we always dispatch to avoid you accidentally writing
a common bug pattern: http://blog.izs.me/post/59142742143/designing-apis-for-asynchrony
Dont leave this on. In normal use, we always dispatch to avoid you accidentally writing
a common bug pattern. See [this blog post](http://blog.izs.me/post/59142742143/designing-apis-for-asynchrony).
## Where is `all()`?
Some promise libraries provide `all`, we provide `when`, it is the same. `when`
was chosen as it is the more common choice which we also think reads better.
Some promise libraries provide `all` for awaiting multiple results. We call this function
`when`, but it is the same thing. We chose `when` because it's the more common term and
because we think it reads better in code.
## How can I test APIs that return promises?
You need to use `XCTestExpectation`.
We also define `wait()` and `hang()`. Use them if you must, but be careful because they
block the current thread!
## Is PromiseKit thread-safe?
Yes, entirely.
@ -125,24 +185,68 @@ Yes, entirely.
However the code *you* write in your `then`s might not be!
Just make sure you dont access state outside the chain from concurrent queues.
By default PromiseKit handlers run on the `main` thread, which is serial, so
typically you won't have to worry about this.
By default, PromiseKit handlers run on the `main` thread, which is serial, so
you typically won't have to worry about this.
## Why are there separate classes for Objective-C and Swift?
`Promise<T>` is generic and and thus cannot be represented by Objective-C.
`Promise<T>` is generic and and so cannot be represented by Objective-C.
## Does PromiseKit conform to Promises/A+?
Yes, we have tests that prove this.
Yes. We have tests that prove this.
## How do PromiseKit and RxSwift differ?
## How do PromiseKit and RxSwift/ReactiveSwift differ?
https://github.com/mxcl/PromiseKit/issues/484
PromiseKit is a lot simpler.
## Why cant I return from a catch like I can in Javascript?
The top-level difference between PromiseKit and RxSwift is that RxSwift `Observable`s (roughly
analogous to PromiseKit `Promise`s) do not necessarily return a single result: they may emit
zero, one, or an infinite stream of values. This small conceptual change leads to an API
that's both surprisingly powerful and surprisingly complex.
Swift demands functions with one purpose, thus we have two error handlers:
RxSwift requires commitment to a paradigm shift in how you program. It proposes that you
restructure your code as a matrix of interacting value pipelines. When applied properly
to a suitable problem, RxSwift can yield great benefits in robustness and simplicity.
But not all applications are suitable for RxSwift.
By contrast, PromiseKit selectively applies the best parts of reactive programming
to the hardest part of pure Swift development, the management of asynchronicity. It's a broadly
applicable tool. Most asynchronous code can be clarified, simplified and made more robust
just by converting it to use promises. (And the conversion process is easy.)
Promises make for code that is clear to most developers. RxSwift, perhaps not. Take a look at this
[sign-up panel](https://github.com/ReactiveX/RxSwift/tree/master/RxExample/RxExample/Examples/GitHubSignup)
implemented in RxSwift and see what you think. (Note that this is one of RxSwift's own examples.)
Even where PromiseKit and RxSwift are broadly similar, there are many differences in implementation:
* RxSwift has a separate API for chain-terminating elements ("subscribers") versus interior
elements. In PromiseKit, all elements of a chain use roughly the same code pattern.
* The RxSwift API to define an interior element of a chain (an "operator") is hair-raisingly complex.
So, RxSwift tries hard to supply every operator you might ever want to use right off the shelf. There are
hundreds. PromiseKit supplies a few utilities to help with specific scenarios, but because it's trivial
to write your own chain elements, there's no need for all this extra code in the library.
* PromiseKit dispatches the execution of every block. RxSwift dispatches only when told to do so. Moreover, the
current dispatching state is an attribute of the chain, not the specific block, as it is in PromiseKit.
The RxSwift system is more powerful but more complex. PromiseKit is simple, predictable and safe.
* In PromiseKit, both sides of a branched chain refer back to their shared common ancestors. In RxSwift,
branching normally creates a duplicate parallel chain that reruns the code at the head of the chain...except
when it doesn't. The rules for determining what will actually happen are complex, and given
a chain created by another chunk of code, you can't really tell what the behavior will be.
* Because RxSwift chains don't necessarily terminate on their own, RxSwift needs you to take on some
explicit garbage collection duties to ensure that pipelines that are no longer needed are properly
deallocated. All promises yield a single value, terminate and then automatically deallocate themselves.
You can find some additional discussion in [this ticket](https://github.com/mxcl/PromiseKit/issues/484).
## Why cant I return from a catch like I can in JavaScript?
Swift demands that functions have one purpose. Thus, we have two error handlers:
* `catch`: ends the chain and handles errors
* `recover`: attempts to recover from errors in a chain
@ -152,35 +256,42 @@ You want `recover`.
## When do promises “start”?
Often people are confused about when Promises “start”. Is it immediately? Is it
later? Is it when you call then?
The answer is: promises do not choose when the underlying task they represent
starts. That is up to that task. For example here is the code for a simple
promise that wraps Alamofire:
later? Is it when you call `then`?
The answer is: The promise **body** executes during initialization of the promise, on the current thread.
As an example, `"Executing the promise body"` will be printed to the console right after the promise is created,
without having to call `then` on the promise.
```swift
func foo() -> Promise<Any>
return Promise { fulfill, reject in
Alamofire.request(rq).responseJSON { rsp in
if let error = rsp.error {
reject(error)
} else {
fulfill(rsp.value)
}
}
let testPromise = Promise<Bool> {
print("Executing the promise body.")
return $0.fulfill(true)
}
```
But what about asynchronous tasks that you create in your promise's body? They behave the same way as they would
without using PromiseKit. Here's a simple example:
```swift
let testPromise = Promise<Bool> { seal in
print("Executing the promise body.")
DispatchQueue.main.asyncAfter(deadline: .now() + 3.0) {
print("Executing asyncAfter.")
return seal.fulfill(true)
}
}
```
Who chooses when this promise starts? The answer is: Alamofire does and in this
case, it “starts” immediately when `foo()` is called.
The message `"Executing the promise body."` is being logged right away, but the message `"Executing asyncAfter."`
is only logged three seconds later. In this case `DispatchQueue` is responsible for deciding when to execute
the task you pass to it, PromiseKit has nothing to do with it.
## What is a good way to use Firebase with PromiseKit
There is no good way to use Firebase with PromiseKit. See the next question for rationale.
There is no good way to use Firebase with PromiseKit. See the next question for
a more detailed rationale.
The best option is to embed your chain in your firebase handler:
The best option is to embed your chain in your Firebase handler:
```
foo.observe(.value) { snapshot in
@ -196,9 +307,91 @@ foo.observe(.value) { snapshot in
}
```
## I need my `then` to fire multiple times
Then were afraid that you cannot use PromiseKit for that event. Promises only resolve `once`, this is the fundamental nature of promises and is considered a feature since it gives you guarantees about the flow of your chains.
Then were afraid you cannot use PromiseKit for that event. Promises only
resolve *once*. This is the fundamental nature of promises and it is considered a
feature because it gives you guarantees about the flow of your chains.
## How do I change the default queues that handlers run on?
You can change the values of `PromiseKit.conf.Q`. There are two variables that
change the default queues that the two kinds of handler run on. A typical
pattern is to change all your `then`-type handlers to run on a background queue
and to have all your “finalizers” run on the main queue:
```
PromiseKit.conf.Q.map = .global()
PromiseKit.conf.Q.return = .main //NOTE this is the default
```
Be very careful about setting either of these queues to `nil`. It has the
effect of running *immediately*, and this is not what you usually want to do in
your application. This is, however, useful when you are running specs and want
your promises to resolve immediately. (This is basically the same idea as "stubbing"
an HTTP request.)
```swift
// in your test suite setup code
PromiseKit.conf.Q.map = nil
PromiseKit.conf.Q.return = nil
```
## How do I use PromiseKit on the server side?
If your server framework requires that the main queue remain unused (e.g., Kitura),
then you must use PromiseKit 6 and you must tell PromiseKit not to dispatch to the
main queue by default. This is easy enough:
```swift
PromiseKit.conf.Q = (map: DispatchQueue.global(), return: DispatchQueue.global())
```
> Note, we recommend using your own queue rather than `.global()`, we've seen better performance this way.
Heres a more complete example:
```swift
import Foundation
import HeliumLogger
import Kitura
import LoggerAPI
import PromiseKit
HeliumLogger.use(.info)
let pmkQ = DispatchQueue(label: "pmkQ", qos: .default, attributes: .concurrent, autoreleaseFrequency: .workItem)
PromiseKit.conf.Q = (map: pmkQ, return: pmkQ)
let router = Router()
router.get("/") { _, response, next in
Log.info("Request received")
after(seconds: 1.0).done {
Log.info("Sending response")
response.send("OK")
next()
}
}
Log.info("Starting server")
Kitura.addHTTPServer(onPort: 8888, with: router)
Kitura.run()
```
## How do I control console output?
By default PromiseKit emits console messages when certain events occur. These events include:
- A promise or guarantee has blocked the main thread
- A promise has been deallocated without being fulfilled
- An error which occurred while fulfilling a promise was swallowed using cauterize
You may turn off or redirect this output by setting a thread safe closure in [PMKConfiguration](https://github.com/mxcl/PromiseKit/blob/master/Sources/Configuration.swift) **before** processing any promises. For example, to turn off console output:
```swift
conf.logHandler = { event in }
```
## My question was not answered

View File

@ -1,4 +1,4 @@
# `then`
# `then` and `done`
Here is a typical promise chain:
@ -7,18 +7,18 @@ firstly {
login()
}.then { creds in
fetch(avatar: creds.user)
}.then { image in
}.done { image in
self.imageView = image
}
```
If this code used completion handlers it would look like this:
If this code used completion handlers, it would look like this:
```swift
login { creds, error in
if let creds = creds {
fetch(avatar: creds.user) { image, error in
if let image = image else {
if let image = image {
self.imageView = image
}
}
@ -26,63 +26,77 @@ login { creds, error in
}
```
`then` *is* just another way to do completion handlers, but it is also quite a bit more. At this
initial stage of our understanding it merely helps readability. The promise chain above is easy
to read, one asynchronous operation leads into the other, read line by line. It's as close to
procedural code as we can easily get with the current state of Swift.
`then` *is* just another way to structure completion handlers, but it is also quite a
bit more. At this initial stage of our understanding, it mostly helps
readability. The promise chain above is easy to scan and understand: one asynchronous
operation leads into the other, line by line. It's as close to
procedural code as we can easily come given the current state of Swift.
`done` is the same as `then` but you cannot return a promise. It is
typically the end of the “success” part of the chain. Above, you can see that we
receive the final image in our `done` and use it to set up the UI.
Lets compare the signatures of the two login methods:
```swift
func login() -> Promise<Creds>
// compared with:
// Compared with:
func login(completion: (Creds?, Error?) -> Void)
// ^^ ugh. Optionals. Double optionals.
```
The distinction is that with promises your functions returns *promises*. So for each handler in our
chain we return a promise. By doing this we can call `then`. Each `then` waits on its promise, so the
chains resolve procedurally, one at a time.
The distinction is that with promises, your functions return *promises* instead
of accepting and running callbacks. Each handler in a chain returns a promise.
`Promise` objects define the `then` method, which waits for the completion of the
promise before continuing the chain. Chains resolve procedurally, one promise
at a time.
A Promise represents the future value of an asynchronous task. It has a type
that represents the type of object it wraps. In the above example `login` is a
function that returns a `Promise` that *will* represent an instance of `Creds`.
A `Promise` represents the future value of an asynchronous task. It has a type
that represents the type of object it wraps. For example, in the example above,
`login` is a function that returns a `Promise` that *will* represent an instance
of `Creds`.
> *Note*: `done` is new to PromiseKit 5. We previously defined a variant of `then` that
did not require you to return a promise. Unfortunately, this convention often confused
Swift and led to odd and hard-to-debug error messages. It also made using PromiseKit
more painful. The introduction of `done` lets you type out promise chains that
compile without additional qualification to help the compiler figure out type information.
---
You may notice that unlike the completion pattern, the promise chain appear to
ignore errors, this is not the case, instead it is the opposite: the promise
chain makes error handling more accessible and harder to ignore.
You may notice that unlike the completion pattern, the promise chain appears to
ignore errors. This is not the case! In fact, it has the opposite effect: the promise
chain makes error handling more accessible and makes errors harder to ignore.
# `catch`
With promises, errors cascade ensuring your apps are robust and the code,
clearer:
With promises, errors cascade along the promise chain, ensuring that your apps are
robust and your code is clear:
```swift
firstly {
login()
}.then { creds in
fetch(avatar: creds.user)
}.then { image in
}.done { image in
self.imageView = image
}.catch {
// any errors in the whole chain land here
}
```
> In fact, Swift emits a warning if you forget to `catch` a chain. But we'll
> talk about that more later.
> Swift emits a warning if you forget to `catch` a chain. But we'll
> talk about that in more detail later.
Promises each are objects that represent individual asychnronous tasks. If those
tasks fail their promises become *rejected*. Chains that contain rejected
promises skip all subsequent `then`s, instead the next `catch` is executed
(strictly, *any* subsequent `catch` handlers).
Each promise is an object that represents an individual, asynchronous task.
If a task fails, its promise becomes *rejected*. Chains that contain rejected
promises skip all subsequent `then`s. Instead, the next `catch` is executed.
(Strictly speaking, *all* subsequent `catch` handlers are executed.)
For fun lets compare this pattern with a completion handler equivalent:
For fun, lets compare this pattern with its completion handler equivalent:
```swift
func handle(error: Error) {
@ -98,11 +112,11 @@ login { creds, error in
}
```
Use of `guard` and a consolidated error handler help, but the promise chains
The use of `guard` and a consolidated error handler help, but the promise chains
readability speaks for itself.
# `always`
# `ensure`
We have learned to compose asynchronicity. Next lets extend our primitives:
@ -112,19 +126,19 @@ firstly {
return login()
}.then {
fetch(avatar: $0.user)
}.then {
}.done {
self.imageView = $0
}.always {
}.ensure {
UIApplication.shared.isNetworkActivityIndicatorVisible = false
}.catch {
//…
}
```
Whatever the outcome in your chain—failure or success—your `always`
handler is called.
No matter the outcome of your chain—-failure or success—-your `ensure`
handler is always called.
For fun lets compare this pattern with a completion handler equivalent:
Lets compare this pattern with its completion handler equivalent:
```swift
UIApplication.shared.isNetworkActivityIndicatorVisible = true
@ -144,14 +158,34 @@ login { creds, error in
}
```
Here it would be trivial for somebody to amend this code and not unset the activity indicator leading to a bug. With
promises this is almost impossible, the Swift compiler will resist you supplementing the chain without promises, you
almost wont need to review the pull-requests.
It would be very easy for someone to amend this code and forget to unset
the activity indicator, leading to a bug. With promises, this type of error is
almost impossible: the Swift compiler resists your supplementing the chain without
using promises. You almost wont need to review the pull requests.
> *Note*: PromiseKit has perhaps capriciously switched between the names `always`
and `ensure` for this function several times in the past. Sorry about this. We suck.
You can also use `finally` as an `ensure` that terminates the promise chain and does not return a value:
```
spinner(visible: true)
firstly {
foo()
}.done {
//…
}.catch {
//…
}.finally {
self.spinner(visible: false)
}
```
# `when`
With completion handlers reacting to multiple asycnhronous operations is either
With completion handlers, reacting to multiple asynchronous operations is either
slow or hard. Slow means doing it serially:
```swift
@ -178,7 +212,7 @@ operation2 {
result2 = $0
group.leave()
}
group.completion = {
group.notify(queue: .main) {
finish(result1, result2)
}
```
@ -188,32 +222,33 @@ Promises are easier:
```swift
firstly {
when(fulfilled: operation1(), operation2())
}.then { result1, result2 in
}.done { result1, result2 in
//…
}
```
`when` takes promises, waits for them to resolve and returns a promise with the results.
`when` takes promises, waits for them to resolve and returns a promise containing the results.
And of course, if any of them fail the chain calls the next `catch`, like *any* promise chain.
As with any promise chain, if any of the component promises fail, the chain calls the next `catch`.
# PromiseKit Extensions
When we made PromiseKit we understood that we wanted to *only* use promises, thus, wherever possible, we offer
extensions on top of Apples APIs that add promises. For example:
When we made PromiseKit, we understood that we wanted to use *only* promises to implement
asynchronous behavior. So wherever possible, we offer extensions to Apples APIs that reframe
the API in terms of promises. For example:
```swift
firstly {
CLLocationManager.promise()
}.then { location in
CLGeocoder.reverseGeocode(location)
}.then { placemarks in
}.done { placemarks in
self.placemark.text = "\(placemarks.first)"
}
```
To use these you need to specify subspecs:
To use these extensions, you need to specify subspecs:
```ruby
pod "PromiseKit"
@ -221,18 +256,21 @@ pod "PromiseKit/CoreLocation"
pod "PromiseKit/MapKit"
```
All our extensions are available at the [PromiseKit organization](https://github.com/PromiseKit) and you should go there
to see what is available and to read the sources so that you can read the documentation. We have copiously documented every
file and every function.
All of these extensions are available at the [PromiseKit organization](https://github.com/PromiseKit).
Go there to see what's available and to read the source code and documentation. Every file and function
has been copiously documented.
> We also provide extensions for common libraries such as [Alamofire](https://github.com/PromiseKit/Alamofire-).
# Making Promises
You can get a long way with our extensions, but sometimes you have to start chains of your own. Maybe you have a third party
API that doesnt provide promises, or maybe you wrote your own asynchronous system. Either way, we provide the starting point,
and if you were to look at the code of our extensions, you would see it is the same method below as we use ourselves.
The standard extensions will take you a long way, but sometimes you'll still need to start chains
of your own. Maybe you're using a third party API that doesnt provide promises, or perhaps you wrote
your own asynchronous system. Either way, it's easy to add promises. If you look at the code of the
standard extensions, you'll see that it uses the same approach described below.
Lets say we have a method:
Lets say we have the following method:
```swift
func fetch(completion: (String?, Error?) -> Void)
@ -242,40 +280,172 @@ How do we convert this to a promise? Well, it's easy:
```swift
func fetch() -> Promise<String> {
return PromiseKit.wrap(fetch) }
return Promise { fetch(completion: $0.resolve) }
}
```
For more complicated situations use the root-resolver:
You may find the expanded version more readable:
```swift
func fetch() -> Promise<String> {
return Promise { fulfill, reject in
foo { result, error in
if let result = result {
fulfill(result)
} else if let error = error {
reject(error)
} else {
reject(PMKError.invalidCallingConvention)
// ^^ we provide this error so that all paths are handled, even
// this path which technically should never happen (but might!)
}
return Promise { seal in
fetch { result, error in
seal.resolve(result, error)
}
}
}
```
Note with the above example `PromiseKit.wrap(foo)` *would* have worked.
Only use the root-resolver when wrap doesnt work *or* you need to handle
non-typical scenarios.
The `seal` object that the `Promise` initializer provides to you defines
many methods for handling garden-variety completion handlers. It even
covers a variety of rarer situations, thus making it easy for you to add
promises to an existing codebase.
> *Note*: We tried to make it so that you could just do `Promise(fetch)`, but we
were not able to make this simpler pattern work universally without requiring
extra disambiguation for the Swift compiler. Sorry; we tried.
> *Note*: In PMK 4, this initializer provided two parameters to your closure:
`fulfill` and `reject`. PMK 5 and 6 give you an object that has both `fulfill` and
`reject` methods, but also many variants of the method `resolve`. You can
typically just pass completion handler parameters to `resolve` and let Swift figure
out which variant to apply to your particular case (as shown in the example above).
> *Note* `Guarantees` (below) have a slightly different initializer (since they
cannot error) so the parameter to the initializer closure is just a closure. Not
a `Resolver` object. Thus do `seal(value)` rather than `seal.fulfill(value)`. This
is because there is no variations in what guarantees can be sealed with, they can
*only* fulfill.
# `Guarantee<T>`
Since PromiseKit 5, we have provided `Guarantee` as a supplementary class to
`Promise`. We do this to complement Swifts strong error handling system.
Guarantees *never* fail, so they cannot be rejected. A good example is `after`:
```
firstly {
after(seconds: 0.1)
}.done {
// there is no way to add a `catch` because after cannot fail.
}
```
Swift warns you if you dont terminate a regular `Promise` chain (i.e., not
a `Guarantee` chain). You're expected to silence this warning by supplying
either a `catch` or a `return`. (In the latter case, you will then have to `catch`
at the point where you receive that promise.)
Use `Guarantee`s wherever possible so that your code has error handling where
it's required and no error handling where it's not required.
In general, you should be able to use `Guarantee`s and `Promise`s interchangeably,
We have gone to great lengths to try and ensure this, so please open a ticket
if you find an issue.
---
If you are creating your own guarantees the syntax is simpler than that of promises;
```swift
func fetch() -> Promise<String> {
return Guarantee { seal in
fetch { result in
seal(result)
}
}
}
```
Which could be reduced to:
```swift
func fetch() -> Promise<String> {
return Guarantee(resolver: fetch)
}
```
# `map`, `compactMap`, etc.
`then` provides you with the result of the previous promise and requires you to return
another promise.
`map` provides you with the result of the previous promise and requires you to return
an object or value type.
`compactMap` provides you with the result of the previous promise and requires you
to return an `Optional`. If you return `nil`, the chain fails with
`PMKError.compactMap`.
> *Rationale*: Before PromiseKit 4, `then` handled all these cases, and it was
painful. We hoped the pain would disappear with new Swift versions. However,
it has become clear that the various pain points are here to stay. In fact, we
as library authors are expected to disambiguate at the naming level of our API.
Therefore, we have split the three main kinds of `then` into `then`, `map` and
`done`. After using these new functions, we realized this is much nicer in practice,
so we added `compactMap` as well (modeled on `Optional.compactMap`).
`compactMap` facilitates quick composition of promise chains. For example:
```swift
firstly {
URLSession.shared.dataTask(.promise, with: rq)
}.compactMap {
try JSONSerialization.jsonObject($0.data) as? [String]
}.done { arrayOfStrings in
//…
}.catch { error in
// Foundation.JSONError if JSON was badly formed
// PMKError.compactMap if JSON was of different type
}
```
> *Tip*: We also provide most of the functional methods you would expect for sequences,
e.g., `map`, `thenMap`, `compactMapValues`, `firstValue`, etc.
# `get`
We provide `get` as a `done` that returns the value fed to `get`.
```swift
firstly {
foo()
}.get { foo in
//…
}.done { foo in
// same foo!
}
```
# `tap`
We provide `tap` for debugging. It's the same as `get` but provides the
`Result<T>` of the `Promise` so you can inspect the value of the chain at this
point without causing any side effects:
```swift
firstly {
foo()
}.tap {
print($0)
}.done {
//…
}.catch {
//…
}
```
# Supplement
## `firstly`
Above we kept using `firstly`, but what is it? Well, it is just [syntactic sugar](https://en.wikipedia.org/wiki/Syntactic_sugar),
you dont need it, but it helps make your chains more readable. Instead of:
We've used `firstly` several times on this page, but what is it, really? In fact,
it is just [syntactic sugar](https://en.wikipedia.org/wiki/Syntactic_sugar).
You dont really need it, but it helps to make your chains more readable. Instead of:
```swift
firstly {
@ -293,24 +463,31 @@ login().then { creds in
}
```
Here is a key understanding: `login()` returns `Promise` and all `Promise` have a `then` function.
Here is a key understanding: `login()` returns a `Promise`, and all `Promise`s have a `then` function. `firstly` returns a `Promise`, and `then` returns a `Promise`, too! But dont worry too much about these details. Learn the *patterns* to start with. Then, when you are ready to advance, learn the underlying architecture.
Thus, indeed, `firstly` returns `Promise` and, indeed, `then` returns `Promise`, but dont worry too much about these details. To start
with learn the *patterns*, then, when you are ready to advance, learn the underlying architecture.
## `when` variants
## `when` Variants
`when` is one of PromiseKits more useful functions, and thus we offer several variants.
`when` is one of PromiseKits more useful functions, and so we offer several variants.
* The default `when` and the one you should typically use is `when(fulfilled:)` this variant waits on all its promises, but if any fail, it fails, and thus the chain *rejects*. It is important to note that all promises in the when *continue*. Promises have *no* control over the tasks they represent, promises are merely are wrapper around tasks.
* We provide `when(resolved:)`. This variant waits even if one or more of its promises fails, consequently the result of this promise is an array of `Result<T>`, and consequently this variant requires that all its promises have the same generic type. See our advanced patterns guide for work arounds for this limitation.
* We provide `race`, this variant allows you to *race* several promises, whichever finishes first is the result. See our advanced patterns guide for typical usage.
* The default `when`, and the one you should typically use, is `when(fulfilled:)`. This variant
waits on all its component promises, but if any fail, `when` fails too, and thus the chain *rejects*.
It's important to note that all promises in the `when` *continue*. Promises have *no* control over
the tasks they represent. Promises are just wrappers around tasks.
* `when(resolved:)` waits even if one or more of its component promises fails. The value produced
by this variant of `when` is an array of `Result<T>`. Consequently, this variant requires all its
component promises to have the same generic type. See our advanced patterns guide for work-arounds
for this limitation.
* The `race` variant lets you *race* several promises. Whichever finishes first is the result. See the
advanced patterns guide for typical usage.
## Swift Closure Inference
Swift will automatically infer returns and return types for one line closures,
thus these are the same:
Swift automatically infers returns and return types for one-line closures.
The following two forms are the same:
```swift
foo.then {
@ -326,23 +503,33 @@ foo.then { baz -> Promise<String> in
Our documentation often omits the `return` for clarity.
However this is a blessing and a curse, as the Swift compiler often will fail
to infer return types. See our [Troubleshooting Guide](Troubleshooting.md) if
However, this shorthand is both a blessing and a curse. You may find that the Swift compiler
often fails to infer return types properly. See our [Troubleshooting Guide](Troubleshooting.md) if
you require further assistance.
> By adding `done` to PromiseKit 5, we have managed to avoid many of these common
pain points in using PromiseKit and Swift.
# Further Reading
The above is the 90% you will use. We **strongly** suggest reading the
[sources], though strictly we are only suggesting you read the function
documentation, don't worry about the internals. There are numerous little
functions that may be useful to you and the documentation for all of the above
The above information is the 90% you will use. We **strongly** suggest reading the
[API Reference].
There are numerous little
functions that may be useful to you, and the documentation for everything outlined above
is more thorough at the source.
In Xcode dont forget to `⌥` click on PromiseKit functions to get at this
documentation while you are developing.
In Xcode, dont forget to option-click on PromiseKit functions to access this
documentation while you're coding.
Otherwise return to our [contents page](/Documentation).
Here are some recent articles that document PromiseKit 5+:
* [Using Promises - Agostini.tech](https://agostini.tech/2018/10/08/using-promisekit)
Careful with general online references, many of them refer to PMK < 5 which has a subtly
different API (sorry about that, but Swift has changed a lot over the years and thus
we had to too).
[sources]: https://github.com/mxcl/PromiseKit/tree/master/Sources
[API Reference]: https://mxcl.dev/PromiseKit/reference/v6/Classes/Promise.html

View File

@ -1,36 +1,74 @@
# Xcode 8 or 9 / Swift 3 or 4
# Xcode 8.3, 9.x or 10.x / Swift 3 or 4
We recommend CocoaPods.
We recommend Carthage over CocoaPods, but both installation methods are supported.
## CocoaPods
```ruby
use_frameworks!
pod "PromiseKit", "~> 4.4"
```
Since CocoaPods 1.0 you will (probably) need to add the `pod` line to a `target`,
eg:
```ruby
use_frameworks!
target "MyTarget" do
pod "PromiseKit", "~> 4.4"
target "Change Me!" do
pod "PromiseKit", "~> 6.8"
end
```
If the generated Xcode project gives you a warning that PromiseKit needs to be upgraded to
Swift 4.0 or Swift 4.2, then add the following:
```ruby
post_install do |installer|
installer.pods_project.targets.each do |target|
if target.name == 'PromiseKit'
target.build_configurations.each do |config|
config.build_settings['SWIFT_VERSION'] = '4.2'
end
end
end
end
```
Adjust the value for `SWIFT_VERSION` as needed.
CocoaPods are aware of this [issue](https://github.com/CocoaPods/CocoaPods/issues/7134).
## Carthage
```ruby
github "mxcl/PromiseKit" ~> 4.4
github "mxcl/PromiseKit" ~> 6.8
```
> Please note, since PromiseKit 6.8.1 our Carthage support has transitioned to
Swift 4 and above only. Strictly we *do* still support Swift 3.1 for Carthage,
and if you like you could edit the PromiseKit `project.pbxproj` file during
`carthage bootstrap` to make this possible. This change was involuntary and due
to Xcode 10.2 dropping support for Swift 3.
## Accio
Add the following to your Package.swift:
```swift
.package(url: "https://github.com/mxcl/PromiseKit.git", .upToNextMajor(from: "6.8.4")),
```
Next, add `PromiseKit` to your App targets dependencies like so:
```swift
.target(
name: "App",
dependencies: [
"PromiseKit",
]
),
```
Then run `accio update`.
## SwiftPM
```ruby
```swift
package.dependencies.append(
.Package(url: "https://github.com/mxcl/PromiseKit", majorVersion: 4)
.package(url: "https://github.com/mxcl/PromiseKit", from: "6.8.0")
)
```
@ -44,15 +82,15 @@ You can just drop `PromiseKit.xcodeproj` into your project and then add
PromiseKit contains Swift, so there have been rev-lock issues with Xcode:
| PromiseKit | Swift | Xcode | CI Status | Release Notes |
| ---------- | -------- | -------- | ------------ | ----------------- |
| 5 | 3.x, 4.x | 8.x, 9.x | ![ci-master] | In beta |
| 4 | 3.x, 4.x | 8.x, 9.x | ![ci-master] | [2016/09][news-4] |
| 3 | 2.x | 7.x, 8.0 | ![ci-swift2] | [2015/10][news-3] |
| 2 | 1.x | 7.x | Unsupported | [2015/10][news-3] |
| 1† | *N/A* | * | ![ci-legacy] | |
| PromiseKit | Swift | Xcode | CI Status | Release Notes |
| ---------- | ----------------------- | -------- | ------------ | ----------------- |
| 6 | 3.1, 3.2, 3.3, 4.x, 5.x | 8.3, 9.x, 10.x | ![ci-master] | [2018/02][news-6] |
| 5 | 3.1, 3.2, 3.3, 4.x | 8.3, 9.x, 10.1 | *Deprecated* | *n/a* |
| 4 | 3.0, 3.1, 3.2, 3.3, 4.x | 8.x, 9.x, 10.1 | ![ci-master] | [2016/09][news-4] |
| 3 | 2.x | 7.x, 8.0 | ![ci-swift2] | [2015/10][news-3] |
| 2 | 1.x | 7.x | *Deprecated* | [2015/10][news-3] |
| 1† | *N/A* | * | ![ci-legacy] | |
† PromiseKit 1 is pure Objective-C and thus can be used with any Xcode, it is
also your only choice if you need to support iOS 7 or below.
@ -68,7 +106,7 @@ We also maintain a series of branches to aid migration for PromiseKit 2:
| 7.1 | 2.1 | 2 | [swift-2.0-minimal-changes] | ![ci-20] |
| 7.0 | 2.0 | 2 | [swift-2.0-minimal-changes] | ![ci-20] |
We do **not** usually backport fixes to these branches, but pull-requests are welcome.
We do **not** usually backport fixes to these branches, but pull requests are welcome.
## Xcode 8 / Swift 2.3 or Xcode 7
@ -83,11 +121,6 @@ github "mxcl/PromiseKit" ~> 3.5
```
# PromiseKit 5
[PromiseKit 5 is experimental and under active development](https://github.com/mxcl/PromiseKit/tree/experimental-5.x).
[travis]: https://travis-ci.org/mxcl/PromiseKit
[ci-master]: https://travis-ci.org/mxcl/PromiseKit.svg?branch=master
[ci-legacy]: https://travis-ci.org/mxcl/PromiseKit.svg?branch=legacy-1.x
@ -95,9 +128,10 @@ github "mxcl/PromiseKit" ~> 3.5
[ci-23]: https://travis-ci.org/mxcl/PromiseKit.svg?branch=swift-2.3-minimal-changes
[ci-22]: https://travis-ci.org/mxcl/PromiseKit.svg?branch=swift-2.2-minimal-changes
[ci-20]: https://travis-ci.org/mxcl/PromiseKit.svg?branch=swift-2.0-minimal-changes
[news-2]: http://promisekit.org/news/2015/05/PromiseKit-2.0-Released/
[news-3]: https://github.com/mxcl/PromiseKit/blob/master/CHANGELOG.markdown#300-oct-1st-2015
[news-4]: http://promisekit.org/news/2016/09/PromiseKit-4.0-Released/
[news-2]: http://mxcl.dev/PromiseKit/news/2015/05/PromiseKit-2.0-Released/
[news-3]: https://github.com/mxcl/PromiseKit/blob/212f31f41864d1e3ec54f5dd529bd8e1e5697024/CHANGELOG.markdown#300-oct-1st-2015
[news-4]: http://mxcl.dev/PromiseKit/news/2016/09/PromiseKit-4.0-Released/
[news-6]: http://mxcl.dev/PromiseKit/news/2018/02/PromiseKit-6.0-Released/
[swift-2.3-minimal-changes]: https://github.com/mxcl/PromiseKit/tree/swift-2.3-minimal-changes
[swift-2.2-minimal-changes]: https://github.com/mxcl/PromiseKit/tree/swift-2.2-minimal-changes
[swift-2.0-minimal-changes]: https://github.com/mxcl/PromiseKit/tree/swift-2.0-minimal-changes
@ -105,25 +139,25 @@ github "mxcl/PromiseKit" ~> 3.5
# Using Git Submodules for PromiseKits Extensions
> Please note, this is a more advanced technique
> *Note*: This is a more advanced technique.
If you use CocoaPods and a few PromiseKit extensions then importing PromiseKit
causes that module to import all the extension frameworks. Thus if you have an
app and a few app-extensions (eg. iOS app, iOS watch extension, iOS Today
If you use CocoaPods and a few PromiseKit extensions, then importing PromiseKit
causes that module to import all the extension frameworks. Thus, if you have an
app and a few app extensions (e.g., iOS app, iOS watch extension, iOS Today
extension) then all your final products that use PromiseKit will have forced
dependencies on all the Apple frameworks that PromiseKit provides extensions
for.
This isnt that bad, but every framework that loads is overhead and startup
time.
This isnt that bad, but every framework that loads entails overhead and
lengthens startup time.
Its better and worse with Carthage since we build individual micro-frameworks
for each PromiseKit-extension, so at least all your final products only link
against the Apple frameworks that they actually need. However, Apple have
advised that apps only link against “about 12” frameworks for performance
reasons, so for Carthage, we are worse off for this metric.
Its both better and worse with Carthage. We build individual micro-frameworks
for each PromiseKit extension, so your final products link
against only the Apple frameworks that they actually need. However, Apple has
advised that apps link only against “about 12” frameworks for performance
reasons. So with Carthage, we are worse off on this metric.
The solution is to instead only import CorePromise:
The solution is to instead import only CorePromise:
```ruby
# CocoaPods
@ -143,7 +177,7 @@ git submodule add https://github.com/PromiseKit/UIKit Submodules/PMKUIKit
Then in Xcode you can add these sources to your targets on a per-target basis.
Then when you `pod update`, ensure you also update your submodules:
Then when you `pod update`, ensure that you also update your submodules:
pod update && git submodule update --recursive --remote
@ -151,9 +185,13 @@ Then when you `pod update`, ensure you also update your submodules:
# Release History
## [6.0](https://github.com/mxcl/PromiseKit/releases/tag/6.0.0) Feb 13th, 2018
* [PromiseKit 6 announcement post][news-6].
## [4.0](https://github.com/mxcl/PromiseKit/releases/tag/4.0.0)
* [PromiseKit 4 announcement post](http://promisekit.org/news/2016/09/PromiseKit-4.0-Released/).
* [PromiseKit 4 announcement post][news-4].
## [3.0](https://github.com/mxcl/PromiseKit/releases/tag/3.0.0) Oct 1st, 2015
@ -173,7 +211,7 @@ If you still are using Xcode 6 and Swift 1.2 then use PromiseKit 2.
## [2.0](https://github.com/mxcl/PromiseKit/releases/tag/2.0.0) May 14th, 2015
[PromiseKit 2 announcement post](http://promisekit.org/news/2015/05/PromiseKit-2.0-Released/).
[PromiseKit 2 announcement post](http://mxcl.dev/PromiseKit/news/2015/05/PromiseKit-2.0-Released/).
## [1.5](https://github.com/mxcl/PromiseKit/releases/tag/1.5.0)

View File

@ -5,19 +5,18 @@ PromiseKit has two promise classes:
* `Promise<T>` (Swift)
* `AnyPromise` (Objective-C)
Each is designed to be an approproate promise implementation for the strong
points of its language:
Each is designed to be an appropriate promise implementation for the strong points of its language:
* `Promise<T>` is strict, defined and precise.
* `AnyPromise` is loose and dynamic.
Unlike most libraries we have extensive bridging support, you can use PromiseKit
Unlike most libraries, we have extensive bridging support, you can use PromiseKit
in mixed projects with mixed language targets and mixed language libraries.
# Using PromiseKit with Objective-C
`AnyPromise` is our promise class for Objective-C. It behaves almost identically to `Promise<T>` (our Swift promise class).
`AnyPromise` is our promise class for Objective-C. It behaves almost identically to `Promise<T>`, our Swift promise class.
```objc
myPromise.then(^(NSString *bar){
@ -51,9 +50,7 @@ myPromise.then(^{
});
```
---
One important feature is the syntactic flexability of your handlers:
One important feature is the syntactic flexibility of your handlers:
```objc
myPromise.then(^{
@ -79,7 +76,7 @@ We do runtime inspection of the block you pass to achieve this magic.
Another important distinction is that the equivalent function to Swifts `recover` is combined with `AnyPromise`s `catch`. This is typical to other “dynamic” promise implementations and thus achieves our goal that `AnyPromise` is loose and dynamic while `Promise<T>` is strict and specific.
A sometimes unexpected consequence of this is that returning nothing from a `catch` *resolves* the returned promise:
A sometimes unexpected consequence of this fact is that returning nothing from a `catch` *resolves* the returned promise:
```objc
myPromise.catch(^{
@ -91,7 +88,7 @@ myPromise.catch(^{
---
Another important distinction is that the `value` property returns even if the promise is rejected, in that case it returns the `NSError` object with which the promise was rejected.
Another important distinction is that the `value` property returns even if the promise is rejected; in that case, it returns the `NSError` object with which the promise was rejected.
# Bridging Between Objective-C & Swift
@ -104,9 +101,8 @@ Lets say you have:
@end
```
Ensure this interface is included in your bridging header.
You can now use this in your Swift code:
Ensure that this interface is included in your bridging header. You can now use the
following pattern in your Swift code:
```swift
let foo = Foo()
@ -129,9 +125,9 @@ Lets say you have:
@objc class Bar: NSObject { /*…*/ }
```
Ensure that your project is generating a `…-Swift.h` header so your Objective-C can see your Swift code.
Ensure that your project is generating a `…-Swift.h` header so that Objective-C can see your Swift code.
If you built this and opened the `…-Swift.h` header you would only see this:
If you built this project and opened the `…-Swift.h` header, you would only see this:
```objc
@interface Foo
@ -141,7 +137,7 @@ If you built this and opened the `…-Swift.h` header you would only see this:
@end
```
This is because Objective-C cannot import Swift objects that are generic. Thus we need to write some stubs:
That's because Objective-C cannot import Swift objects that are generic. So we need to write some stubs:
```swift
@objc class Foo: NSObject {
@ -154,7 +150,7 @@ This is because Objective-C cannot import Swift objects that are generic. Thus w
}
```
If we built this and opened our generated header we would now see:
If we built this and opened our generated header, we would now see:
```objc
@interface Foo
@ -168,5 +164,56 @@ If we built this and opened our generated header we would now see:
Perfect.
Note that AnyPromise can only bridge objects that conform to `AnyObject` or derive `NSObject`. This is a limitation of Objective-C.
Note that AnyPromise can only bridge objects that conform to `AnyObject` or derive from `NSObject`. This is a limitation of Objective-C.
# Using ObjC AnyPromises from Swift
Simply use them, the type of your handler parameter is `Any`:
```objective-c
- (AnyPromise *)fetchThings {
return [AnyPromise promiseWithValue:@[@"a", @"b", @"c"]];
}
```
Since ObjC is not type-safe and Swift is, you will (probably) need to cast the `Any` to whatever it is you actually are feeding:
```swift
Foo.fetchThings().done { any in
let bar = any as! [String]
}
```
## :warning: Caution:
ARC in Objective-C, unlike in Objective-C++, is not exception-safe by default.
So, throwing an error will result in keeping a strong reference to the closure
that contains the throw statement.
This pattern will consequently result in memory leaks if you're not careful.
> *Note:* Only having a strong reference to the closure would result in memory leaks.
> In our case, PromiseKit automatically keeps a strong reference to the closure until it's released.
__Workarounds:__
1. Return a Promise with value NSError\
Instead of throwing a normal error, you can return a Promise with value NSError instead.
```objc
myPromise.then(^{
return [AnyPromise promiseWithValue:[NSError myCustomError]];
}).catch(^(NSError *error){
if ([error isEqual:[NSError myCustomError]]) {
// In case, same error as the one we thrown
return;
}
//…
});
```
2. Enable ARC for exceptions in Objective-C (not recommended)\
You can add this ```-fobjc-arc-exceptions to your``` to your compiler flags to enable ARC for exceptions.
This is not recommended unless you've read the Apple documentation and are comfortable with the caveats.
For more details on ARC and exceptions:
https://clang.llvm.org/docs/AutomaticReferenceCounting.html#exceptions

View File

@ -1,6 +1,6 @@
# Contents
* [README](/README.md)
* [README](../README.md)
* Handbook
* [Getting Started](GettingStarted.md)
* [Promises: Common Patterns](CommonPatterns.md)
@ -10,3 +10,5 @@
* [Objective-C Guide](ObjectiveC.md)
* [Troubleshooting](Troubleshooting.md)
* [Appendix](Appendix.md)
* [Examples](Examples)
* [API Reference](https://mxcl.dev/PromiseKit/reference/v6/Classes/Promise.html)

View File

@ -1,109 +1,241 @@
# Troubleshooting
Sadly, Swift often gives misleading diagnostic messages for compile errors with
PromiseKit.
## Compilation errors
## Compile Errors
99% of compilation issues involving PromiseKit can be addressed or diagnosed by one of the fixes below.
### Cannot convert return expression of type … to return type AnyPromise
Specify the return type for the closure. DONT return `AnyPromise` (unless you
wanted to).
### Missing return in a closure expected to return AnyPromise
Specify the return type for the closure. DONT return `AnyPromise` (unless you
wanted to).
### Ambiguous *bar*
Specify the return type for the closure.
### Confusing Errors
The best one I saw lately was *`UIImage` is not convertible to `UIImage?`*
Swifts diagnostic reporting will be flat-out misleading and wrong inside
complicated closures, and with PromiseKit we have a lot of those.
When specifying the `return` types doesnt help try the advice in the next
section:
## Pain Free Swift Promises
The best way is to appease Swift is to decompose your chains into separated
local functions. For example:
### Check your handler
```swift
func foo() -> Promise<Int>
func step1() -> Promise<String> {
// many
// lines
// of
// code
}
func step2() -> Promise<Int> {
// many
// lines
// of
// code
}
return firstly {
step1()
}.then {
step2()
}
return firstly {
URLSession.shared.dataTask(.promise, with: url)
}.compactMap {
JSONSerialization.jsonObject(with: $0.data) as? [String: Any]
}.then { dict in
User(dict: dict)
}
```
Otherwise try to always have your chain return to something that specifies the
promise type. This is easy when you are writing functions that return promises,
and in general this is what you should aim for anyway: because it makes your
promises more composable.
Swift (unhelpfully) says:
When you return promises from functions that specify the return type Swift can
infer the types properly and it makes writing chains that much easier.
> Cannot convert value of type '([String : Any]) -> User' to expected argument type '([String : Any]) -> _'
## Neither `catch` or `then` are called in my chain
Check something didnt throw a `CancellableError`. Easiest way is to amend your
`catch`:
Whats the real problem? `then` *must* return a `Promise`, and you're trying to return something else. What you really want is `map`:
```swift
foo.then {
return firstly {
URLSession.shared.dataTask(.promise, with: url)
}.compactMap {
JSONSerialization.jsonObject(with: $0.data) as? [String: Any]
}.map { dict in
User(dict: dict)
}
```
### Specify closure parameters **and** return type
For example:
```swift
return firstly {
foo()
}.then { user in
//…
}.catch(policy: .allErrorsIncludingCancellation) {
// cancelled errors are handled *as well*
return bar()
}
```
## `Pending Promise Deallocated!`
This code may compile if you specify the type of `user`:
If you see this warning you have a path in your `Promise` initializer where
neither `fulfill` or `reject` are called:
```swift
Promise<String> { fulfill, reject in
return firstly {
foo()
}.then { (user: User) in
//…
return bar()
}
```
If it still doesn't compile, perhaps you need to specify the return type, too:
```swift
return firstly {
foo()
}.then { (user: User) -> Promise<Bar> in
//…
return bar()
}
```
We have made great effort to reduce the need for explicit typing in PromiseKit 6,
but as with all Swift functions that return a generic type (e.g., `Array.map`),
you may need to explicitly tell Swift what a closure returns if the closure's body is
longer than one line.
> *Tip*: Sometimes you can force a one-liner by using semicolons.
### Acknowledge all incoming closure parameters
Swift does not permit you to silently ignore a closure's parameters. For example, this code:
```swift
func _() -> Promise<Void> {
return firstly {
proc.launch(.promise) // proc: Foundation.Process
}.then {
when(fulfilled: p1, p2) // both p1 & p2 are `Promise<Void>`
}
}
```
Fails to compile with the error:
Cannot invoke 'then' with an argument list of type '(() -> _)
What's the problem? Well, `Process.launch(.promise)` returns
`Promise<(String, String)>`, and we are ignoring this value in our `then` closure.
If wed referenced `$0` or named the parameter, Swift would have been satisfied.
Assuming that we really do want to ignore the argument, the fix is to explicitly
acknowledge its existence by assigning it the name "_". That's Swift-ese for "I
know there's a value here, but I'm ignoring it."
```swift
func _() -> Promise<Void> {
return firstly {
proc.launch(.promise)
}.then { _ in
when(fulfilled: p1, p2)
}
}
```
In this situation, you won't always receive an error message that's as clear as the
one shown above. Sometimes, a missing closure parameter sends Swift scurrying off
into type inference limbo. When it finally concludes that there's no way for it to make
all the inferred types work together, it may end up assigning blame to some other
closure entirely and giving you an error message that makes no sense at all.
When faced with this kind of enigmatic complaint, a good rule of thumb is to
double-check your argument and return types carefully. If everything looks OK,
temporarily add explicit type information as shown above, just to rule
out misinference as a possible cause.
### Try moving code to a temporary inline function
Try taking the code out of a closure and putting it in a standalone function. Now Swift
will give you the *real* error message. For example:
```swift
func doStuff() {
firstly {
foo()
}.then {
let bar = bar()
let baz = baz()
when(fulfilled: bar, baz)
}
}
```
Becomes:
```swift
func doStuff() {
func fluff() -> Promise<…> {
let bar = bar()
let baz = baz()
when(fulfilled: bar, baz)
}
firstly {
foo()
}.then {
fluff()
}
}
```
An *inline* function like this is all you need. Here, the problem is that you
forgot to mark the last line of the closure with an explicit `return`. It's required
here because the closure is longer than one line.
## You copied code off the Internet that doesnt work
Swift has changed a lot over the years and so PromiseKit has had to change to keep
up. The code you copied is probably for an older PromiseKit. *Read the definitions of the
functions.* It's easy to do this in Xcode by option-clicking or command-clicking function names.
All PromiseKit functions are documented and provide examples.
## "Context type for closure argument expects 1 argument, which cannot be implicitly ignored"
You have a `then`; you want a `done`.
## "Missing argument for parameter #1 in call"
This is part of Swift 4s “tuplegate”.
You must specify your `Void` parameter:
```swift
seal.fulfill(())
```
Yes: we hope they revert this change in Swift 5 too.
## "Ambiguous reference to 'firstly(execute:)'"
Remove the firstly, e.g.:
```swift
firstly {
foo()
}.then {
//…
}
```
becomes:
```swift
foo().then {
//…
}
```
Rebuild and Swift should now tell you the *real* error.
## Other issues
### `Pending Promise Deallocated!`
If you see this warning, you have a path in your `Promise` initializer that allows
the promise to escape without being sealed:
```swift
Promise<String> { seal in
task { value, error in
if let value = value as? String {
fulfill(value)
seal.fulfill(value)
} else if let error = error {
reject(error)
seal.reject(error)
}
}
}
```
There are two missing paths here and if either occur the promise will soon be
deallocated without resolving. This will show itself as a bug in your app,
probably the awful: infinite spinner.
There are two missing paths here, and if either occurs, the promise will soon be
deallocated without resolving. This will manifest itself as a bug in your app,
probably the awful infinite spinner.
So lets be thorough:
```swift
Promise<String> { fulfill, reject in
Promise<String> { seal in
task { value, error in
if let value = value as? String {
fulfill(value)
@ -119,8 +251,49 @@ Promise<String> { fulfill, reject in
}
```
If this seems tedious it shouldnt. You would have to be this thorough without promises too, the difference is without promises you wouldnt get a warning in the console letting you know your mistake!
If this seems tedious, it shouldnt. You would have to be this thorough without promises, too.
The difference is that without promises, you wouldnt get a warning in the console notifying
you of your mistake!
## Slow Compilation / Compiler Cannot Solve in Reasonable Time
### Slow compilation / compiler cannot solve in reasonable time
Add return types to your closures.
### My promise never resolves
There are several potential causes:
#### 1. Check to be sure that your asynchronous task even *starts*
Youd be surprised how often this is the cause.
For example, if you are using `URLSession` without our extension (but
dont do that; *use* our extension! we know all the pitfalls), did you forget
to call `resume` on the task? If so, the task never actually starts, and so of
course it never finishes, either.
#### 2. Check that all paths in your custom Promise initializers are handled
See “Pending Promise Deallocated” above. Usually you will see this warning if
you are not handling a path, but that requires your promise deallocate, so you
may not see this warning yet you are still not handling all paths.
Unhandled paths mean the promise will not resolve.
#### 3. Ensure the queue your promise handler runs upon is not blocked
If the thread is blocked the handlers cannot execute. Commonly you can see this
if you are using our `wait()` function. Please read the documentation for `wait()`
for suggestions and caveats.
### `Result of call to 'done(on:_:)' is unused`, `Result of call to 'then(on:_:)' is unused`
PromiseKit deliberately avoids the `@discardableResult` annotation because the
unused result warning is a hint that you have not handled the error in your
chain. So do one of these:
1. Add a `catch`
2. `return` the promise (thus punting the error handling to the caller)
3. Use `cauterize()` to silence the warning.
Obviously, do 1 or 2 in preference to 3.

@ -1 +1 @@
Subproject commit 8152cad728dc490f6984114fc70a6e1d169857f7
Subproject commit 911b257783efc0aa705481d556c8ecdfcdc055a9

@ -1 +1 @@
Subproject commit 4b78fe2b4b81d03ac6238957e481b4d5cd8dea0d
Subproject commit 6c01f5719ff08bbbf81d42b7e20c5188cab4a81d

@ -1 +1 @@
Subproject commit 74ce091905e224629ceea946408d90c0c2733eb4
Subproject commit c694fa0628a96951c6e2db113dc4eb6dd4e47ebc

@ -1 +1 @@
Subproject commit 33bd93f1986620b519defb1385dc235d88fe39b4
Subproject commit a41a97d3d3aa6efb575d0f42f3b8aeec5a756ead

@ -1 +1 @@
Subproject commit 4893354a5bb07e9a959c7e1a6e5cb91859a30657
Subproject commit e4317d5279db7ecfc5bc565e1dd88fcabc02c352

@ -1 +1 @@
Subproject commit 0fb8765d39fc96118d5cc3fe548c8fdecdfe4ca2
Subproject commit fbc9941219c9bbdfe6d664ca6b8d10b5e2f8fb70

@ -1 +1 @@
Subproject commit bd463ed6cd667b065b6ceb4c8723acbb6fe34aa6
Subproject commit 9586f3c06c4934e776c2b96c128f332a168c2c91

@ -1 +1 @@
Subproject commit 671405582e1bb9b1fbce3f69b727a179db2a1099
Subproject commit 8e0bc957996daad5c49f8809839f8699883ed364

@ -1 +1 @@
Subproject commit 2d9497395350fd0219ba7b62ac08b55f0d2c7cdb
Subproject commit c4ea4dba6e0516986b3cb1d93c86f6991ce46861

@ -1 +1 @@
Subproject commit 35da7f0b69965c5771c3c93a6e9cd081c75a55af
Subproject commit 1f90d67538047087428d82550976c2e43aba0961

@ -1 +1 @@
Subproject commit 06ba5746d8bdfed3dde17679ef20d37922de867f
Subproject commit 1a276e598dac59489ed904887e0740fa75e571e0

1
Extensions/HealthKit Submodule

@ -0,0 +1 @@
Subproject commit b185822cb1fcf5297347498b783b4e21b2e55282

1
Extensions/HomeKit Submodule

@ -0,0 +1 @@
Subproject commit 668abe78a52a23bd276c8924dba91592ac0ca45a

@ -1 +1 @@
Subproject commit 711243c75ffe39b74fb3d103efde70024a204fde
Subproject commit d1d4ebdd6ceac78d734e2ae27e8ab429906d6929

@ -1 +1 @@
Subproject commit 51297fc5c494d691e02ea9231cc17f52404cb885
Subproject commit 2a93ce737502e13a3eaedc444b9ecb5abb28ec79

@ -1 +1 @@
Subproject commit 111d2b4fedd8729c2bb9deabc04b03bb1f47dcdb
Subproject commit 5ec40a68f168255dcc339b9620e2dcf62079ae6b

@ -1 +1 @@
Subproject commit b5658cee071236ada50d6754d38243720697b672
Subproject commit 48f801454b01c69a1553873cb1d95e90ae2ec4cc

@ -1 +1 @@
Subproject commit 37e5beb954c5a931ddc3167678974b5269293096
Subproject commit b22f187b6b3f82f102aa309b256bf24570a73d1f

@ -1 +1 @@
Subproject commit 283b497b2450cc374bd0a0c06356eee838837838
Subproject commit 378912a47a206183b931d2008e0f396e9fc390e8

@ -1 +1 @@
Subproject commit a56c183961d1bfe368df40804544b968eb84e0f9
Subproject commit e26f6a55921ea671855093754686991b08ef97cc

@ -1 +1 @@
Subproject commit a3cb42da9d5adbb4eb412e8ff9d5794be2b2c252
Subproject commit 6b009f906fc489346a73759b5668b263a320c51c

@ -1 +1 @@
Subproject commit 76f13774360dab28e2cf9013f1914f4c574daae2
Subproject commit baf104ba29ff94d9d41e90e8ec557a56b87fecd4

@ -1 +1 @@
Subproject commit b6aa361ba81605cb080b84e71d4222918e6cc66e
Subproject commit b5b3fca958a7296f71313b6c172584bc91d6da8b

View File

@ -1,4 +1,4 @@
Copyright 2016, Max Howell; mxcl@me.com
Copyright 2016-present, Max Howell; mxcl@me.com
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the

View File

@ -1,22 +1,30 @@
// swift-tools-version:4.0
import PackageDescription
let package = Package(
name: "PromiseKit",
exclude: [
"Sources/AnyPromise.swift",
"Sources/Promise+AnyPromise.swift",
"Sources/AnyPromise.m",
"Sources/dispatch_promise.m",
"Sources/GlobalState.m",
"Sources/hang.m",
"Sources/NSMethodSignatureForBlock.m",
"Sources/join.m",
"Sources/PMKCallVariadicBlock.m",
"Sources/when.m",
"Sources/after.m",
"Sources/AnyPromise+Private.h",
"Sources/AnyPromise.h",
"Sources/PromiseKit.h",
"Tests"
]
)
let pkg = Package(name: "PromiseKit")
pkg.products = [
.library(name: "PromiseKit", targets: ["PromiseKit"]),
]
let pmk: Target = .target(name: "PromiseKit")
pmk.path = "Sources"
pmk.exclude = [
"AnyPromise.swift",
"AnyPromise.m",
"PMKCallVariadicBlock.m",
"dispatch_promise.m",
"join.m",
"when.m",
"NSMethodSignatureForBlock.m",
"after.m",
"hang.m",
"race.m",
"Deprecations.swift"
]
pkg.swiftLanguageVersions = [3, 4]
pkg.targets = [
pmk,
.testTarget(name: "A+", dependencies: ["PromiseKit"]),
.testTarget(name: "CorePromise", dependencies: ["PromiseKit"], path: "Tests/CorePromise"),
]

30
Package@swift-4.2.swift Normal file
View File

@ -0,0 +1,30 @@
// swift-tools-version:4.2
import PackageDescription
let pkg = Package(name: "PromiseKit")
pkg.products = [
.library(name: "PromiseKit", targets: ["PromiseKit"]),
]
let pmk: Target = .target(name: "PromiseKit")
pmk.path = "Sources"
pmk.exclude = [
"AnyPromise.swift",
"AnyPromise.m",
"PMKCallVariadicBlock.m",
"dispatch_promise.m",
"join.m",
"when.m",
"NSMethodSignatureForBlock.m",
"after.m",
"hang.m",
"race.m",
"Deprecations.swift"
]
pkg.swiftLanguageVersions = [.v3, .v4, .v4_2]
pkg.targets = [
pmk,
.testTarget(name: "A+", dependencies: ["PromiseKit"]),
.testTarget(name: "CorePromise", dependencies: ["PromiseKit"], path: "Tests/CorePromise"),
]

33
Package@swift-5.0.swift Normal file
View File

@ -0,0 +1,33 @@
// swift-tools-version:5.0
import PackageDescription
let pkg = Package(name: "PromiseKit")
pkg.platforms = [
.macOS(.v10_10), .iOS(.v8), .tvOS(.v9), .watchOS(.v2)
]
pkg.products = [
.library(name: "PromiseKit", targets: ["PromiseKit"]),
]
let pmk: Target = .target(name: "PromiseKit")
pmk.path = "Sources"
pmk.exclude = [
"AnyPromise.swift",
"AnyPromise.m",
"PMKCallVariadicBlock.m",
"dispatch_promise.m",
"join.m",
"when.m",
"NSMethodSignatureForBlock.m",
"after.m",
"hang.m",
"race.m",
"Deprecations.swift"
]
pkg.swiftLanguageVersions = [.v4, .v4_2, .v5]
pkg.targets = [
pmk,
.testTarget(name: "A+", dependencies: ["PromiseKit"]),
.testTarget(name: "CorePromise", dependencies: ["PromiseKit"], path: "Tests/CorePromise"),
]

View File

@ -7,23 +7,21 @@ import PromiseKit
func promise3() -> Promise<Int> {
return after(seconds: 1).then {
return 3
}
return after(.seconds(1)).map{ 3 }
}
firstly {
Promise(value: 1)
}.then { _ in
Promise.value(1)
}.map { _ in
2
}.then { _ in
promise3()
}.then {
}.done {
print($0) // => 3
}.always {
// always happens
}.catch { error in
// only happens for errors
}.finally {
PlaygroundPage.current.finishExecution()
}
PlaygroundPage.current.needsIndefiniteExecution = true

File diff suppressed because it is too large Load Diff

View File

@ -2,6 +2,6 @@
<Workspace
version = "1.0">
<FileRef
location = "self:PromiseKit.xcodeproj">
location = "self:">
</FileRef>
</Workspace>

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>

View File

@ -1,13 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "0900"
LastUpgradeVersion = "1200"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "NO">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForTesting = "NO"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
@ -26,8 +26,18 @@
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
language = ""
shouldUseLaunchSchemeArgsEnv = "YES">
shouldUseLaunchSchemeArgsEnv = "YES"
codeCoverageEnabled = "YES"
onlyGenerateCoverageForSpecifiedTargets = "YES">
<CodeCoverageTargets>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "63B0AC561D595E1B00FA21D9"
BuildableName = "PromiseKit.framework"
BlueprintName = "PromiseKit"
ReferencedContainer = "container:PromiseKit.xcodeproj">
</BuildableReference>
</CodeCoverageTargets>
<Testables>
<TestableReference
skipped = "NO">
@ -49,16 +59,6 @@
ReferencedContainer = "container:PromiseKit.xcodeproj">
</BuildableReference>
</TestableReference>
<TestableReference
skipped = "NO">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "631411121D5978D700E24B9E"
BuildableName = "PMKDispatchTests.xctest"
BlueprintName = "PMKDispatchTests"
ReferencedContainer = "container:PromiseKit.xcodeproj">
</BuildableReference>
</TestableReference>
<TestableReference
skipped = "NO">
<BuildableReference
@ -73,30 +73,28 @@
skipped = "NO">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "631411471D597A0400E24B9E"
BuildableName = "PMKExtensionTests.xctest"
BlueprintName = "PMKExtensionTests"
BlueprintIdentifier = "633027E0203CC0060037E136"
BuildableName = "PMKDeprecatedTests.xctest"
BlueprintName = "PMKDeprecatedTests"
ReferencedContainer = "container:PromiseKit.xcodeproj">
</BuildableReference>
</TestableReference>
<TestableReference
skipped = "NO">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "C0244E4E2047A6CB00ACB4AC"
BuildableName = "PMKJSA+Tests.xctest"
BlueprintName = "PMKJSA+Tests"
ReferencedContainer = "container:PromiseKit.xcodeproj">
</BuildableReference>
</TestableReference>
</Testables>
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "63B0AC561D595E1B00FA21D9"
BuildableName = "PromiseKit.framework"
BlueprintName = "PromiseKit"
ReferencedContainer = "container:PromiseKit.xcodeproj">
</BuildableReference>
</MacroExpansion>
<AdditionalOptions>
</AdditionalOptions>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
language = ""
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
@ -112,8 +110,6 @@
ReferencedContainer = "container:PromiseKit.xcodeproj">
</BuildableReference>
</MacroExpansion>
<AdditionalOptions>
</AdditionalOptions>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"

175
README.md
View File

@ -1,8 +1,6 @@
![PromiseKit](http://promisekit.org/public/img/logo-tight.png)
![PromiseKit](../gh-pages/public/img/logo-tight.png)
![badge-pod] ![badge-languages] ![badge-pms] ![badge-platforms] [![Build Status](https://travis-ci.org/mxcl/PromiseKit.svg?branch=master)](https://travis-ci.org/mxcl/PromiseKit)
[繁體中文](README.zh_Hant.md), [简体中文](README.zh_CN.md)
[![badge-pod][]][cocoapods] ![badge-languages][] ![badge-pms][] ![badge-platforms][] [![badge-travis][]][travis]
---
@ -13,12 +11,15 @@ more readable code. Your co-workers will thank you.
```swift
UIApplication.shared.isNetworkActivityIndicatorVisible = true
let fetchImage = URLSession.shared.dataTask(.promise, with: url).compactMap{ UIImage(data: $0.data) }
let fetchLocation = CLLocationManager.requestLocation().lastValue
firstly {
when(URLSession.dataTask(with: url).asImage(), CLLocationManager.promise())
}.then { image, location -> Void in
when(fulfilled: fetchImage, fetchLocation)
}.done { image, location in
self.imageView.image = image
self.label.text = "\(location)"
}.always {
}.ensure {
UIApplication.shared.isNetworkActivityIndicatorVisible = false
}.catch { error in
self.show(UIAlertController(for: error), sender: self)
@ -26,8 +27,20 @@ firstly {
```
PromiseKit is a thoughtful and complete implementation of promises for any
platform with a `swiftc`, it has *excellent* Objective-C bridging and
*delightful* specializations for iOS, macOS, tvOS and watchOS.
platform that has a `swiftc`. It has *excellent* Objective-C bridging and
*delightful* specializations for iOS, macOS, tvOS and watchOS. It is a top-100
pod used in many of the most popular apps in the world.
[![codecov](https://codecov.io/gh/mxcl/PromiseKit/branch/master/graph/badge.svg)](https://codecov.io/gh/mxcl/PromiseKit)
# PromiseKit 7 Alpha
We are testing PromiseKit 7 alpha, it is Swift 5 only. It is tagged and thus
importable in all package managers.
# PromiseKit 6
[Release notes and migration guide][PMK6].
# Quick Start
@ -37,14 +50,44 @@ In your [Podfile]:
use_frameworks!
target "Change Me!" do
pod "PromiseKit", "~> 4.4"
pod "PromiseKit", "~> 6.8"
end
```
PromiseKit 4 supports Xcode 8.1, 8.2, 8.3 and 9.0; Swift 3.0, 3.1, 3.2 and 4.0; iOS, macOS, tvOS, watchOS, Linux and Android; CocoaPods, Carthage and SwiftPM; ([CI Matrix](https://travis-ci.org/mxcl/PromiseKit)).
> The above gives an Xcode warning? See our [Installation Guide].
For Carthage, SwiftPM, etc., or for instructions when using older Swifts or
Xcodes see our [Installation Guide](Documentation/Installation.md).
PromiseKit 6, 5 and 4 support Xcode 8.3, 9.x and 10.0; Swift 3.1,
3.2, 3.3, 3.4, 4.0, 4.1, 4.2, 4.3 and 5.0 (development snapshots); iOS, macOS,
tvOS, watchOS, Linux and Android; CocoaPods, Carthage and SwiftPM;
([CI Matrix](https://travis-ci.org/mxcl/PromiseKit)).
For Carthage, SwiftPM, Accio, etc., or for instructions when using older Swifts or Xcodes, see our [Installation Guide]. We recommend
[Carthage](https://github.com/Carthage/Carthage) or
[Accio](https://github.com/JamitLabs/Accio).
# Professionally Supported PromiseKit is Now Available
TideLift gives software development teams a single source for purchasing
and maintaining their software, with professional grade assurances from
the experts who know it best, while seamlessly integrating with existing
tools.
[Get Professional Support for PromiseKit with TideLift](https://tidelift.com/subscription/pkg/cocoapods-promisekit?utm_source=cocoapods-promisekit&utm_medium=referral&utm_campaign=readme).
# PromiseKit is Thousands of Hours of Work
Hey there, Im Max Howell. Im a prolific producer of open source software and
probably you already use some of it (I created [`brew`]). I work full-time on
open source and its hard; currently *I earn less than minimum wage*. Please
help me continue my work, I appreciate it 🙏🏻
<a href="https://www.patreon.com/mxcl">
<img src="https://c5.patreon.com/external/logo/become_a_patron_button@2x.png" width="160">
</a>
[Other ways to say thanks](http://mxcl.dev/#donate).
[`brew`]: https://brew.sh
# Documentation
@ -55,80 +98,114 @@ Xcodes see our [Installation Guide](Documentation/Installation.md).
* Manual
* [Installation Guide](Documentation/Installation.md)
* [Objective-C Guide](Documentation/ObjectiveC.md)
* [Troubleshooting](Documentation/Troubleshooting.md) (eg. solutions to common compile errors)
* [Troubleshooting](Documentation/Troubleshooting.md) (e.g., solutions to common compile errors)
* [Appendix](Documentation/Appendix.md)
If you are looking for a functions documentation, then please note
[our sources](Sources/) are thoroughly documented.
* [API Reference](https://mxcl.dev/PromiseKit/reference/v6/Classes/Promise.html)
# Extensions
Promises are only as useful as the asynchronous tasks they represent, thus we
Promises are only as useful as the asynchronous tasks they represent. Thus, we
have converted (almost) all of Apples APIs to promises. The default CocoaPod
comes with promises for UIKit and Foundation, the rest can be installed by
specifying additional subspecs in your `Podfile`, eg:
provides Promises and the extensions for Foundation and UIKit. The other
extensions are available by specifying additional subspecs in your `Podfile`,
e.g.:
```ruby
pod "PromiseKit/MapKit" # MKDirections().promise().then { /*…*/ }
pod "PromiseKit/CoreLocation" # CLLocationManager.promise().then { /*…*/ }
pod "PromiseKit/MapKit" # MKDirections().calculate().then { /*…*/ }
pod "PromiseKit/CoreLocation" # CLLocationManager.requestLocation().then { /*…*/ }
```
All our extensions are separate repositories at the [PromiseKit organization].
## I don't want the extensions!
Then dont have them:
```ruby
pod "PromiseKit/CorePromise", "~> 6.8"
```
> *Note:* Carthage installations come with no extensions by default.
## Choose Your Networking Library
Promise chains are commonly started with networking, thus we offer multiple
options: [Alamofire], [OMGHTTPURLRQ] and of course (vanilla) `NSURLSession`:
Promise chains commonly start with a network operation. Thus, we offer
extensions for `URLSession`:
```swift
// pod 'PromiseKit/Alamofire'
// https://github.com/PromiseKit/Alamofire
Alamofire.request("http://example.com", method: .post).responseJSON().then { json in
// pod 'PromiseKit/Foundation' # https://github.com/PromiseKit/Foundation
firstly {
URLSession.shared.dataTask(.promise, with: try makeUrlRequest()).validate()
// ^^ we provide `.validate()` so that eg. 404s get converted to errors
}.map {
try JSONDecoder().decode(Foo.self, with: $0.data)
}.done { foo in
//…
}.catch { error in
//…
}
// pod 'PromiseKit/OMGHTTPURLRQ'
// https://github.com/PromiseKit/OMGHTTPURLRQ
URLSession.POST("http://example.com").asDictionary().then { json in
//…
}.catch { error in
//…
func makeUrlRequest() throws -> URLRequest {
var rq = URLRequest(url: url)
rq.httpMethod = "POST"
rq.addValue("application/json", forHTTPHeaderField: "Content-Type")
rq.addValue("application/json", forHTTPHeaderField: "Accept")
rq.httpBody = try JSONEncoder().encode(obj)
return rq
}
```
// pod 'PromiseKit/Foundation'
// https://github.com/PromiseKit/Foundation
URLSession.shared.dataTask(url).asDictionary().then { json in
// …
And [Alamofire]:
```swift
// pod 'PromiseKit/Alamofire' # https://github.com/PromiseKit/Alamofire-
firstly {
Alamofire
.request("http://example.com", method: .post, parameters: params)
.responseDecodable(Foo.self)
}.done { foo in
//…
}.catch { error in
//…
}
```
Nobody ever got fired for using Alamofire, but at the end of the day, its
just a small wrapper around `NSURLSession`. OMGHTTPURLRQ supplements
`NSURLRequest` to make generating REST style requests easier, and the PromiseKit
extensions extend `NSURLSession` to make OMG usage more convenient. But since a
while now most servers accept JSON, so writing a simple API class that uses
vanilla `NSURLSession` and our promises is not hard, and gives you the most
control with the fewest black-boxes.
Nowadays, considering that:
The choice is yours.
* We almost always POST JSON
* We now have `JSONDecoder`
* PromiseKit now has `map` and other functional primitives
* PromiseKit (like Alamofire, but not raw-`URLSession`) also defaults to having
callbacks go to the main thread
We recommend vanilla `URLSession`. It uses fewer black boxes and sticks closer to the metal. Alamofire was essential until the three bullet points above
became true, but nowadays it isnt really necessary.
# Support
Ask your question at our [Gitter chat channel] or on [our bug tracker].
Please check our [Troubleshooting Guide](Documentation/Troubleshooting.md), and
if after that you still have a question, ask at our [Gitter chat channel] or on [our bug tracker].
## Security & Vulnerability Reporting or Disclosure
https://tidelift.com/security
[badge-pod]: https://img.shields.io/cocoapods/v/PromiseKit.svg?label=version
[badge-pms]: https://img.shields.io/badge/supports-CocoaPods%20%7C%20Carthage%20%7C%20SwiftPM-green.svg
[badge-pms]: https://img.shields.io/badge/supports-CocoaPods%20%7C%20Carthage%20%7C%20Accio%20%7C%20SwiftPM-green.svg
[badge-languages]: https://img.shields.io/badge/languages-Swift%20%7C%20ObjC-orange.svg
[badge-platforms]: https://img.shields.io/badge/platforms-macOS%20%7C%20iOS%20%7C%20watchOS%20%7C%20tvOS%20%7C%20Linux-lightgrey.svg
[badge-mit]: https://img.shields.io/badge/license-MIT-blue.svg
[OMGHTTPURLRQ]: https://github.com/mxcl/OMGHTTPURLRQ
[Alamofire]: http://alamofire.org
[OMGHTTPURLRQ]: https://github.com/PromiseKit/OMGHTTPURLRQ
[Alamofire]: http://github.com/PromiseKit/Alamofire-
[PromiseKit organization]: https://github.com/PromiseKit
[Gitter chat channel]: https://gitter.im/mxcl/PromiseKit
[our bug tracker]: https://github.com/mxcl/PromiseKit/issues/new
[Podfile]: https://guides.cocoapods.org/syntax/podfile.html
[PMK6]: http://mxcl.dev/PromiseKit/news/2018/02/PromiseKit-6.0-Released/
[Installation Guide]: Documentation/Installation.md
[badge-travis]: https://travis-ci.org/mxcl/PromiseKit.svg?branch=master
[travis]: https://travis-ci.org/mxcl/PromiseKit
[cocoapods]: https://cocoapods.org/pods/PromiseKit

View File

@ -1,238 +0,0 @@
![PromiseKit](http://promisekit.org/public/img/logo-tight.png)
![badge-pod] ![badge-languages] ![badge-pms] ![badge-platforms] ![badge-mit]
[English](README.markdown)
---
现代编程语言都很好的支持了异步编程,因此在 swift 编程中,拥有功能强大且轻量级的异步编程工具的需求变得很强烈。
```swift
UIApplication.shared.isNetworkActivityIndicatorVisible = true
firstly {
when(URLSession.dataTask(with: url).asImage(), CLLocationManager.promise())
}.then { image, location -> Void in
self.imageView.image = image;
self.label.text = "\(location)"
}.always {
UIApplication.shared.isNetworkActivityIndicatorVisible = false
}.catch { error in
UIAlertView(/*…*/).show()
}
```
PromiseKit 是一款 swift 编写的支持 iOSmacOStvOSwatchOS 等多平台的轻量级异步编程库,同时 PromiseKit 完美的支持了 Objective-C 桥接。
# 快速预览
我们推荐您使用 [CocoaPods] 或者 [Carthage] 来集成 PromiseKit您也可以通过把 `PromiseKit.xcodeproj` 拖拽到项目中并导入 `PromiseKit.framework` 来手动集成。
## Xcode 8 / Swift 3
```ruby
# CocoaPods >= 1.1.0-rc.2
swift_version = "3.0"
pod "PromiseKit", "~> 4.0"
# Carthage
github "mxcl/PromiseKit" ~> 4.0
# SwiftPM
let package = Package(
dependencies: [
.Package(url: "https://github.com/mxcl/PromiseKit", majorVersion: 4)
]
)
```
## Xcode 8 / Swift 2.3 or Xcode 7
```ruby
# CocoaPods
swift_version = "2.3"
pod "PromiseKit", "~> 3.5"
# Carthage
github "mxcl/PromiseKit" ~> 3.5
```
# 文档
您可以通过 [promisekit.org] 站点来查看全部文档
## 概览
`then` 方法中定义异步任务:
```swift
login().then { json in
//…
}
```
链式调用:
```swift
login().then { json -> Promise<UIImage> in
return fetchAvatar(json["username"])
}.then { avatarImage in
self.imageView.image = avatarImage
}
```
`catch` 链式调用进行错误处理:
```swift
login().then {
return fetchAvatar()
}.then { avatarImage in
//…
}.catch { error in
UIAlertView(/*…*/).show()
}
```
组合调用:
```swift
let username = login().then{ $0["username"] }
when(username, CLLocationManager.promise()).then { user, location in
return fetchAvatar(user, location: location)
}.then { image in
//…
}
```
简单重构:
```swift
func avatar() -> Promise<UIImage> {
let username = login().then{ $0["username"] }
return when(username, CLLocationManager.promise()).then { user, location in
return fetchAvatar(user, location: location)
}
}
```
您也可以创建一个新的异步任务:
```swift
func fetchAvatar(user: String) -> Promise<UIImage> {
return Promise { fulfill, reject in
MyWebHelper.GET("\(user)/avatar") { data, err in
guard let data = data else { return reject(err) }
guard let img = UIImage(data: data) else { return reject(MyError.InvalidImage) }
guard let img.size.width > 0 else { return reject(MyError.ImageTooSmall) }
fulfill(img)
}
}
}
```
## 更多用法
您可以通过 [promisekit.org] 站点获得更多用法。
## PromiseKit vs. Xcode
由于 Xcode 支持不同版本的 swift下面是 PromiseKit 与 Xcode 的对应关系:
| Swift | Xcode | PromiseKit | CI Status | Release Notes |
| ----- | ----- | ---------- | ------------ | ----------------- |
| 3 | 8 | 4 | ![ci-master] | [2016/09][news-4] |
| 2 | 7/8 | 3 | ![ci-swift2] | [2015/10][news-3] |
| 1 | 7 | 3 | | [2015/10][news-3] |
| *N/A* | * | 1† | ![ci-legacy] | |
† PromiseKit 1 是纯 Objective-C 开发的,因此您可以在 Xcode 的任何版本中使用,当需要支持 iOS 7 或更低版本时只能选择 PromiseKit 1。
---
我们同时维护了一些分支来帮助您做 Swift 版本间的移植:
| Xcode | Swift | PromiseKit | Branch | CI Status |
| ----- | ----- | -----------| --------------------------- | --------- |
| 8.0 | 2.3 | 2 | [swift-2.3-minimal-changes] | ![ci-23] |
| 7.3 | 2.2 | 2 | [swift-2.2-minimal-changes] | ![ci-22] |
| 7.2 | 2.2 | 2 | [swift-2.2-minimal-changes] | ![ci-22] |
| 7.1 | 2.1 | 2 | [swift-2.0-minimal-changes] | ![ci-20] |
| 7.0 | 2.0 | 2 | [swift-2.0-minimal-changes] | ![ci-20] |
我们通常不会再对这些分支做维护,但同样欢迎提交 PR。
# 扩展
Promises 仅在执行异步任务时非常有用,因此我们把苹果绝大部分接口都转换成了异步任务。当导入 Promises 时已经默认包含了 UIKit 和 Foundation。其他框架需要在 `Podfile` 中单独声明:
```ruby
pod "PromiseKit/MapKit" # MKDirections().promise().then { /*…*/ }
pod "PromiseKit/CoreLocation" # CLLocationManager.promise().then { /*…*/ }
```
扩展的所有 repo 请访问 [PromiseKit org ](https://github.com/PromiseKit)。
`Cartfile` 中声明:
```ruby
github "PromiseKit/MapKit" ~> 1.0
```
## 选择网络库
直接使用 `URLSession` 通常是不可取的,您可以选择使用 [Alamofire] or [OMGHTTPURLRQ]:
```swift
// pod 'PromiseKit/Alamofire'
Alamofire.request("http://example.com", withMethod: .GET).responseJSON().then { json in
//…
}.catch { error in
//…
}
// pod 'PromiseKit/OMGHTTPURLRQ'
URLSession.GET("http://example.com").asDictionary().then { json in
}.catch { error in
//…
}
```
[AFNetworking] 我们推荐使用 [csotiriou/AFNetworking]。
# 需要将您的代码转换到 Promises?
[有偿帮助](mailto:mxcl@me.com),我有几年 Promises 编码经验并在移动开发领域已有 10 年的开发经验。
# 支持
如果您有任何问题可以访问 [Gitter chat channel](https://gitter.im/mxcl/PromiseKit),也可以进行 [bug 追踪](https://github.com/mxcl/PromiseKit/issues/new)
[travis]: https://travis-ci.org/mxcl/PromiseKit
[ci-master]: https://travis-ci.org/mxcl/PromiseKit.svg?branch=master
[ci-legacy]: https://travis-ci.org/mxcl/PromiseKit.svg?branch=legacy-1.x
[ci-swift2]: https://travis-ci.org/mxcl/PromiseKit.svg?branch=swift-2.x
[ci-23]: https://travis-ci.org/mxcl/PromiseKit.svg?branch=swift-2.3-minimal-changes
[ci-22]: https://travis-ci.org/mxcl/PromiseKit.svg?branch=swift-2.2-minimal-changes
[ci-20]: https://travis-ci.org/mxcl/PromiseKit.svg?branch=swift-2.0-minimal-changes
[news-2]: http://promisekit.org/news/2015/05/PromiseKit-2.0-Released/
[news-3]: https://github.com/mxcl/PromiseKit/blob/master/CHANGELOG.markdown#300-oct-1st-2015
[news-4]: http://promisekit.org/news/2016/09/PromiseKit-4.0-Released/
[swift-2.3-minimal-changes]: https://github.com/mxcl/PromiseKit/tree/swift-2.3-minimal-changes
[swift-2.2-minimal-changes]: https://github.com/mxcl/PromiseKit/tree/swift-2.2-minimal-changes
[swift-2.0-minimal-changes]: https://github.com/mxcl/PromiseKit/tree/swift-2.0-minimal-changes
[promisekit.org]: http://promisekit.org/docs/
[badge-pod]: https://img.shields.io/cocoapods/v/PromiseKit.svg?label=version
[badge-platforms]: https://img.shields.io/badge/platforms-macOS%20%7C%20iOS%20%7C%20watchOS%20%7C%20tvOS-lightgrey.svg
[badge-languages]: https://img.shields.io/badge/languages-Swift%20%7C%20ObjC-orange.svg
[badge-mit]: https://img.shields.io/badge/license-MIT-blue.svg
[badge-pms]: https://img.shields.io/badge/supports-CocoaPods%20%7C%20Carthage%20%7C%20SwiftPM-green.svg
[OMGHTTPURLRQ]: https://github.com/mxcl/OMGHTTPURLRQ
[Alamofire]: http://alamofire.org
[AFNetworking]: https://github.com/AFNetworking/AFNetworking
[csotiriou/AFNetworking]: https://github.com/csotiriou/AFNetworking-PromiseKit
[CocoaPods]: http://cocoapods.org
[Carthage]: 2016-09-05-PromiseKit-4.0-Released

View File

@ -1,236 +0,0 @@
![PromiseKit](http://promisekit.org/public/img/logo-tight.png)
![badge-pod] ![badge-languages] ![badge-pms] ![badge-platforms] ![badge-mit]
[English](README.markdown)
---
現今的程式開發大量使用非同步存取, 難道不正是時候該開始考慮使用合適的工具讓非同步程式更加的強大, 易用, 並且使用愉快?
```swift
UIApplication.shared.isNetworkActivityIndicatorVisible = true
firstly {
when(URLSession.dataTask(with: url).asImage(), CLLocationManager.promise())
}.then { image, location -> Void in
self.imageView.image = image;
self.label.text = "\(location)"
}.always {
UIApplication.shared.isNetworkActivityIndicatorVisible = false
}.catch { error in
UIAlertView(/*…*/).show()
}
```
PromiseKit 是一個使用 Swift 語言完整實作 Promise 概念的工具套件. 除了能夠完美的和現有的 Objective-C 程式進行整合之外, 也支援多個不同平台, 如, iOS, macOS, tvOs 以及 watchOS.
# 快速入門
我們推薦使用 [CocoaPods] 或 [Carthage] 等套件管理程式來進行安裝. 如不使用套件管理程式安裝, 也可以手動下載相關程式, 並把 `PromiseKit.xcodeproj` 拖曳加入您的專案中, 並將 `PromiseKit.framework` 加入.
## Xcode 8 / Swift 3
```ruby
# CocoaPods >= 1.1.0-rc.2
swift_version = "3.0"
pod "PromiseKit", "~> 4.0"
# Carthage
github "mxcl/PromiseKit" ~> 4.0
# SwiftPM
let package = Package(
dependencies: [
.Package(url: "https://github.com/mxcl/PromiseKit", majorVersion: 4)
]
)
```
## Xcode 8 / Swift 2.3 or Xcode 7
```ruby
# CocoaPods
swift_version = "2.3"
pod "PromiseKit", "~> 3.5"
# Carthage
github "mxcl/PromiseKit" ~> 3.5
```
# 相關文件
您可以在 [promisekit.org] 查詢完整的相關文件.
## 總覽
使用 `then` 定義非同步的執行任務:
```swift
login().then { json in
//…
}
```
鏈結使用:
```swift
login().then { json -> Promise<UIImage> in
return fetchAvatar(json["username"])
}.then { avatarImage in
self.imageView.image = avatarImage
}
```
串連式的錯誤/例外處理:
```swift
login().then {
return fetchAvatar()
}.then { avatarImage in
//…
}.catch { error in
UIAlertView(/*…*/).show()
}
```
組合應用:
```swift
let username = login().then{ $0["username"] }
when(username, CLLocationManager.promise()).then { user, location in
return fetchAvatar(user, location: location)
}.then { image in
//…
}
```
毫無難度的重構:
```swift
func avatar() -> Promise<UIImage> {
let username = login().then{ $0["username"] }
return when(username, CLLocationManager.promise()).then { user, location in
return fetchAvatar(user, location: location)
}
}
```
您可以輕易地建構一個非同步任務:
```swift
func fetchAvatar(user: String) -> Promise<UIImage> {
return Promise { fulfill, reject in
MyWebHelper.GET("\(user)/avatar") { data, err in
guard let data = data else { return reject(err) }
guard let img = UIImage(data: data) else { return reject(MyError.InvalidImage) }
guard let img.size.width > 0 else { return reject(MyError.ImageTooSmall) }
fulfill(img)
}
}
}
```
## 更多詳細用法
您可以透過 [promisekit.org] 學習更多完整的用法.
## PromiseKit vs. Xcode
PromiseKit 使用 Swift 進行開發, 下表列出 Xcode 以及 Swifit 的相關對應版本:
| Swift | Xcode | PromiseKit | CI Status | Release Notes |
| ----- | ----- | ---------- | ------------ | ----------------- |
| 3 | 8 | 4 | ![ci-master] | [2016/09][news-4] |
| 2 | 7/8 | 3 | ![ci-swift2] | [2015/10][news-3] |
| 1 | 7 | 3 | | [2015/10][news-3] |
| *N/A* | * | 1† | ![ci-legacy] | |
† PromiseKit 1 使用純粹的 Objective-C 進行開發,因此可以在任意的 Xcode 版本中使用,若您需要支援 iOS7 或者更低版本時, 只能選擇使用 PromiseKit 1。
---
我們同時維護了一些分支來協助您做不同 Swift 版本間的移植:
| Xcode | Swift | PromiseKit | Branch | CI Status |
| ----- | ----- | -----------| --------------------------- | --------- |
| 8.0 | 2.3 | 2 | [swift-2.3-minimal-changes] | ![ci-23] |
| 7.3 | 2.2 | 2 | [swift-2.2-minimal-changes] | ![ci-22] |
| 7.2 | 2.2 | 2 | [swift-2.2-minimal-changes] | ![ci-22] |
| 7.1 | 2.1 | 2 | [swift-2.0-minimal-changes] | ![ci-20] |
| 7.0 | 2.0 | 2 | [swift-2.0-minimal-changes] | ![ci-20] |
我們通常**不會**再對上述的分支進行維護, 但如果有任何的 PR 我們也歡迎提交.
# 相關擴充
Promises 僅對執行非同步任務非常有用, 因此我們把 Apple 絕大部份的 API 轉換成 Promises. 透過 CocoaPod 導入套件時即預設帶入 UIKit 和 Foundation, 而其他框架需要在您的 `Podfile` 檔案中進行額外的設定, 例如:
```ruby
pod "PromiseKit/MapKit" # MKDirections().promise().then { /*…*/ }
pod "PromiseKit/CoreLocation" # CLLocationManager.promise().then { /*…*/ }
```
我們所有相關的擴充專案可以到 [PromiseKit org ](https://github.com/PromiseKit) 查詢.
使用 `Cartfile` 進行設定:
```ruby
github "PromiseKit/MapKit" ~> 1.0
```
## 選擇使用網路相關函式庫
`URLSession` 一般來說很難勝任複雜的網路存取相關任務; 建議使用 [Alamofire] 或者 [OMGHTTPURLRQ]:
```swift
// pod 'PromiseKit/Alamofire'
Alamofire.request("http://example.com", withMethod: .GET).responseJSON().then { json in
//…
}.catch { error in
//…
}
// pod 'PromiseKit/OMGHTTPURLRQ'
URLSession.GET("http://example.com").asDictionary().then { json in
}.catch { error in
//…
}
```
針對使用 [AFNetworking] 的開發者, 我們推薦使用 [csotiriou/AFNetworking].
# 有轉換您現有的程式碼到 Promises 的需求嗎?
[與我聯繫](mailto:mxcl@me.com), 我在 iOS 上使用 Promises 進行開發已經有多年的經驗, 同時也有 10 年以上開發行動裝置 App 的經驗.
# 支援
可以在 [Gitter chat channel](https://gitter.im/mxcl/PromiseKit) 詢問相關問題, 或者直接追蹤我們的 [bug tracker](https://github.com/mxcl/PromiseKit/issues/new)
[travis]: https://travis-ci.org/mxcl/PromiseKit
[ci-master]: https://travis-ci.org/mxcl/PromiseKit.svg?branch=master
[ci-legacy]: https://travis-ci.org/mxcl/PromiseKit.svg?branch=legacy-1.x
[ci-swift2]: https://travis-ci.org/mxcl/PromiseKit.svg?branch=swift-2.x
[ci-23]: https://travis-ci.org/mxcl/PromiseKit.svg?branch=swift-2.3-minimal-changes
[ci-22]: https://travis-ci.org/mxcl/PromiseKit.svg?branch=swift-2.2-minimal-changes
[ci-20]: https://travis-ci.org/mxcl/PromiseKit.svg?branch=swift-2.0-minimal-changes
[news-2]: http://promisekit.org/news/2015/05/PromiseKit-2.0-Released/
[news-3]: https://github.com/mxcl/PromiseKit/blob/master/CHANGELOG.markdown#300-oct-1st-2015
[news-4]: http://promisekit.org/news/2016/09/PromiseKit-4.0-Released/
[swift-2.3-minimal-changes]: https://github.com/mxcl/PromiseKit/tree/swift-2.3-minimal-changes
[swift-2.2-minimal-changes]: https://github.com/mxcl/PromiseKit/tree/swift-2.2-minimal-changes
[swift-2.0-minimal-changes]: https://github.com/mxcl/PromiseKit/tree/swift-2.0-minimal-changes
[promisekit.org]: http://promisekit.org/docs/
[badge-pod]: https://img.shields.io/cocoapods/v/PromiseKit.svg?label=version
[badge-platforms]: https://img.shields.io/badge/platforms-macOS%20%7C%20iOS%20%7C%20watchOS%20%7C%20tvOS-lightgrey.svg
[badge-languages]: https://img.shields.io/badge/languages-Swift%20%7C%20ObjC-orange.svg
[badge-mit]: https://img.shields.io/badge/license-MIT-blue.svg
[badge-pms]: https://img.shields.io/badge/supports-CocoaPods%20%7C%20Carthage%20%7C%20SwiftPM-green.svg
[OMGHTTPURLRQ]: https://github.com/mxcl/OMGHTTPURLRQ
[Alamofire]: http://alamofire.org
[AFNetworking]: https://github.com/AFNetworking/AFNetworking
[csotiriou/AFNetworking]: https://github.com/csotiriou/AFNetworking-PromiseKit
[CocoaPods]: http://cocoapods.org
[Carthage]: 2016-09-05-PromiseKit-4.0-Released

View File

@ -25,14 +25,8 @@
#import "AnyPromise.h"
@interface AnyPromise (Swift)
- (AnyPromise * __nonnull)__thenOn:(__nonnull dispatch_queue_t)q execute:(id __nullable (^ __nonnull)(id __nullable))body;
- (AnyPromise * __nonnull)__catchOn:(__nonnull dispatch_queue_t)q withPolicy:(PMKCatchPolicy)policy execute:(id __nullable (^ __nonnull)(id __nullable))body;
- (AnyPromise * __nonnull)__alwaysOn:(__nonnull dispatch_queue_t)q execute:(void (^ __nonnull)(void))body;
- (void)__pipe:(void(^ __nonnull)(__nullable id))body;
- (AnyPromise * __nonnull)initWithResolverBlock:(void (^ __nonnull)(PMKResolver __nonnull))resolver;
@end
extern NSError * __nullable PMKProcessUnhandledException(id __nonnull thrown);
@class PMKArray;
@interface AnyPromise ()
- (void)__pipe:(void(^ __nonnull)(__nullable id))block NS_REFINED_FOR_SWIFT;
@end

View File

@ -1,43 +1,77 @@
#import <Foundation/Foundation.h>
#import <dispatch/dispatch.h>
#import "fwd.h"
#import <PromiseKit/fwd.h>
/// INTERNAL DO NOT USE
@class __AnyPromise;
/// Provided to simplify some usage sites
typedef void (^PMKResolver)(id __nullable) NS_REFINED_FOR_SWIFT;
typedef NS_ENUM(NSInteger, PMKCatchPolicy) {
PMKCatchPolicyAllErrors,
PMKCatchPolicyAllErrorsExceptCancellation
} NS_SWIFT_NAME(CatchPolicy);
#if __has_include("PromiseKit-Swift.h")
#pragma clang diagnostic push
#pragma clang diagnostic ignored"-Wdocumentation"
#import "PromiseKit-Swift.h"
#pragma clang diagnostic pop
#else
// this hack because `AnyPromise` is Swift, but we add
// our own methods via the below category. This hack is
// only required while building PromiseKit since, once
// built, the generated -Swift header exists.
__attribute__((objc_subclassing_restricted)) __attribute__((objc_runtime_name("AnyPromise")))
@interface AnyPromise : NSObject
@property (nonatomic, readonly) BOOL resolved;
@property (nonatomic, readonly) BOOL pending;
@property (nonatomic, readonly) __nullable id value;
+ (instancetype __nonnull)promiseWithResolverBlock:(void (^ __nonnull)(__nonnull PMKResolver))resolveBlock;
+ (instancetype __nonnull)promiseWithValue:(__nullable id)value;
@end
#endif
@interface AnyPromise (obj)
@property (nonatomic, readonly) __nullable id value;
/// An Objective-C implementation of the promise pattern.
@interface AnyPromise: NSObject
/**
The provided block is executed when its receiver is resolved.
Create a new promise that resolves with the provided block.
Use this method when wrapping asynchronous code that does *not* use promises so that this code can be used in promise chains.
If `resolve` is called with an `NSError` object, the promise is rejected, otherwise the promise is fulfilled.
Dont use this method if you already have promises! Instead, just return your promise.
Should you need to fulfill a promise but have no sensical value to use: your promise is a `void` promise: fulfill with `nil`.
The block you pass is executed immediately on the calling thread.
- Parameter block: The provided block is immediately executed, inside the block call `resolve` to resolve this promise and cause any attached handlers to execute. If you are wrapping a delegate-based system, we recommend instead to use: initWithResolver:
- Returns: A new promise.
- Warning: Resolving a promise with `nil` fulfills it.
- SeeAlso: https://github.com/mxcl/PromiseKit/blob/master/Documentation/GettingStarted.md#making-promises
- SeeAlso: https://github.com/mxcl/PromiseKit/blob/master/Documentation/CommonPatterns.md#wrapping-delegate-systems
*/
+ (instancetype __nonnull)promiseWithResolverBlock:(void (^ __nonnull)(__nonnull PMKResolver))resolveBlock NS_REFINED_FOR_SWIFT;
/// INTERNAL DO NOT USE
- (instancetype __nonnull)initWith__D:(__AnyPromise * __nonnull)d;
/**
Creates a resolved promise.
When developing your own promise systems, it is occasionally useful to be able to return an already resolved promise.
- Parameter value: The value with which to resolve this promise. Passing an `NSError` will cause the promise to be rejected, passing an AnyPromise will return a new AnyPromise bound to that promise, otherwise the promise will be fulfilled with the value passed.
- Returns: A resolved promise.
*/
+ (instancetype __nonnull)promiseWithValue:(__nullable id)value NS_REFINED_FOR_SWIFT;
/**
The value of the asynchronous task this promise represents.
A promise has `nil` value if the asynchronous task it represents has not finished. If the value is `nil` the promise is still `pending`.
- Warning: *Note* Our Swift variants value property returns nil if the promise is rejected where AnyPromise will return the error object. This fits with the pattern where AnyPromise is not strictly typed and is more dynamic, but you should be aware of the distinction.
- Note: If the AnyPromise was fulfilled with a `PMKManifold`, returns only the first fulfillment object.
- Returns: The value with which this promise was resolved or `nil` if this promise is pending.
*/
@property (nonatomic, readonly) __nullable id value NS_REFINED_FOR_SWIFT;
/// - Returns: if the promise is pending resolution.
@property (nonatomic, readonly) BOOL pending NS_REFINED_FOR_SWIFT;
/// - Returns: if the promise is resolved and fulfilled.
@property (nonatomic, readonly) BOOL fulfilled NS_REFINED_FOR_SWIFT;
/// - Returns: if the promise is resolved and rejected.
@property (nonatomic, readonly) BOOL rejected NS_REFINED_FOR_SWIFT;
/**
The provided block is executed when its receiver is fulfilled.
If you provide a block that takes a parameter, the value of the receiver will be passed as that parameter.
@ -93,9 +127,10 @@ typedef NS_ENUM(NSInteger, PMKCatchPolicy) {
@warning *Note* Cancellation errors are not caught.
@warning *Note* Since catch is a c++ keyword, this method is not available in Objective-C++ files. Instead use catchWithPolicy.
@warning *Note* Since catch is a c++ keyword, this method is not available in Objective-C++ files. Instead use catchOn.
@see catchWithPolicy
@see catchOn
@see catchInBackground
*/
- (AnyPromise * __nonnull(^ __nonnull)(id __nonnull))catch NS_REFINED_FOR_SWIFT;
#endif
@ -111,7 +146,8 @@ typedef NS_ENUM(NSInteger, PMKCatchPolicy) {
@warning *Note* Since catch is a c++ keyword, this method is not available in Objective-C++ files. Instead use catchWithPolicy.
@see catchWithPolicy
@see catch
@see catchOn
*/
- (AnyPromise * __nonnull(^ __nonnull)(id __nonnull))catchInBackground NS_REFINED_FOR_SWIFT;
@ -125,50 +161,33 @@ typedef NS_ENUM(NSInteger, PMKCatchPolicy) {
@warning *Note* Cancellation errors are not caught.
@see catchWithPolicy
@see catch
@see catchInBackground
*/
- (AnyPromise * __nonnull(^ __nonnull)(dispatch_queue_t __nonnull, id __nonnull))catchOn NS_REFINED_FOR_SWIFT;
/**
The provided block is executed when the receiver is rejected with the specified policy.
Specify the policy with which to catch as the first parameter to your block. Either for all errors, or all errors *except* cancellation errors.
@see catch
*/
- (AnyPromise * __nonnull(^ __nonnull)(PMKCatchPolicy, id __nonnull))catchWithPolicy NS_REFINED_FOR_SWIFT;
/**
The provided block is executed when the receiver is rejected with the specified policy.
Specify the policy with which to catch as the first parameter to your block. Either for all errors, or all errors *except* cancellation errors.
The provided block always runs on queue provided.
@see catch
*/
- (AnyPromise * __nonnull(^ __nonnull)(dispatch_queue_t __nonnull, PMKCatchPolicy, id __nonnull))catchOnWithPolicy NS_REFINED_FOR_SWIFT;
/**
The provided block is executed when the receiver is resolved.
The provided block always runs on the main queue.
@see alwaysOn
@see ensureOn
*/
- (AnyPromise * __nonnull(^ __nonnull)(dispatch_block_t __nonnull))always NS_REFINED_FOR_SWIFT;
- (AnyPromise * __nonnull(^ __nonnull)(dispatch_block_t __nonnull))ensure NS_REFINED_FOR_SWIFT;
/**
The provided block is executed on the dispatch queue of your choice when the receiver is resolved.
@see always
@see ensure
*/
- (AnyPromise * __nonnull(^ __nonnull)(dispatch_queue_t __nonnull, dispatch_block_t __nonnull))alwaysOn NS_REFINED_FOR_SWIFT;
- (AnyPromise * __nonnull(^ __nonnull)(dispatch_queue_t __nonnull, dispatch_block_t __nonnull))ensureOn NS_REFINED_FOR_SWIFT;
/// @see always
- (AnyPromise * __nonnull(^ __nonnull)(dispatch_block_t __nonnull))finally __attribute__((deprecated("Use always")));
/// @see alwaysOn
- (AnyPromise * __nonnull(^ __nonnull)(dispatch_block_t __nonnull, dispatch_block_t __nonnull))finallyOn __attribute__((deprecated("Use always")));
/**
Wait until the promise is resolved.
@return Value if fulfilled or error if rejected.
*/
- (id __nullable)wait NS_REFINED_FOR_SWIFT;
/**
Create a new promise with an associated resolver.
@ -197,16 +216,6 @@ typedef NS_ENUM(NSInteger, PMKCatchPolicy) {
@end
@interface AnyPromise (Unavailable)
- (instancetype __nonnull)init __attribute__((unavailable("It is illegal to create an unresolvable promise.")));
+ (instancetype __nonnull)new __attribute__((unavailable("It is illegal to create an unresolvable promise.")));
@end
typedef void (^PMKAdapter)(id __nullable, NSError * __nullable) NS_REFINED_FOR_SWIFT;
typedef void (^PMKIntegerAdapter)(NSInteger, NSError * __nullable) NS_REFINED_FOR_SWIFT;
typedef void (^PMKBooleanAdapter)(BOOL, NSError * __nullable) NS_REFINED_FOR_SWIFT;
@ -230,7 +239,7 @@ typedef void (^PMKBooleanAdapter)(BOOL, NSError * __nullable) NS_REFINED_FOR_SWI
@warning *Important* If both parameters are nil, the promise fulfills,
if both are non-nil the promise rejects. This is per the convention.
@see http://promisekit.org/sealing-your-own-promises/
@see https://github.com/mxcl/PromiseKit/blob/master/Documentation/GettingStarted.md#making-promises
*/
+ (instancetype __nonnull)promiseWithAdapterBlock:(void (^ __nonnull)(PMKAdapter __nonnull adapter))block NS_REFINED_FOR_SWIFT;
@ -281,15 +290,17 @@ extern id __nonnull __PMKArrayWithCount(NSUInteger, ...);
#endif
@interface AnyPromise (Deprecations)
@interface AnyPromise (Unavailable)
+ (instancetype __nonnull)new:(__nullable id)resolvers __attribute__((unavailable("See +promiseWithResolverBlock:")));
+ (instancetype __nonnull)when:(__nullable id)promises __attribute__((unavailable("See PMKWhen()")));
+ (instancetype __nonnull)join:(__nullable id)promises __attribute__((unavailable("See PMKJoin()")));
- (instancetype __nonnull)init __attribute__((unavailable("It is illegal to create an unresolvable promise.")));
+ (instancetype __nonnull)new __attribute__((unavailable("It is illegal to create an unresolvable promise.")));
- (AnyPromise * __nonnull(^ __nonnull)(dispatch_block_t __nonnull))always __attribute__((unavailable("See -ensure")));
- (AnyPromise * __nonnull(^ __nonnull)(dispatch_block_t __nonnull))alwaysOn __attribute__((unavailable("See -ensureOn")));
- (AnyPromise * __nonnull(^ __nonnull)(dispatch_block_t __nonnull))finally __attribute__((unavailable("See -ensure")));
- (AnyPromise * __nonnull(^ __nonnull)(dispatch_block_t __nonnull, dispatch_block_t __nonnull))finallyOn __attribute__((unavailable("See -ensureOn")));
@end
__attribute__((unavailable("See AnyPromise")))
@interface PMKPromise
@end

View File

@ -1,22 +1,60 @@
#if __has_include("PromiseKit-Swift.h")
#import "PromiseKit-Swift.h"
#else
#import <PromiseKit/PromiseKit-Swift.h>
#endif
#import "PMKCallVariadicBlock.m"
#import "AnyPromise+Private.h"
extern dispatch_queue_t PMKDefaultDispatchQueue(void);
#import "AnyPromise.h"
NSString *const PMKErrorDomain = @"PMKErrorDomain";
@implementation AnyPromise (objc)
@implementation AnyPromise {
__AnyPromise *d;
}
- (instancetype)initWith__D:(__AnyPromise *)dd {
self = [super init];
if (self) self->d = dd;
return self;
}
- (instancetype)initWithResolver:(PMKResolver __strong *)resolver {
return [[self class] promiseWithResolverBlock:^(PMKResolver resolve){
*resolver = resolve;
self = [super init];
if (self)
d = [[__AnyPromise alloc] initWithResolver:^(void (^resolve)(id)) {
*resolver = resolve;
}];
return self;
}
+ (instancetype)promiseWithResolverBlock:(void (^)(PMKResolver _Nonnull))resolveBlock {
id d = [[__AnyPromise alloc] initWithResolver:resolveBlock];
return [[self alloc] initWith__D:d];
}
+ (instancetype)promiseWithValue:(id)value {
//TODO provide a more efficient route for sealed promises
id d = [[__AnyPromise alloc] initWithResolver:^(void (^resolve)(id)) {
resolve(value);
}];
return [[self alloc] initWith__D:d];
}
//TODO remove if possible, but used by when.m
- (void)__pipe:(void (^)(id _Nullable))block {
[d __pipe:block];
}
//NOTE used by AnyPromise.swift
- (id)__d {
return d;
}
- (AnyPromise *(^)(id))then {
return ^(id block) {
return [self __thenOn:PMKDefaultDispatchQueue() execute:^(id obj) {
return [self->d __thenOn:dispatch_get_main_queue() execute:^(id obj) {
return PMKCallVariadicBlock(block, obj);
}];
};
@ -24,7 +62,7 @@ NSString *const PMKErrorDomain = @"PMKErrorDomain";
- (AnyPromise *(^)(dispatch_queue_t, id))thenOn {
return ^(dispatch_queue_t queue, id block) {
return [self __thenOn:queue execute:^(id obj) {
return [self->d __thenOn:queue execute:^(id obj) {
return PMKCallVariadicBlock(block, obj);
}];
};
@ -32,7 +70,7 @@ NSString *const PMKErrorDomain = @"PMKErrorDomain";
- (AnyPromise *(^)(id))thenInBackground {
return ^(id block) {
return [self __thenOn:dispatch_get_global_queue(0, 0) execute:^(id obj) {
return [self->d __thenOn:dispatch_get_global_queue(0, 0) execute:^(id obj) {
return PMKCallVariadicBlock(block, obj);
}];
};
@ -40,7 +78,7 @@ NSString *const PMKErrorDomain = @"PMKErrorDomain";
- (AnyPromise *(^)(dispatch_queue_t, id))catchOn {
return ^(dispatch_queue_t q, id block) {
return [self __catchOn:q withPolicy:PMKCatchPolicyAllErrorsExceptCancellation execute:^(id obj) {
return [self->d __catchOn:q execute:^(id obj) {
return PMKCallVariadicBlock(block, obj);
}];
};
@ -48,7 +86,7 @@ NSString *const PMKErrorDomain = @"PMKErrorDomain";
- (AnyPromise *(^)(id))catch {
return ^(id block) {
return [self __catchOn:PMKDefaultDispatchQueue() withPolicy:PMKCatchPolicyAllErrorsExceptCancellation execute:^(id obj) {
return [self->d __catchOn:dispatch_get_main_queue() execute:^(id obj) {
return PMKCallVariadicBlock(block, obj);
}];
};
@ -56,40 +94,50 @@ NSString *const PMKErrorDomain = @"PMKErrorDomain";
- (AnyPromise *(^)(id))catchInBackground {
return ^(id block) {
return [self __catchOn:dispatch_get_global_queue(0, 0) withPolicy:PMKCatchPolicyAllErrorsExceptCancellation execute:^(id obj) {
return [self->d __catchOn:dispatch_get_global_queue(0, 0) execute:^(id obj) {
return PMKCallVariadicBlock(block, obj);
}];
};
}
- (AnyPromise *(^)(dispatch_queue_t, PMKCatchPolicy, id))catchOnWithPolicy {
return ^(dispatch_queue_t q, PMKCatchPolicy policy, id block) {
return [self __catchOn:q withPolicy:policy execute:^(id obj) {
return PMKCallVariadicBlock(block, obj);
}];
};
}
- (AnyPromise *(^)(PMKCatchPolicy, id))catchWithPolicy {
return ^(PMKCatchPolicy policy, id block) {
return [self __catchOn:PMKDefaultDispatchQueue() withPolicy:policy execute:^(id obj) {
return PMKCallVariadicBlock(block, obj);
}];
};
}
- (AnyPromise *(^)(dispatch_block_t))always {
- (AnyPromise *(^)(dispatch_block_t))ensure {
return ^(dispatch_block_t block) {
return [self __alwaysOn:PMKDefaultDispatchQueue() execute:block];
return [self->d __ensureOn:dispatch_get_main_queue() execute:block];
};
}
- (AnyPromise *(^)(dispatch_queue_t, dispatch_block_t))alwaysOn {
- (AnyPromise *(^)(dispatch_queue_t, dispatch_block_t))ensureOn {
return ^(dispatch_queue_t queue, dispatch_block_t block) {
return [self __alwaysOn:queue execute:block];
return [self->d __ensureOn:queue execute:block];
};
}
- (id)wait {
return [d __wait];
}
- (BOOL)pending {
return [[d valueForKey:@"__pending"] boolValue];
}
- (BOOL)rejected {
return IsError([d __value]);
}
- (BOOL)fulfilled {
return !self.rejected;
}
- (id)value {
id obj = [d __value];
if ([obj isKindOfClass:[PMKArray class]]) {
return obj[0];
} else {
return obj;
}
}
@end
@ -128,14 +176,4 @@ NSString *const PMKErrorDomain = @"PMKErrorDomain";
}];
}
- (id)value {
id obj = [self valueForKey:@"__value"];
if ([obj isKindOfClass:[PMKArray class]]) {
return obj[0];
} else {
return obj;
}
}
@end

View File

@ -1,279 +1,224 @@
import Foundation
/**
AnyPromise is an Objective-C compatible promise.
__AnyPromise is an implementation detail.
Because of how ObjC/Swift compatibility work we have to compose our AnyPromise
with this internal object, however this is still part of the public interface.
Sadly. Please dont use it.
*/
@objc(AnyPromise) public class AnyPromise: NSObject {
let state: State<Any?>
@objc(__AnyPromise) public class __AnyPromise: NSObject {
fileprivate let box: Box<Any?>
/**
- Returns: A new `AnyPromise` bound to a `Promise<Any>`.
*/
required public init(_ bridge: Promise<Any?>) {
state = bridge.state
@objc public init(resolver body: (@escaping (Any?) -> Void) -> Void) {
box = EmptyBox<Any?>()
super.init()
body {
if let p = $0 as? AnyPromise {
p.d.__pipe(self.box.seal)
} else {
self.box.seal($0)
}
}
}
/// hack to ensure Swift picks the right initializer for each of the below
private init(force: Promise<Any?>) {
state = force.state
@objc public func __thenOn(_ q: DispatchQueue, execute: @escaping (Any?) -> Any?) -> AnyPromise {
return AnyPromise(__D: __AnyPromise(resolver: { resolve in
self.__pipe { obj in
if !(obj is NSError) {
q.async {
resolve(execute(obj))
}
} else {
resolve(obj)
}
}
}))
}
/**
- Returns: A new `AnyPromise` bound to a `Promise<T>`.
*/
public convenience init<T>(_ bridge: Promise<T?>) {
self.init(force: bridge.then(on: zalgo) { $0 })
@objc public func __catchOn(_ q: DispatchQueue, execute: @escaping (Any?) -> Any?) -> AnyPromise {
return AnyPromise(__D: __AnyPromise(resolver: { resolve in
self.__pipe { obj in
if obj is NSError {
q.async {
resolve(execute(obj))
}
} else {
resolve(obj)
}
}
}))
}
/**
- Returns: A new `AnyPromise` bound to a `Promise<T>`.
*/
convenience public init<T>(_ bridge: Promise<T>) {
self.init(force: bridge.then(on: zalgo) { $0 })
@objc public func __ensureOn(_ q: DispatchQueue, execute: @escaping () -> Void) -> AnyPromise {
return AnyPromise(__D: __AnyPromise(resolver: { resolve in
self.__pipe { obj in
q.async {
execute()
resolve(obj)
}
}
}))
}
/**
- Returns: A new `AnyPromise` bound to a `Promise<Void>`.
- Note: A void `AnyPromise` has a value of `nil`.
*/
convenience public init(_ bridge: Promise<Void>) {
self.init(force: bridge.then(on: zalgo) { nil })
@objc public func __wait() -> Any? {
if Thread.isMainThread {
conf.logHandler(.waitOnMainThread)
}
var result = __value
if result == nil {
let group = DispatchGroup()
group.enter()
self.__pipe { obj in
result = obj
group.leave()
}
group.wait()
}
return result
}
/// Internal, do not use! Some behaviors undefined.
@objc public func __pipe(_ to: @escaping (Any?) -> Void) {
let to = { (obj: Any?) -> Void in
if obj is NSError {
to(obj) // or we cannot determine if objects are errors in objc land
} else {
to(obj)
}
}
switch box.inspect() {
case .pending:
box.inspect {
switch $0 {
case .pending(let handlers):
handlers.append { obj in
to(obj)
}
case .resolved(let obj):
to(obj)
}
}
case .resolved(let obj):
to(obj)
}
}
/**
Bridges an `AnyPromise` to a `Promise<Any?>`.
@objc public var __value: Any? {
switch box.inspect() {
case .resolved(let obj):
return obj
default:
return nil
}
}
- Note: AnyPromises fulfilled with `PMKManifold` lose all but the first fulfillment object.
- Remark: Could not make this an initializer of `Promise` due to generics issues.
*/
@objc public var __pending: Bool {
switch box.inspect() {
case .pending:
return true
case .resolved:
return false
}
}
}
extension AnyPromise: Thenable, CatchMixin {
/// - Returns: A new `AnyPromise` bound to a `Promise<Any>`.
public convenience init<U: Thenable>(_ bridge: U) {
self.init(__D: __AnyPromise(resolver: { resolve in
bridge.pipe {
switch $0 {
case .rejected(let error):
resolve(error as NSError)
case .fulfilled(let value):
resolve(value)
}
}
}))
}
public func pipe(to body: @escaping (Result<Any?>) -> Void) {
func fulfill() {
// calling through to the ObjC `value` property unwraps (any) PMKManifold
// and considering this is the Swift pipe; we want that.
body(.fulfilled(self.value(forKey: "value")))
}
switch box.inspect() {
case .pending:
box.inspect {
switch $0 {
case .pending(let handlers):
handlers.append {
if let error = $0 as? Error {
body(.rejected(error))
} else {
fulfill()
}
}
case .resolved(let error as Error):
body(.rejected(error))
case .resolved:
fulfill()
}
}
case .resolved(let error as Error):
body(.rejected(error))
case .resolved:
fulfill()
}
}
fileprivate var d: __AnyPromise {
return value(forKey: "__d") as! __AnyPromise
}
var box: Box<Any?> {
return d.box
}
public var result: Result<Any?>? {
guard let value = __value else {
return nil
}
if let error = value as? Error {
return .rejected(error)
} else {
return .fulfilled(value)
}
}
public typealias T = Any?
}
#if swift(>=3.1)
public extension Promise where T == Any? {
convenience init(_ anyPromise: AnyPromise) {
self.init {
anyPromise.pipe(to: $0.resolve)
}
}
}
#else
extension AnyPromise {
public func asPromise() -> Promise<Any?> {
return Promise(sealant: { resolve in
state.pipe { resolution in
switch resolution {
case .rejected:
resolve(resolution)
case .fulfilled:
let obj = (self as AnyObject).value(forKey: "value")
resolve(.fulfilled(obj))
return Promise(.pending, resolver: { resolve in
pipe { result in
switch result {
case .rejected(let error):
resolve.reject(error)
case .fulfilled(let obj):
resolve.fulfill(obj)
}
}
})
}
/// - See: `Promise.then()`
public func then<T>(on q: DispatchQueue = .default, execute body: @escaping (Any?) throws -> T) -> Promise<T> {
return asPromise().then(on: q, execute: body)
}
/// - See: `Promise.then()`
public func then(on q: DispatchQueue = .default, execute body: @escaping (Any?) throws -> AnyPromise) -> Promise<Any?> {
return asPromise().then(on: q, execute: body)
}
/// - See: `Promise.then()`
public func then<T>(on q: DispatchQueue = .default, execute body: @escaping (Any?) throws -> Promise<T>) -> Promise<T> {
return asPromise().then(on: q, execute: body)
}
/// - See: `Promise.always()`
public func always(on q: DispatchQueue = .default, execute body: @escaping () -> Void) -> Promise<Any?> {
return asPromise().always(execute: body)
}
/// - See: `Promise.tap()`
public func tap(on q: DispatchQueue = .default, execute body: @escaping (Result<Any?>) -> Void) -> Promise<Any?> {
return asPromise().tap(execute: body)
}
/// - See: `Promise.recover()`
public func recover(on q: DispatchQueue = .default, policy: CatchPolicy = .allErrorsExceptCancellation, execute body: @escaping (Error) throws -> Promise<Any?>) -> Promise<Any?> {
return asPromise().recover(on: q, policy: policy, execute: body)
}
/// - See: `Promise.recover()`
public func recover(on q: DispatchQueue = .default, policy: CatchPolicy = .allErrorsExceptCancellation, execute body: @escaping (Error) throws -> Any?) -> Promise<Any?> {
return asPromise().recover(on: q, policy: policy, execute: body)
}
/// - See: `Promise.catch()`
public func `catch`(on q: DispatchQueue = .default, policy: CatchPolicy = .allErrorsExceptCancellation, execute body: @escaping (Error) -> Void) {
state.catch(on: q, policy: policy, else: { _ in }, execute: body)
}
//MARK: ObjC methods
/**
A promise starts pending and eventually resolves.
- Returns: `true` if the promise has not yet resolved.
*/
@objc public var pending: Bool {
return state.get() == nil
}
/**
A promise starts pending and eventually resolves.
- Returns: `true` if the promise has resolved.
*/
@objc public var resolved: Bool {
return !pending
}
/**
The value of the asynchronous task this promise represents.
A promise has `nil` value if the asynchronous task it represents has not finished. If the value is `nil` the promise is still `pending`.
- Warning: *Note* Our Swift variants value property returns nil if the promise is rejected where AnyPromise will return the error object. This fits with the pattern where AnyPromise is not strictly typed and is more dynamic, but you should be aware of the distinction.
- Note: If the AnyPromise was fulfilled with a `PMKManifold`, returns only the first fulfillment object.
- Returns: The value with which this promise was resolved or `nil` if this promise is pending.
*/
@objc private var __value: Any? {
switch state.get() {
case nil:
return nil
case .rejected(let error, _)?:
return error
case .fulfilled(let obj)?:
return obj
}
}
/**
Creates a resolved promise.
When developing your own promise systems, it is occasionally useful to be able to return an already resolved promise.
- Parameter value: The value with which to resolve this promise. Passing an `NSError` will cause the promise to be rejected, passing an AnyPromise will return a new AnyPromise bound to that promise, otherwise the promise will be fulfilled with the value passed.
- Returns: A resolved promise.
*/
@objc public class func promiseWithValue(_ value: Any?) -> AnyPromise {
let state: State<Any?>
switch value {
case let promise as AnyPromise:
state = promise.state
case let err as Error:
state = SealedState(resolution: Resolution(err))
default:
state = SealedState(resolution: .fulfilled(value))
}
return AnyPromise(state: state)
}
private init(state: State<Any?>) {
self.state = state
}
/**
Create a new promise that resolves with the provided block.
Use this method when wrapping asynchronous code that does *not* use promises so that this code can be used in promise chains.
If `resolve` is called with an `NSError` object, the promise is rejected, otherwise the promise is fulfilled.
Dont use this method if you already have promises! Instead, just return your promise.
Should you need to fulfill a promise but have no sensical value to use: your promise is a `void` promise: fulfill with `nil`.
The block you pass is executed immediately on the calling thread.
- Parameter block: The provided block is immediately executed, inside the block call `resolve` to resolve this promise and cause any attached handlers to execute. If you are wrapping a delegate-based system, we recommend instead to use: initWithResolver:
- Returns: A new promise.
- Warning: Resolving a promise with `nil` fulfills it.
- SeeAlso: http://promisekit.org/sealing-your-own-promises/
- SeeAlso: http://promisekit.org/wrapping-delegation/
*/
@objc public class func promiseWithResolverBlock(_ body: (@escaping (Any?) -> Void) -> Void) -> AnyPromise {
return AnyPromise(sealant: { resolve in
body { obj in
makeHandler({ _ in obj }, resolve)(obj)
}
})
}
private init(sealant: (@escaping (Resolution<Any?>) -> Void) -> Void) {
var resolve: ((Resolution<Any?>) -> Void)!
state = UnsealedState(resolver: &resolve)
sealant(resolve)
}
@objc func __thenOn(_ q: DispatchQueue, execute body: @escaping (Any?) -> Any?) -> AnyPromise {
return AnyPromise(sealant: { resolve in
state.then(on: q, else: resolve, execute: makeHandler(body, resolve))
})
}
@objc func __catchOn(_ q: DispatchQueue, withPolicy policy: CatchPolicy, execute body: @escaping (Any?) -> Any?) -> AnyPromise {
return AnyPromise(sealant: { resolve in
state.catch(on: q, policy: policy, else: resolve) { err in
makeHandler(body, resolve)(err as NSError)
}
})
}
@objc func __alwaysOn(_ q: DispatchQueue, execute body: @escaping () -> Void) -> AnyPromise {
return AnyPromise(sealant: { resolve in
state.always(on: q) { resolution in
body()
resolve(resolution)
}
})
}
/**
Convert an `AnyPromise` to `Promise<T>`.
anyPromise.toPromise(T).then { (t: T) -> U in ... }
- Returns: A `Promise<T>` with the requested type.
- Throws: `CastingError.CastingAnyPromiseFailed(T)` if self's value cannot be downcasted to the given type.
*/
public func asPromise<T>(type: T.Type) -> Promise<T> {
return self.then(on: zalgo) { (value: Any?) -> T in
if let value = value as? T {
return value
}
throw PMKError.castError(type)
}
}
/// used by PMKWhen and PMKJoin
@objc func __pipe(_ body: @escaping (Any?) -> Void) {
state.pipe { resolution in
switch resolution {
case .rejected(let error, let token):
token.consumed = true // when and join will create a new parent error that is unconsumed
body(error as NSError)
case .fulfilled(let value):
body(value)
}
}
}
}
extension AnyPromise {
/**
- Returns: A description of the state of this promise.
*/
override public var description: String {
return "AnyPromise: \(state)"
}
}
private func makeHandler(_ body: @escaping (Any?) -> Any?, _ resolve: @escaping (Resolution<Any?>) -> Void) -> (Any?) -> Void {
return { obj in
let obj = body(obj)
switch obj {
case let err as Error:
resolve(Resolution(err))
case let promise as AnyPromise:
promise.state.pipe(resolve)
default:
resolve(.fulfilled(obj))
}
}
}
#endif

101
Sources/Box.swift Normal file
View File

@ -0,0 +1,101 @@
import Dispatch
enum Sealant<R> {
case pending(Handlers<R>)
case resolved(R)
}
final class Handlers<R> {
var bodies: [(R) -> Void] = []
func append(_ item: @escaping(R) -> Void) { bodies.append(item) }
}
/// - Remark: not protocol http://www.russbishop.net/swift-associated-types-cont
class Box<T> {
func inspect() -> Sealant<T> { fatalError() }
func inspect(_: (Sealant<T>) -> Void) { fatalError() }
func seal(_: T) {}
}
final class SealedBox<T>: Box<T> {
let value: T
init(value: T) {
self.value = value
}
override func inspect() -> Sealant<T> {
return .resolved(value)
}
}
class EmptyBox<T>: Box<T> {
private var sealant = Sealant<T>.pending(.init())
private let barrier = DispatchQueue(label: "org.promisekit.barrier", attributes: .concurrent)
override func seal(_ value: T) {
var handlers: Handlers<T>!
barrier.sync(flags: .barrier) {
guard case .pending(let _handlers) = self.sealant else {
return // already fulfilled!
}
handlers = _handlers
self.sealant = .resolved(value)
}
//FIXME we are resolved so should `pipe(to:)` be called at this instant, thens are called in order would be invalid
//NOTE we dont do this in the above `sync` because that could potentially deadlock
//THOUGH since `then` etc. typically invoke after a run-loop cycle, this issue is somewhat less severe
if let handlers = handlers {
handlers.bodies.forEach{ $0(value) }
}
//TODO solution is an unfortunate third state sealed where then's get added
// to a separate handler pool for that state
// any other solution has potential races
}
override func inspect() -> Sealant<T> {
var rv: Sealant<T>!
barrier.sync {
rv = self.sealant
}
return rv
}
override func inspect(_ body: (Sealant<T>) -> Void) {
var sealed = false
barrier.sync(flags: .barrier) {
switch sealant {
case .pending:
// body will append to handlers, so we must stay barrierd
body(sealant)
case .resolved:
sealed = true
}
}
if sealed {
// we do this outside the barrier to prevent potential deadlocks
// it's safe because we never transition away from this state
body(sealant)
}
}
}
extension Optional where Wrapped: DispatchQueue {
@inline(__always)
func async(flags: DispatchWorkItemFlags?, _ body: @escaping() -> Void) {
switch self {
case .none:
body()
case .some(let q):
if let flags = flags {
q.async(flags: flags, execute: body)
} else {
q.async(execute: body)
}
}
}
}

256
Sources/Catchable.swift Normal file
View File

@ -0,0 +1,256 @@
import Dispatch
/// Provides `catch` and `recover` to your object that conforms to `Thenable`
public protocol CatchMixin: Thenable
{}
public extension CatchMixin {
/**
The provided closure executes when this promise rejects.
Rejecting a promise cascades: rejecting all subsequent promises (unless
recover is invoked) thus you will typically place your catch at the end
of a chain. Often utility promises will not have a catch, instead
delegating the error handling to the caller.
- Parameter on: The queue to which the provided closure dispatches.
- Parameter policy: The default policy does not execute your handler for cancellation errors.
- Parameter execute: The handler to execute if this promise is rejected.
- Returns: A promise finalizer.
- SeeAlso: [Cancellation](https://github.com/mxcl/PromiseKit/blob/master/Documentation/CommonPatterns.md#cancellation)
*/
@discardableResult
func `catch`(on: DispatchQueue? = conf.Q.return, flags: DispatchWorkItemFlags? = nil, policy: CatchPolicy = conf.catchPolicy, _ body: @escaping(Error) -> Void) -> PMKFinalizer {
let finalizer = PMKFinalizer()
pipe {
switch $0 {
case .rejected(let error):
guard policy == .allErrors || !error.isCancelled else {
fallthrough
}
on.async(flags: flags) {
body(error)
finalizer.pending.resolve(())
}
case .fulfilled:
finalizer.pending.resolve(())
}
}
return finalizer
}
}
public class PMKFinalizer {
let pending = Guarantee<Void>.pending()
/// `finally` is the same as `ensure`, but it is not chainable
public func finally(on: DispatchQueue? = conf.Q.return, flags: DispatchWorkItemFlags? = nil, _ body: @escaping () -> Void) {
pending.guarantee.done(on: on, flags: flags) {
body()
}
}
}
public extension CatchMixin {
/**
The provided closure executes when this promise rejects.
Unlike `catch`, `recover` continues the chain.
Use `recover` in circumstances where recovering the chain from certain errors is a possibility. For example:
firstly {
CLLocationManager.requestLocation()
}.recover { error in
guard error == CLError.unknownLocation else { throw error }
return .value(CLLocation.chicago)
}
- Parameter on: The queue to which the provided closure dispatches.
- Parameter body: The handler to execute if this promise is rejected.
- SeeAlso: [Cancellation](https://github.com/mxcl/PromiseKit/blob/master/Documentation/CommonPatterns.md#cancellation)
*/
func recover<U: Thenable>(on: DispatchQueue? = conf.Q.map, flags: DispatchWorkItemFlags? = nil, policy: CatchPolicy = conf.catchPolicy, _ body: @escaping(Error) throws -> U) -> Promise<T> where U.T == T {
let rp = Promise<U.T>(.pending)
pipe {
switch $0 {
case .fulfilled(let value):
rp.box.seal(.fulfilled(value))
case .rejected(let error):
if policy == .allErrors || !error.isCancelled {
on.async(flags: flags) {
do {
let rv = try body(error)
guard rv !== rp else { throw PMKError.returnedSelf }
rv.pipe(to: rp.box.seal)
} catch {
rp.box.seal(.rejected(error))
}
}
} else {
rp.box.seal(.rejected(error))
}
}
}
return rp
}
/**
The provided closure executes when this promise rejects.
This variant of `recover` requires the handler to return a Guarantee, thus it returns a Guarantee itself and your closure cannot `throw`.
- Note it is logically impossible for this to take a `catchPolicy`, thus `allErrors` are handled.
- Parameter on: The queue to which the provided closure dispatches.
- Parameter body: The handler to execute if this promise is rejected.
- SeeAlso: [Cancellation](https://github.com/mxcl/PromiseKit/blob/master/Documentation/CommonPatterns.md#cancellation)
*/
@discardableResult
func recover(on: DispatchQueue? = conf.Q.map, flags: DispatchWorkItemFlags? = nil, _ body: @escaping(Error) -> Guarantee<T>) -> Guarantee<T> {
let rg = Guarantee<T>(.pending)
pipe {
switch $0 {
case .fulfilled(let value):
rg.box.seal(value)
case .rejected(let error):
on.async(flags: flags) {
body(error).pipe(to: rg.box.seal)
}
}
}
return rg
}
/**
The provided closure executes when this promise resolves, whether it rejects or not.
firstly {
UIApplication.shared.networkActivityIndicatorVisible = true
}.done {
//
}.ensure {
UIApplication.shared.networkActivityIndicatorVisible = false
}.catch {
//
}
- Parameter on: The queue to which the provided closure dispatches.
- Parameter body: The closure that executes when this promise resolves.
- Returns: A new promise, resolved with this promises resolution.
*/
func ensure(on: DispatchQueue? = conf.Q.return, flags: DispatchWorkItemFlags? = nil, _ body: @escaping () -> Void) -> Promise<T> {
let rp = Promise<T>(.pending)
pipe { result in
on.async(flags: flags) {
body()
rp.box.seal(result)
}
}
return rp
}
/**
The provided closure executes when this promise resolves, whether it rejects or not.
The chain waits on the returned `Guarantee<Void>`.
firstly {
setup()
}.done {
//
}.ensureThen {
teardown() // -> Guarante<Void>
}.catch {
//
}
- Parameter on: The queue to which the provided closure dispatches.
- Parameter body: The closure that executes when this promise resolves.
- Returns: A new promise, resolved with this promises resolution.
*/
func ensureThen(on: DispatchQueue? = conf.Q.return, flags: DispatchWorkItemFlags? = nil, _ body: @escaping () -> Guarantee<Void>) -> Promise<T> {
let rp = Promise<T>(.pending)
pipe { result in
on.async(flags: flags) {
body().done {
rp.box.seal(result)
}
}
}
return rp
}
/**
Consumes the Swift unused-result warning.
- Note: You should `catch`, but in situations where you know you dont need a `catch`, `cauterize` makes your intentions clear.
*/
@discardableResult
func cauterize() -> PMKFinalizer {
return self.catch {
conf.logHandler(.cauterized($0))
}
}
}
public extension CatchMixin where T == Void {
/**
The provided closure executes when this promise rejects.
This variant of `recover` is specialized for `Void` promises and de-errors your chain returning a `Guarantee`, thus you cannot `throw` and you must handle all errors including cancellation.
- Parameter on: The queue to which the provided closure dispatches.
- Parameter body: The handler to execute if this promise is rejected.
- SeeAlso: [Cancellation](https://github.com/mxcl/PromiseKit/blob/master/Documentation/CommonPatterns.md#cancellation)
*/
@discardableResult
func recover(on: DispatchQueue? = conf.Q.map, flags: DispatchWorkItemFlags? = nil, _ body: @escaping(Error) -> Void) -> Guarantee<Void> {
let rg = Guarantee<Void>(.pending)
pipe {
switch $0 {
case .fulfilled:
rg.box.seal(())
case .rejected(let error):
on.async(flags: flags) {
body(error)
rg.box.seal(())
}
}
}
return rg
}
/**
The provided closure executes when this promise rejects.
This variant of `recover` ensures that no error is thrown from the handler and allows specifying a catch policy.
- Parameter on: The queue to which the provided closure dispatches.
- Parameter body: The handler to execute if this promise is rejected.
- SeeAlso: [Cancellation](https://github.com/mxcl/PromiseKit/blob/master/Documentation/CommonPatterns.md#cancellation)
*/
func recover(on: DispatchQueue? = conf.Q.map, flags: DispatchWorkItemFlags? = nil, policy: CatchPolicy = conf.catchPolicy, _ body: @escaping(Error) throws -> Void) -> Promise<Void> {
let rg = Promise<Void>(.pending)
pipe {
switch $0 {
case .fulfilled:
rg.box.seal(.fulfilled(()))
case .rejected(let error):
if policy == .allErrors || !error.isCancelled {
on.async(flags: flags) {
do {
rg.box.seal(.fulfilled(try body(error)))
} catch {
rg.box.seal(.rejected(error))
}
}
} else {
rg.box.seal(.rejected(error))
}
}
}
return rg
}
}

View File

@ -0,0 +1,35 @@
import Dispatch
/**
PromiseKits configurable parameters.
Do not change these after any Promise machinery executes as the configuration object is not thread-safe.
We would like it to be, but sadly `Swift` does not expose `dispatch_once` et al. which is what we used to use in order to make the configuration immutable once first used.
*/
public struct PMKConfiguration {
/// The default queues that promises handlers dispatch to
public var Q: (map: DispatchQueue?, return: DispatchQueue?) = (map: DispatchQueue.main, return: DispatchQueue.main)
/// The default catch-policy for all `catch` and `resolve`
public var catchPolicy = CatchPolicy.allErrorsExceptCancellation
/// The closure used to log PromiseKit events.
/// Not thread safe; change before processing any promises.
/// - Note: The default handler calls `print()`
public var logHandler: (LogEvent) -> Void = { event in
switch event {
case .waitOnMainThread:
print("PromiseKit: warning: `wait()` called on main thread!")
case .pendingPromiseDeallocated:
print("PromiseKit: warning: pending promise deallocated")
case .pendingGuaranteeDeallocated:
print("PromiseKit: warning: pending guarantee deallocated")
case .cauterized (let error):
print("PromiseKit:cauterized-error: \(error)")
}
}
}
/// Modify this as soon as possible in your applications lifetime
public var conf = PMKConfiguration()

View File

@ -0,0 +1,44 @@
extension Promise: CustomStringConvertible {
/// - Returns: A description of the state of this promise.
public var description: String {
switch result {
case nil:
return "Promise(…\(T.self))"
case .rejected(let error)?:
return "Promise(\(error))"
case .fulfilled(let value)?:
return "Promise(\(value))"
}
}
}
extension Promise: CustomDebugStringConvertible {
/// - Returns: A debug-friendly description of the state of this promise.
public var debugDescription: String {
switch box.inspect() {
case .pending(let handlers):
return "Promise<\(T.self)>.pending(handlers: \(handlers.bodies.count))"
case .resolved(.rejected(let error)):
return "Promise<\(T.self)>.rejected(\(type(of: error)).\(error))"
case .resolved(.fulfilled(let value)):
return "Promise<\(T.self)>.fulfilled(\(value))"
}
}
}
#if !SWIFT_PACKAGE
extension AnyPromise {
/// - Returns: A description of the state of this promise.
override open var description: String {
switch box.inspect() {
case .pending:
return "AnyPromise(…)"
case .resolved(let obj?):
return "AnyPromise(\(obj))"
case .resolved(nil):
return "AnyPromise(nil)"
}
}
}
#endif

View File

@ -0,0 +1,93 @@
import Dispatch
@available(*, deprecated, message: "See `init(resolver:)`")
public func wrap<T>(_ body: (@escaping (T?, Error?) -> Void) throws -> Void) -> Promise<T> {
return Promise { seal in
try body(seal.resolve)
}
}
@available(*, deprecated, message: "See `init(resolver:)`")
public func wrap<T>(_ body: (@escaping (T, Error?) -> Void) throws -> Void) -> Promise<T> {
return Promise { seal in
try body(seal.resolve)
}
}
@available(*, deprecated, message: "See `init(resolver:)`")
public func wrap<T>(_ body: (@escaping (Error?, T?) -> Void) throws -> Void) -> Promise<T> {
return Promise { seal in
try body(seal.resolve)
}
}
@available(*, deprecated, message: "See `init(resolver:)`")
public func wrap(_ body: (@escaping (Error?) -> Void) throws -> Void) -> Promise<Void> {
return Promise { seal in
try body(seal.resolve)
}
}
@available(*, deprecated, message: "See `init(resolver:)`")
public func wrap<T>(_ body: (@escaping (T) -> Void) throws -> Void) -> Promise<T> {
return Promise { seal in
try body(seal.fulfill)
}
}
public extension Promise {
@available(*, deprecated, message: "See `ensure`")
func always(on q: DispatchQueue = .main, execute body: @escaping () -> Void) -> Promise {
return ensure(on: q, body)
}
}
public extension Thenable {
#if PMKFullDeprecations
/// disabled due to ambiguity with the other `.flatMap`
@available(*, deprecated, message: "See: `compactMap`")
func flatMap<U>(on: DispatchQueue? = conf.Q.map, _ transform: @escaping(T) throws -> U?) -> Promise<U> {
return compactMap(on: on, transform)
}
#endif
}
public extension Thenable where T: Sequence {
#if PMKFullDeprecations
/// disabled due to ambiguity with the other `.map`
@available(*, deprecated, message: "See: `mapValues`")
func map<U>(on: DispatchQueue? = conf.Q.map, _ transform: @escaping(T.Iterator.Element) throws -> U) -> Promise<[U]> {
return mapValues(on: on, transform)
}
/// disabled due to ambiguity with the other `.flatMap`
@available(*, deprecated, message: "See: `flatMapValues`")
func flatMap<U: Sequence>(on: DispatchQueue? = conf.Q.map, _ transform: @escaping(T.Iterator.Element) throws -> U) -> Promise<[U.Iterator.Element]> {
return flatMapValues(on: on, transform)
}
#endif
@available(*, deprecated, message: "See: `filterValues`")
func filter(on: DispatchQueue? = conf.Q.map, test: @escaping (T.Iterator.Element) -> Bool) -> Promise<[T.Iterator.Element]> {
return filterValues(on: on, test)
}
}
public extension Thenable where T: Collection {
@available(*, deprecated, message: "See: `firstValue`")
var first: Promise<T.Iterator.Element> {
return firstValue
}
@available(*, deprecated, message: "See: `lastValue`")
var last: Promise<T.Iterator.Element> {
return lastValue
}
}
public extension Thenable where T: Sequence, T.Iterator.Element: Comparable {
@available(*, deprecated, message: "See: `sortedValues`")
func sorted(on: DispatchQueue? = conf.Q.map) -> Promise<[T.Iterator.Element]> {
return sortedValues(on: on)
}
}

View File

@ -1,53 +0,0 @@
import Dispatch
extension DispatchQueue {
/**
Submits a block for asynchronous execution on a dispatch queue.
DispatchQueue.global().promise {
try md5(input)
}.then { md5 in
//
}
- Parameter body: The closure that resolves this promise.
- Returns: A new promise resolved by the result of the provided closure.
- SeeAlso: `DispatchQueue.async(group:qos:flags:execute:)`
- SeeAlso: `dispatch_promise()`
- SeeAlso: `dispatch_promise_on()`
*/
public final func promise<T>(group: DispatchGroup? = nil, qos: DispatchQoS = .default, flags: DispatchWorkItemFlags = [], execute body: @escaping () throws -> T) -> Promise<T> {
return Promise(sealant: { resolve in
async(group: group, qos: qos, flags: flags) {
do {
resolve(.fulfilled(try body()))
} catch {
resolve(Resolution(error))
}
}
})
}
/// Unavailable due to Swift compiler issues
@available(*, unavailable)
public final func promise<T>(group: DispatchGroup? = nil, qos: DispatchQoS = .default, flags: DispatchWorkItemFlags = [], execute body: () throws -> Promise<T>) -> Promise<T> { fatalError() }
/**
The default queue for all handlers.
Defaults to `DispatchQueue.main`.
- SeeAlso: `PMKDefaultDispatchQueue()`
- SeeAlso: `PMKSetDefaultDispatchQueue()`
*/
class public final var `default`: DispatchQueue {
get {
return __PMKDefaultDispatchQueue()
}
set {
__PMKSetDefaultDispatchQueue(newValue)
}
}
}

View File

@ -2,14 +2,7 @@ import Foundation
public enum PMKError: Error {
/**
The ErrorType for a rejected `join`.
- Parameter 0: The promises passed to this `join` that did not *all* fulfill.
- Note: The array is untyped because Swift generics are fussy with enums.
*/
case join([AnyObject])
/**
The completionHandler with form (T?, ErrorType?) was called with (nil, nil)
The completionHandler with form `(T?, Error?)` was called with `(nil, nil)`.
This is invalid as per Cocoa/Apple calling conventions.
*/
case invalidCallingConvention
@ -20,153 +13,92 @@ public enum PMKError: Error {
*/
case returnedSelf
/** `when()` was called with a concurrency of <= 0 */
case whenConcurrentlyZero
/** `when()`, `race()` etc. were called with invalid parameters, eg. an empty array. */
case badInput
/** AnyPromise.toPromise failed to cast as requested */
case castError(Any.Type)
/// The operation was cancelled
case cancelled
/// `nil` was returned from `flatMap`
@available(*, deprecated, message: "See: `compactMap`")
case flatMap(Any, Any.Type)
/// `nil` was returned from `compactMap`
case compactMap(Any, Any.Type)
/**
The lastValue or firstValue of a sequence was requested but the sequence was empty.
Also used if all values of this collection failed the test passed to `firstValue(where:)`.
*/
case emptySequence
}
public enum PMKURLError: Error {
/**
The URLRequest succeeded but a valid UIImage could not be decoded from
the data that was received.
*/
case invalidImageData(URLRequest, Data)
/**
The HTTP request returned a non-200 status code.
*/
case badResponse(URLRequest, Data?, URLResponse?)
/**
The data could not be decoded using the encoding specified by the HTTP
response headers.
*/
case stringEncoding(URLRequest, Data, URLResponse)
/**
Usually the `URLResponse` is actually an `HTTPURLResponse`, if so you
can access it using this property. Since it is returned as an unwrapped
optional: be sure.
*/
public var NSHTTPURLResponse: Foundation.HTTPURLResponse! {
extension PMKError: CustomDebugStringConvertible {
public var debugDescription: String {
switch self {
case .invalidImageData:
return nil
case .badResponse(_, _, let rsp):
return rsp as! Foundation.HTTPURLResponse
case .stringEncoding(_, _, let rsp):
return rsp as! Foundation.HTTPURLResponse
case .flatMap(let obj, let type):
return "Could not `flatMap<\(type)>`: \(obj)"
case .compactMap(let obj, let type):
return "Could not `compactMap<\(type)>`: \(obj)"
case .invalidCallingConvention:
return "A closure was called with an invalid calling convention, probably (nil, nil)"
case .returnedSelf:
return "A promise handler returned itself"
case .badInput:
return "Bad input was provided to a PromiseKit function"
case .cancelled:
return "The asynchronous sequence was cancelled"
case .emptySequence:
return "The first or last element was requested for an empty sequence"
}
}
}
extension PMKURLError: CustomStringConvertible {
public var description: String {
switch self {
case let .badResponse(rq, data, rsp):
if let data = data, let str = String(data: data, encoding: .utf8), let rsp = rsp {
return "PromiseKit: badResponse: \(rq): \(rsp)\n\(str)"
} else {
fallthrough
}
default:
return "\(self)"
}
extension PMKError: LocalizedError {
public var errorDescription: String? {
return debugDescription
}
}
public enum JSONError: Error {
/// The JSON response was different to that requested
case unexpectedRootNode(Any)
}
//////////////////////////////////////////////////////////// Cancellation
/// An error that may represent the cancelled condition
public protocol CancellableError: Error {
/// returns true if this Error represents a cancelled condition
var isCancelled: Bool { get }
}
#if !SWIFT_PACKAGE
private struct ErrorPair: Hashable {
let domain: String
let code: Int
init(_ d: String, _ c: Int) {
domain = d; code = c
}
var hashValue: Int {
return "\(domain):\(code)".hashValue
}
}
private func ==(lhs: ErrorPair, rhs: ErrorPair) -> Bool {
return lhs.domain == rhs.domain && lhs.code == rhs.code
}
extension NSError {
@objc public class func cancelledError() -> NSError {
let info = [NSLocalizedDescriptionKey: "The operation was cancelled"]
return NSError(domain: PMKErrorDomain, code: PMKOperationCancelled, userInfo: info)
}
/**
- Warning: You must call this method before any promises in your application are rejected. Failure to ensure this may lead to concurrency crashes.
- Warning: You must call this method on the main thread. Failure to do this may lead to concurrency crashes.
*/
@objc public class func registerCancelledErrorDomain(_ domain: String, code: Int) {
cancelledErrorIdentifiers.insert(ErrorPair(domain, code))
}
/// - Returns: true if the error represents cancellation.
@objc public var isCancelled: Bool {
return (self as Error).isCancelledError
}
}
private var cancelledErrorIdentifiers = Set([
ErrorPair(PMKErrorDomain, PMKOperationCancelled),
ErrorPair(NSCocoaErrorDomain, NSUserCancelledError),
ErrorPair(NSURLErrorDomain, NSURLErrorCancelled),
])
#endif
extension Error {
public var isCancelledError: Bool {
if let ce = self as? CancellableError {
return ce.isCancelled
} else {
#if SWIFT_PACKAGE
public var isCancelled: Bool {
do {
throw self
} catch PMKError.cancelled {
return true
} catch let error as CancellableError {
return error.isCancelled
} catch URLError.cancelled {
return true
} catch CocoaError.userCancelled {
return true
} catch {
#if canImport(StoreKit)
let domain = (error as AnyObject).value(forKey: "domain") as? String
let code = (error as AnyObject).value(forKey: "code") as? Int
return ("SKErrorDomain", 2) == (domain, code)
#else
return false
#else
let ne = self as NSError
return cancelledErrorIdentifiers.contains(ErrorPair(ne.domain, ne.code))
#endif
#endif
}
}
}
/// Used by `catch` and `recover`
public enum CatchPolicy {
/// Indicates that `catch` or `recover` handle all error types including cancellable-errors.
case allErrors
//////////////////////////////////////////////////////// Unhandled Errors
class ErrorConsumptionToken {
var consumed = false
let error: Error
init(_ error: Error) {
self.error = error
}
deinit {
if !consumed {
#if os(Linux) || os(Android)
PMKUnhandledErrorHandler(error)
#else
PMKUnhandledErrorHandler(error as NSError)
#endif
}
}
/// Indicates that `catch` or `recover` handle all error except cancellable-errors.
case allErrorsExceptCancellation
}

View File

@ -1,76 +0,0 @@
#import "PromiseKit.h"
@interface NSError (PMK)
- (BOOL)isCancelled;
@end
static dispatch_once_t __PMKDefaultDispatchQueueToken;
static dispatch_queue_t __PMKDefaultDispatchQueue;
dispatch_queue_t PMKDefaultDispatchQueue() {
dispatch_once(&__PMKDefaultDispatchQueueToken, ^{
if (__PMKDefaultDispatchQueue == nil) {
__PMKDefaultDispatchQueue = dispatch_get_main_queue();
}
});
return __PMKDefaultDispatchQueue;
}
void PMKSetDefaultDispatchQueue(dispatch_queue_t newDefaultQueue) {
dispatch_once(&__PMKDefaultDispatchQueueToken, ^{
__PMKDefaultDispatchQueue = newDefaultQueue;
});
}
static dispatch_once_t __PMKErrorUnhandlerToken;
static void (^__PMKErrorUnhandler)(NSError *);
void PMKUnhandledErrorHandler(NSError *error) {
dispatch_once(&__PMKErrorUnhandlerToken, ^{
if (__PMKErrorUnhandler == nil) {
__PMKErrorUnhandler = ^(NSError *error){
if (!error.isCancelled) {
NSLog(@"PromiseKit: unhandled error: %@", error);
}
};
}
});
return __PMKErrorUnhandler(error);
}
void PMKSetUnhandledErrorHandler(void(^newHandler)(NSError *)) {
dispatch_once(&__PMKErrorUnhandlerToken, ^{
__PMKErrorUnhandler = newHandler;
});
}
static dispatch_once_t __PMKUnhandledExceptionHandlerToken;
static NSError *(^__PMKUnhandledExceptionHandler)(id);
NSError *PMKProcessUnhandledException(id thrown) {
dispatch_once(&__PMKUnhandledExceptionHandlerToken, ^{
__PMKUnhandledExceptionHandler = ^id(id reason){
if ([reason isKindOfClass:[NSError class]])
return reason;
if ([reason isKindOfClass:[NSString class]])
return [NSError errorWithDomain:PMKErrorDomain code:PMKUnexpectedError userInfo:@{NSLocalizedDescriptionKey: reason}];
return nil;
};
});
id err = __PMKUnhandledExceptionHandler(thrown);
if (!err) {
NSLog(@"PromiseKit no longer catches *all* exceptions. However you can change this behavior by setting a new PMKProcessUnhandledException handler.");
@throw thrown;
}
return err;
}
void PMKSetUnhandledExceptionHandler(NSError *(^newHandler)(id)) {
dispatch_once(&__PMKUnhandledExceptionHandlerToken, ^{
__PMKUnhandledExceptionHandler = newHandler;
});
}

390
Sources/Guarantee.swift Normal file
View File

@ -0,0 +1,390 @@
import class Foundation.Thread
import Dispatch
/**
A `Guarantee` is a functional abstraction around an asynchronous operation that cannot error.
- See: `Thenable`
*/
public final class Guarantee<T>: Thenable {
let box: PromiseKit.Box<T>
fileprivate init(box: SealedBox<T>) {
self.box = box
}
/// Returns a `Guarantee` sealed with the provided value.
public static func value(_ value: T) -> Guarantee<T> {
return .init(box: SealedBox(value: value))
}
/// Returns a pending `Guarantee` that can be resolved with the provided closures parameter.
public init(resolver body: (@escaping(T) -> Void) -> Void) {
box = Box()
body(box.seal)
}
/// - See: `Thenable.pipe`
public func pipe(to: @escaping(Result<T>) -> Void) {
pipe{ to(.fulfilled($0)) }
}
func pipe(to: @escaping(T) -> Void) {
switch box.inspect() {
case .pending:
box.inspect {
switch $0 {
case .pending(let handlers):
handlers.append(to)
case .resolved(let value):
to(value)
}
}
case .resolved(let value):
to(value)
}
}
/// - See: `Thenable.result`
public var result: Result<T>? {
switch box.inspect() {
case .pending:
return nil
case .resolved(let value):
return .fulfilled(value)
}
}
final private class Box<T>: EmptyBox<T> {
deinit {
switch inspect() {
case .pending:
PromiseKit.conf.logHandler(.pendingGuaranteeDeallocated)
case .resolved:
break
}
}
}
init(_: PMKUnambiguousInitializer) {
box = Box()
}
/// Returns a tuple of a pending `Guarantee` and a function that resolves it.
public class func pending() -> (guarantee: Guarantee<T>, resolve: (T) -> Void) {
return { ($0, $0.box.seal) }(Guarantee<T>(.pending))
}
}
public extension Guarantee {
@discardableResult
func done(on: DispatchQueue? = conf.Q.return, flags: DispatchWorkItemFlags? = nil, _ body: @escaping(T) -> Void) -> Guarantee<Void> {
let rg = Guarantee<Void>(.pending)
pipe { (value: T) in
on.async(flags: flags) {
body(value)
rg.box.seal(())
}
}
return rg
}
func get(on: DispatchQueue? = conf.Q.return, flags: DispatchWorkItemFlags? = nil, _ body: @escaping (T) -> Void) -> Guarantee<T> {
return map(on: on, flags: flags) {
body($0)
return $0
}
}
func map<U>(on: DispatchQueue? = conf.Q.map, flags: DispatchWorkItemFlags? = nil, _ body: @escaping(T) -> U) -> Guarantee<U> {
let rg = Guarantee<U>(.pending)
pipe { value in
on.async(flags: flags) {
rg.box.seal(body(value))
}
}
return rg
}
#if swift(>=4) && !swift(>=5.2)
func map<U>(on: DispatchQueue? = conf.Q.map, flags: DispatchWorkItemFlags? = nil, _ keyPath: KeyPath<T, U>) -> Guarantee<U> {
let rg = Guarantee<U>(.pending)
pipe { value in
on.async(flags: flags) {
rg.box.seal(value[keyPath: keyPath])
}
}
return rg
}
#endif
@discardableResult
func then<U>(on: DispatchQueue? = conf.Q.map, flags: DispatchWorkItemFlags? = nil, _ body: @escaping(T) -> Guarantee<U>) -> Guarantee<U> {
let rg = Guarantee<U>(.pending)
pipe { value in
on.async(flags: flags) {
body(value).pipe(to: rg.box.seal)
}
}
return rg
}
func asVoid() -> Guarantee<Void> {
return map(on: nil) { _ in }
}
/**
Blocks this thread, so you know, dont call this on a serial thread that
any part of your chain may use. Like the main thread for example.
*/
func wait() -> T {
if Thread.isMainThread {
conf.logHandler(.waitOnMainThread)
}
var result = value
if result == nil {
let group = DispatchGroup()
group.enter()
pipe { (foo: T) in result = foo; group.leave() }
group.wait()
}
return result!
}
}
public extension Guarantee where T: Sequence {
/**
`Guarantee<[T]>` => `T` -> `U` => `Guarantee<[U]>`
Guarantee.value([1,2,3])
.mapValues { integer in integer * 2 }
.done {
// $0 => [2,4,6]
}
*/
func mapValues<U>(on: DispatchQueue? = conf.Q.map, flags: DispatchWorkItemFlags? = nil, _ transform: @escaping(T.Iterator.Element) -> U) -> Guarantee<[U]> {
return map(on: on, flags: flags) { $0.map(transform) }
}
#if swift(>=4) && !swift(>=5.2)
/**
`Guarantee<[T]>` => `KeyPath<T, U>` => `Guarantee<[U]>`
Guarantee.value([Person(name: "Max"), Person(name: "Roman"), Person(name: "John")])
.mapValues(\.name)
.done {
// $0 => ["Max", "Roman", "John"]
}
*/
func mapValues<U>(on: DispatchQueue? = conf.Q.map, flags: DispatchWorkItemFlags? = nil, _ keyPath: KeyPath<T.Iterator.Element, U>) -> Guarantee<[U]> {
return map(on: on, flags: flags) { $0.map { $0[keyPath: keyPath] } }
}
#endif
/**
`Guarantee<[T]>` => `T` -> `[U]` => `Guarantee<[U]>`
Guarantee.value([1,2,3])
.flatMapValues { integer in [integer, integer] }
.done {
// $0 => [1,1,2,2,3,3]
}
*/
func flatMapValues<U: Sequence>(on: DispatchQueue? = conf.Q.map, flags: DispatchWorkItemFlags? = nil, _ transform: @escaping(T.Iterator.Element) -> U) -> Guarantee<[U.Iterator.Element]> {
return map(on: on, flags: flags) { (foo: T) in
foo.flatMap { transform($0) }
}
}
/**
`Guarantee<[T]>` => `T` -> `U?` => `Guarantee<[U]>`
Guarantee.value(["1","2","a","3"])
.compactMapValues { Int($0) }
.done {
// $0 => [1,2,3]
}
*/
func compactMapValues<U>(on: DispatchQueue? = conf.Q.map, flags: DispatchWorkItemFlags? = nil, _ transform: @escaping(T.Iterator.Element) -> U?) -> Guarantee<[U]> {
return map(on: on, flags: flags) { foo -> [U] in
#if !swift(>=3.3) || (swift(>=4) && !swift(>=4.1))
return foo.flatMap(transform)
#else
return foo.compactMap(transform)
#endif
}
}
#if swift(>=4) && !swift(>=5.2)
/**
`Guarantee<[T]>` => `KeyPath<T, U?>` => `Guarantee<[U]>`
Guarantee.value([Person(name: "Max"), Person(name: "Roman", age: 26), Person(name: "John", age: 23)])
.compactMapValues(\.age)
.done {
// $0 => [26, 23]
}
*/
func compactMapValues<U>(on: DispatchQueue? = conf.Q.map, flags: DispatchWorkItemFlags? = nil, _ keyPath: KeyPath<T.Iterator.Element, U?>) -> Guarantee<[U]> {
return map(on: on, flags: flags) { foo -> [U] in
#if !swift(>=4.1)
return foo.flatMap { $0[keyPath: keyPath] }
#else
return foo.compactMap { $0[keyPath: keyPath] }
#endif
}
}
#endif
/**
`Guarantee<[T]>` => `T` -> `Guarantee<U>` => `Guaranetee<[U]>`
Guarantee.value([1,2,3])
.thenMap { .value($0 * 2) }
.done {
// $0 => [2,4,6]
}
*/
func thenMap<U>(on: DispatchQueue? = conf.Q.map, flags: DispatchWorkItemFlags? = nil, _ transform: @escaping(T.Iterator.Element) -> Guarantee<U>) -> Guarantee<[U]> {
return then(on: on, flags: flags) {
when(fulfilled: $0.map(transform))
}.recover {
// if happens then is bug inside PromiseKit
fatalError(String(describing: $0))
}
}
/**
`Guarantee<[T]>` => `T` -> `Guarantee<[U]>` => `Guarantee<[U]>`
Guarantee.value([1,2,3])
.thenFlatMap { integer in .value([integer, integer]) }
.done {
// $0 => [1,1,2,2,3,3]
}
*/
func thenFlatMap<U: Thenable>(on: DispatchQueue? = conf.Q.map, flags: DispatchWorkItemFlags? = nil, _ transform: @escaping(T.Iterator.Element) -> U) -> Guarantee<[U.T.Iterator.Element]> where U.T: Sequence {
return then(on: on, flags: flags) {
when(fulfilled: $0.map(transform))
}.map(on: nil) {
$0.flatMap { $0 }
}.recover {
// if happens then is bug inside PromiseKit
fatalError(String(describing: $0))
}
}
/**
`Guarantee<[T]>` => `T` -> Bool => `Guarantee<[T]>`
Guarantee.value([1,2,3])
.filterValues { $0 > 1 }
.done {
// $0 => [2,3]
}
*/
func filterValues(on: DispatchQueue? = conf.Q.map, flags: DispatchWorkItemFlags? = nil, _ isIncluded: @escaping(T.Iterator.Element) -> Bool) -> Guarantee<[T.Iterator.Element]> {
return map(on: on, flags: flags) {
$0.filter(isIncluded)
}
}
#if swift(>=4) && !swift(>=5.2)
/**
`Guarantee<[T]>` => `KeyPath<T, Bool>` => `Guarantee<[T]>`
Guarantee.value([Person(name: "Max"), Person(name: "Roman", age: 26, isStudent: false), Person(name: "John", age: 23, isStudent: true)])
.filterValues(\.isStudent)
.done {
// $0 => [Person(name: "John", age: 23, isStudent: true)]
}
*/
func filterValues(on: DispatchQueue? = conf.Q.map, flags: DispatchWorkItemFlags? = nil, _ keyPath: KeyPath<T.Iterator.Element, Bool>) -> Guarantee<[T.Iterator.Element]> {
return map(on: on, flags: flags) {
$0.filter { $0[keyPath: keyPath] }
}
}
#endif
/**
`Guarantee<[T]>` => (`T`, `T`) -> Bool => `Guarantee<[T]>`
Guarantee.value([5,2,3,4,1])
.sortedValues { $0 > $1 }
.done {
// $0 => [5,4,3,2,1]
}
*/
func sortedValues(on: DispatchQueue? = conf.Q.map, flags: DispatchWorkItemFlags? = nil, _ areInIncreasingOrder: @escaping(T.Iterator.Element, T.Iterator.Element) -> Bool) -> Guarantee<[T.Iterator.Element]> {
return map(on: on, flags: flags) {
$0.sorted(by: areInIncreasingOrder)
}
}
}
public extension Guarantee where T: Sequence, T.Iterator.Element: Comparable {
/**
`Guarantee<[T]>` => `Guarantee<[T]>`
Guarantee.value([5,2,3,4,1])
.sortedValues()
.done {
// $0 => [1,2,3,4,5]
}
*/
func sortedValues(on: DispatchQueue? = conf.Q.map, flags: DispatchWorkItemFlags? = nil) -> Guarantee<[T.Iterator.Element]> {
return map(on: on, flags: flags) { $0.sorted() }
}
}
#if swift(>=3.1)
public extension Guarantee where T == Void {
convenience init() {
self.init(box: SealedBox(value: Void()))
}
static var value: Guarantee<Void> {
return .value(Void())
}
}
#endif
public extension DispatchQueue {
/**
Asynchronously executes the provided closure on a dispatch queue.
DispatchQueue.global().async(.promise) {
md5(input)
}.done { md5 in
//
}
- Parameter body: The closure that resolves this promise.
- Returns: A new `Guarantee` resolved by the result of the provided closure.
- Note: There is no Promise/Thenable version of this due to Swift compiler ambiguity issues.
*/
@available(macOS 10.10, iOS 2.0, tvOS 10.0, watchOS 2.0, *)
final func async<T>(_: PMKNamespacer, group: DispatchGroup? = nil, qos: DispatchQoS = .default, flags: DispatchWorkItemFlags = [], execute body: @escaping () -> T) -> Guarantee<T> {
let rg = Guarantee<T>(.pending)
async(group: group, qos: qos, flags: flags) {
rg.box.seal(body())
}
return rg
}
}
#if os(Linux)
import func CoreFoundation._CFIsMainThread
extension Thread {
// `isMainThread` is not implemented yet in swift-corelibs-foundation.
static var isMainThread: Bool {
return _CFIsMainThread()
}
}
#endif

30
Sources/LogEvent.swift Normal file
View File

@ -0,0 +1,30 @@
/**
The PromiseKit events which may be logged.
````
/// A promise or guarantee has blocked the main thread
case waitOnMainThread
/// A promise has been deallocated without being resolved
case pendingPromiseDeallocated
/// An error which occurred while fulfilling a promise was swallowed
case cauterized(Error)
/// Errors which give a string error message
case misc (String)
````
*/
public enum LogEvent {
/// A promise or guarantee has blocked the main thread
case waitOnMainThread
/// A promise has been deallocated without being resolved
case pendingPromiseDeallocated
/// A guarantee has been deallocated without being resolved
case pendingGuaranteeDeallocated
/// An error which occurred while resolving a promise was swallowed
case cauterized(Error)
}

View File

@ -109,6 +109,12 @@ static id PMKCallVariadicBlock(id frock, id result) {
@try {
return _PMKCallVariadicBlock(frock, result);
} @catch (id thrown) {
return PMKProcessUnhandledException(thrown);
if ([thrown isKindOfClass:[NSString class]])
return thrown;
if ([thrown isKindOfClass:[NSError class]])
return thrown;
// we dont catch objc exceptions: they are meant to crash your app
@throw thrown;
}
}

View File

@ -1,41 +0,0 @@
import class Dispatch.DispatchQueue
extension Promise {
/**
The provided closure executes once this promise resolves.
- Parameter on: The queue on which the provided closure executes.
- Parameter body: The closure that is executed when this promise fulfills.
- Returns: A new promise that resolves when the `AnyPromise` returned from the provided closure resolves. For example:
URLSession.GET(url).then { (data: NSData) -> AnyPromise in
//
return SCNetworkReachability()
}.then { _ in
//
}
*/
public func then(on q: DispatchQueue = .default, execute body: @escaping (T) throws -> AnyPromise) -> Promise<Any?> {
return Promise<Any?>(sealant: { resolve in
state.then(on: q, else: resolve) { value in
try body(value).state.pipe(resolve)
}
})
}
@available(*, unavailable, message: "unwrap the promise")
public func then(on: DispatchQueue = .default, execute body: (T) throws -> AnyPromise?) -> Promise<AnyObject?> { fatalError() }
}
/**
`firstly` can make chains more readable.
*/
public func firstly(execute body: () throws -> AnyPromise) -> Promise<Any?> {
return Promise(sealant: { resolve in
do {
try body().state.pipe(resolve)
} catch {
resolve(Resolution(error))
}
})
}

View File

@ -1,57 +0,0 @@
extension Promise {
/**
- Returns: The error with which this promise was rejected; `nil` if this promise is not rejected.
*/
public var error: Error? {
switch state.get() {
case .none:
return nil
case .some(.fulfilled):
return nil
case .some(.rejected(let error, _)):
return error
}
}
/**
- Returns: `true` if the promise has not yet resolved.
*/
public var isPending: Bool {
return state.get() == nil
}
/**
- Returns: `true` if the promise has resolved.
*/
public var isResolved: Bool {
return !isPending
}
/**
- Returns: `true` if the promise was fulfilled.
*/
public var isFulfilled: Bool {
return value != nil
}
/**
- Returns: `true` if the promise was rejected.
*/
public var isRejected: Bool {
return error != nil
}
/**
- Returns: The value with which this promise was fulfilled or `nil` if this promise is pending or rejected.
*/
public var value: T? {
switch state.get() {
case .none:
return nil
case .some(.fulfilled(let value)):
return value
case .some(.rejected):
return nil
}
}
}

View File

@ -1,637 +1,184 @@
import class Dispatch.DispatchQueue
import class Foundation.NSError
import func Foundation.NSLog
import class Foundation.Thread
import Dispatch
/**
A *promise* represents the future value of a (usually) asynchronous task.
To obtain the value of a promise we call `then`.
Promises are chainable: `then` returns a promise, you can call `then` on
that promise, which returns a promise, you can call `then` on that
promise, et cetera.
Promises start in a pending state and *resolve* with a value to become
*fulfilled* or an `Error` to become rejected.
- SeeAlso: [PromiseKit `then` Guide](http://promisekit.org/docs/)
A `Promise` is a functional abstraction around a failable asynchronous operation.
- See: `Thenable`
*/
open class Promise<T> {
let state: State<T>
public final class Promise<T>: Thenable, CatchMixin {
let box: Box<Result<T>>
fileprivate init(box: SealedBox<Result<T>>) {
self.box = box
}
/**
Create a new, pending promise.
Initialize a new fulfilled promise.
func fetchAvatar(user: String) -> Promise<UIImage> {
return Promise { fulfill, reject in
MyWebHelper.GET("\(user)/avatar") { data, err in
guard let data = data else { return reject(err) }
guard let img = UIImage(data: data) else { return reject(MyError.InvalidImage) }
guard let img.size.width > 0 else { return reject(MyError.ImageTooSmall) }
fulfill(img)
}
}
}
We do not provide `init(value:)` because Swift is greedy
and would pick that initializer in cases where it should pick
one of the other more specific options leading to Promises with
`T` that is eg: `Error` or worse `(T->Void,Error->Void)` for
uses of our PMK < 4 pending initializer due to Swift trailing
closure syntax (nothing good comes without pain!).
- Parameter resolvers: The provided closure is called immediately on the active thread; commence your asynchronous task, calling either fulfill or reject when it completes.
- Parameter fulfill: Fulfills this promise with the provided value.
- Parameter reject: Rejects this promise with the provided error.
Though often easy to detect, sometimes these issues would be
hidden by other type inference leading to some nasty bugs in
production.
- Returns: A new promise.
In PMK5 we tried to work around this by making the pending
initializer take the form `Promise(.pending)` but this led to
bad migration errors for PMK4 users. Hence instead we quickly
released PMK6 and now only provide this initializer for making
sealed & fulfilled promises.
- Note: If you are wrapping a delegate-based system, we recommend
to use instead: `Promise.pending()`
Usage is still (usually) good:
- SeeAlso: http://promisekit.org/docs/sealing-promises/
- SeeAlso: http://promisekit.org/docs/cookbook/wrapping-delegation/
- SeeAlso: pending()
guard foo else {
return .value(bar)
}
*/
required public init(resolvers: (_ fulfill: @escaping (T) -> Void, _ reject: @escaping (Error) -> Void) throws -> Void) {
var resolve: ((Resolution<T>) -> Void)!
public static func value(_ value: T) -> Promise<T> {
return Promise(box: SealedBox(value: .fulfilled(value)))
}
/// Initialize a new rejected promise.
public init(error: Error) {
box = SealedBox(value: .rejected(error))
}
/// Initialize a new promise bound to the provided `Thenable`.
public init<U: Thenable>(_ bridge: U) where U.T == T {
box = EmptyBox()
bridge.pipe(to: box.seal)
}
/// Initialize a new promise that can be resolved with the provided `Resolver`.
public init(resolver body: (Resolver<T>) throws -> Void) {
box = EmptyBox()
let resolver = Resolver(box)
do {
state = UnsealedState(resolver: &resolve)
try resolvers({ resolve(.fulfilled($0)) }, { error in
#if !PMKDisableWarnings
if self.isPending {
resolve(Resolution(error))
} else {
NSLog("PromiseKit: warning: reject called on already rejected Promise: \(error)")
}
#else
resolve(Resolution(error))
#endif
})
try body(resolver)
} catch {
resolve(Resolution(error))
resolver.reject(error)
}
}
/**
Create an already fulfilled promise.
To create a resolved `Void` promise, do: `Promise(value: ())`
*/
required public init(value: T) {
state = SealedState(resolution: .fulfilled(value))
/// - Returns: a tuple of a new pending promise and its `Resolver`.
public class func pending() -> (promise: Promise<T>, resolver: Resolver<T>) {
return { ($0, Resolver($0.box)) }(Promise<T>(.pending))
}
/**
Create an already rejected promise.
*/
required public init(error: Error) {
state = SealedState(resolution: Resolution(error))
}
/**
Careful with this, it is imperative that sealant can only be called once
or you will end up with spurious unhandled-errors due to possible double
rejections and thus immediately deallocated ErrorConsumptionTokens.
*/
init(sealant: (@escaping (Resolution<T>) -> Void) -> Void) {
var resolve: ((Resolution<T>) -> Void)!
state = UnsealedState(resolver: &resolve)
sealant(resolve)
}
/**
A `typealias` for the return values of `pending()`. Simplifies declaration of properties that reference the values' containing tuple when this is necessary. For example, when working with multiple `pendingPromise(value: ())`s within the same scope, or when the promise initialization must occur outside of the caller's initialization.
class Foo: BarDelegate {
var task: Promise<Int>.PendingTuple?
}
- SeeAlso: pending()
*/
public typealias PendingTuple = (promise: Promise, fulfill: (T) -> Void, reject: (Error) -> Void)
/**
Making promises that wrap asynchronous delegation systems or other larger asynchronous systems without a simple completion handler is easier with pending.
class Foo: BarDelegate {
let (promise, fulfill, reject) = Promise<Int>.pending()
func barDidFinishWithResult(result: Int) {
fulfill(result)
}
func barDidError(error: NSError) {
reject(error)
}
}
- Returns: A tuple consisting of:
1) A promise
2) A function that fulfills that promise
3) A function that rejects that promise
*/
public final class func pending() -> PendingTuple {
var fulfill: ((T) -> Void)!
var reject: ((Error) -> Void)!
let promise = self.init { fulfill = $0; reject = $1 }
return (promise, fulfill, reject)
}
/**
The provided closure is executed when this promise is resolved.
- Parameter on: The queue to which the provided closure dispatches.
- Parameter body: The closure that is executed when this Promise is fulfilled.
- Returns: A new promise that is resolved with the value returned from the provided closure. For example:
URLSession.GET(url).then { data -> Int in
//
return data.length
}.then { length in
//
}
*/
public func then<U>(on q: DispatchQueue = .default, execute body: @escaping (T) throws -> U) -> Promise<U> {
return Promise<U> { resolve in
state.then(on: q, else: resolve) { value in
resolve(.fulfilled(try body(value)))
/// - See: `Thenable.pipe`
public func pipe(to: @escaping(Result<T>) -> Void) {
switch box.inspect() {
case .pending:
box.inspect {
switch $0 {
case .pending(let handlers):
handlers.append(to)
case .resolved(let value):
to(value)
}
}
case .resolved(let value):
to(value)
}
}
/**
The provided closure executes when this promise resolves.
This variant of `then` allows chaining promises, the promise returned by the provided closure is resolved before the promise returned by this closure resolves.
- Parameter on: The queue to which the provided closure dispatches.
- Parameter execute: The closure that executes when this promise fulfills.
- Returns: A new promise that resolves when the promise returned from the provided closure resolves. For example:
URLSession.GET(url1).then { data in
return CLLocationManager.promise()
}.then { location in
//
}
*/
public func then<U>(on q: DispatchQueue = .default, execute body: @escaping (T) throws -> Promise<U>) -> Promise<U> {
var resolve: ((Resolution<U>) -> Void)!
let rv = Promise<U>{ resolve = $0 }
state.then(on: q, else: resolve) { value in
let promise = try body(value)
guard promise !== rv else { throw PMKError.returnedSelf }
promise.state.pipe(resolve)
}
return rv
}
/**
The provided closure executes when this promise resolves.
This variant of `then` allows returning a tuple of promises within provided closure. All of the returned
promises needs be fulfilled for this promise to be marked as resolved.
- Note: At maximum 5 promises may be returned in a tuple
- Note: If *any* of the tuple-provided promises reject, the returned promise is immediately rejected with that error.
- Warning: In the event of rejection the other promises will continue to resolve and, as per any other promise, will either fulfill or reject.
- Parameter on: The queue to which the provided closure dispatches.
- Parameter execute: The closure that executes when this promise fulfills.
- Returns: A new promise that resolves when all promises returned from the provided closure resolve. For example:
loginPromise.then { _ -> (Promise<Data>, Promise<UIImage>)
return (URLSession.GET(userUrl), URLSession.dataTask(with: avatarUrl).asImage())
}.then { userData, avatarImage in
//
}
*/
public func then<U, V>(on q: DispatchQueue = .default, execute body: @escaping (T) throws -> (Promise<U>, Promise<V>)) -> Promise<(U, V)> {
return then(on: q, execute: body) { when(fulfilled: $0.0, $0.1) }
}
/// This variant of `then` allows returning a tuple of promises within provided closure.
public func then<U, V, X>(on q: DispatchQueue = .default, execute body: @escaping (T) throws -> (Promise<U>, Promise<V>, Promise<X>)) -> Promise<(U, V, X)> {
return then(on: q, execute: body) { when(fulfilled: $0.0, $0.1, $0.2) }
}
/// This variant of `then` allows returning a tuple of promises within provided closure.
public func then<U, V, X, Y>(on q: DispatchQueue = .default, execute body: @escaping (T) throws -> (Promise<U>, Promise<V>, Promise<X>, Promise<Y>)) -> Promise<(U, V, X, Y)> {
return then(on: q, execute: body) { when(fulfilled: $0.0, $0.1, $0.2, $0.3) }
}
/// This variant of `then` allows returning a tuple of promises within provided closure.
public func then<U, V, X, Y, Z>(on q: DispatchQueue = .default, execute body: @escaping (T) throws -> (Promise<U>, Promise<V>, Promise<X>, Promise<Y>, Promise<Z>)) -> Promise<(U, V, X, Y, Z)> {
return then(on: q, execute: body) { when(fulfilled: $0.0, $0.1, $0.2, $0.3, $0.4) }
}
/// utility function to serve `then` implementations with `body` returning tuple of promises
private func then<U, V>(on q: DispatchQueue, execute body: @escaping (T) throws -> V, when: @escaping (V) -> Promise<U>) -> Promise<U> {
return Promise<U> { resolve in
state.then(on: q, else: resolve) { value in
let promise = try body(value)
// since when(promise) switches to `zalgo`, we have to pipe back to `q`
when(promise).state.pipe(on: q, to: resolve)
}
/// - See: `Thenable.result`
public var result: Result<T>? {
switch box.inspect() {
case .pending:
return nil
case .resolved(let result):
return result
}
}
/**
The provided closure executes when this promise rejects.
Rejecting a promise cascades: rejecting all subsequent promises (unless
recover is invoked) thus you will typically place your catch at the end
of a chain. Often utility promises will not have a catch, instead
delegating the error handling to the caller.
- Parameter on: The queue to which the provided closure dispatches.
- Parameter policy: The default policy does not execute your handler for cancellation errors.
- Parameter execute: The handler to execute if this promise is rejected.
- Returns: `self`
- SeeAlso: [Cancellation](http://promisekit.org/docs/)
- Important: The promise that is returned is `self`. `catch` cannot affect the chain, in PromiseKit 3 no promise was returned to strongly imply this, however for PromiseKit 4 we started returning a promise so that you can `always` after a catch or return from a function that has an error handler.
*/
@discardableResult
public func `catch`(on q: DispatchQueue = .default, policy: CatchPolicy = .allErrorsExceptCancellation, execute body: @escaping (Error) -> Void) -> Promise {
state.catch(on: q, policy: policy, else: { _ in }, execute: body)
return self
init(_: PMKUnambiguousInitializer) {
box = EmptyBox()
}
}
public extension Promise {
/**
The provided closure executes when this promise rejects.
Unlike `catch`, `recover` continues the chain provided the closure does not throw. Use `recover` in circumstances where recovering the chain from certain errors is a possibility. For example:
CLLocationManager.promise().recover { error in
guard error == CLError.unknownLocation else { throw error }
return CLLocation.Chicago
}
- Parameter on: The queue to which the provided closure dispatches.
- Parameter policy: The default policy does not execute your handler for cancellation errors.
- Parameter execute: The handler to execute if this promise is rejected.
- SeeAlso: [Cancellation](http://promisekit.org/docs/)
Blocks this thread, soyou knowdont call this on a serial thread that
any part of your chain may use. Like the main thread for example.
*/
public func recover(on q: DispatchQueue = .default, policy: CatchPolicy = .allErrorsExceptCancellation, execute body: @escaping (Error) throws -> Promise) -> Promise {
var resolve: ((Resolution<T>) -> Void)!
let rv = Promise{ resolve = $0 }
state.catch(on: q, policy: policy, else: resolve) { error in
let promise = try body(error)
guard promise !== rv else { throw PMKError.returnedSelf }
promise.state.pipe(resolve)
func wait() throws -> T {
if Thread.isMainThread {
conf.logHandler(LogEvent.waitOnMainThread)
}
return rv
}
/**
The provided closure executes when this promise rejects.
var result = self.result
Unlike `catch`, `recover` continues the chain provided the closure does not throw. Use `recover` in circumstances where recovering the chain from certain errors is a possibility. For example:
CLLocationManager.promise().recover { error in
guard error == CLError.unknownLocation else { throw error }
return CLLocation.Chicago
}
- Parameter on: The queue to which the provided closure dispatches.
- Parameter policy: The default policy does not execute your handler for cancellation errors.
- Parameter execute: The handler to execute if this promise is rejected.
- SeeAlso: [Cancellation](http://promisekit.org/docs/)
*/
public func recover(on q: DispatchQueue = .default, policy: CatchPolicy = .allErrorsExceptCancellation, execute body: @escaping (Error) throws -> T) -> Promise {
return Promise { resolve in
state.catch(on: q, policy: policy, else: resolve) { error in
resolve(.fulfilled(try body(error)))
}
if result == nil {
let group = DispatchGroup()
group.enter()
pipe { result = $0; group.leave() }
group.wait()
}
}
/**
The provided closure executes when this promise resolves.
firstly {
UIApplication.shared.networkActivityIndicatorVisible = true
}.then {
//
}.always {
UIApplication.shared.networkActivityIndicatorVisible = false
}.catch {
//
}
- Parameter on: The queue to which the provided closure dispatches.
- Parameter execute: The closure that executes when this promise resolves.
- Returns: A new promise, resolved with this promises resolution.
*/
@discardableResult
public func always(on q: DispatchQueue = .default, execute body: @escaping () -> Void) -> Promise {
state.always(on: q) { resolution in
body()
}
return self
}
/**
Allows you to tap into a promise chain and inspect its result.
The function you provide cannot mutate the chain.
URLSession.GET(/**/).tap { result in
print(result)
}
- Parameter on: The queue to which the provided closure dispatches.
- Parameter execute: The closure that executes when this promise resolves.
- Returns: A new promise, resolved with this promises resolution.
*/
@discardableResult
public func tap(on q: DispatchQueue = .default, execute body: @escaping (Result<T>) -> Void) -> Promise {
state.always(on: q) { resolution in
body(Result(resolution))
}
return self
}
/**
Void promises are less prone to generics-of-doom scenarios.
- SeeAlso: when.swift contains enlightening examples of using `Promise<Void>` to simplify your code.
*/
public func asVoid() -> Promise<Void> {
return then(on: zalgo) { _ in return }
}
//MARK: deprecations
@available(*, unavailable, renamed: "always()")
public func finally(on: DispatchQueue = DispatchQueue.main, execute body: () -> Void) -> Promise { fatalError() }
@available(*, unavailable, renamed: "always()")
public func ensure(on: DispatchQueue = DispatchQueue.main, execute body: () -> Void) -> Promise { fatalError() }
@available(*, unavailable, renamed: "pending()")
public class func `defer`() -> PendingTuple { fatalError() }
@available(*, unavailable, renamed: "pending()")
public class func `pendingPromise`() -> PendingTuple { fatalError() }
@available(*, unavailable, message: "deprecated: use then(on: .global())")
public func thenInBackground<U>(execute body: (T) throws -> U) -> Promise<U> { fatalError() }
@available(*, unavailable, renamed: "catch")
public func onError(policy: CatchPolicy = .allErrors, execute body: (Error) -> Void) { fatalError() }
@available(*, unavailable, renamed: "catch")
public func errorOnQueue(_ on: DispatchQueue, policy: CatchPolicy = .allErrors, execute body: (Error) -> Void) { fatalError() }
@available(*, unavailable, renamed: "catch")
public func error(policy: CatchPolicy, execute body: (Error) -> Void) { fatalError() }
@available(*, unavailable, renamed: "catch")
public func report(policy: CatchPolicy = .allErrors, execute body: (Error) -> Void) { fatalError() }
@available(*, unavailable, renamed: "init(value:)")
public init(_ value: T) { fatalError() }
//MARK: disallow `Promise<Error>`
@available(*, unavailable, message: "cannot instantiate Promise<Error>")
public init<T: Error>(resolvers: (_ fulfill: (T) -> Void, _ reject: (Error) -> Void) throws -> Void) { fatalError() }
@available(*, unavailable, message: "cannot instantiate Promise<Error>")
public class func pending<T: Error>() -> (promise: Promise, fulfill: (T) -> Void, reject: (Error) -> Void) { fatalError() }
//MARK: disallow returning `Error`
@available (*, unavailable, message: "instead of returning the error; throw")
public func then<U: Error>(on: DispatchQueue = .default, execute body: (T) throws -> U) -> Promise<U> { fatalError() }
@available (*, unavailable, message: "instead of returning the error; throw")
public func recover<T: Error>(on: DispatchQueue = .default, execute body: (Error) throws -> T) -> Promise { fatalError() }
//MARK: disallow returning `Promise?`
@available(*, unavailable, message: "unwrap the promise")
public func then<U>(on: DispatchQueue = .default, execute body: (T) throws -> Promise<U>?) -> Promise<U> { fatalError() }
@available(*, unavailable, message: "unwrap the promise")
public func recover(on: DispatchQueue = .default, execute body: (Error) throws -> Promise?) -> Promise { fatalError() }
}
extension Promise: CustomStringConvertible {
public var description: String {
return "Promise: \(state)"
}
}
/**
Judicious use of `firstly` *may* make chains more readable.
Compare:
URLSession.GET(url1).then {
URLSession.GET(url2)
}.then {
URLSession.GET(url3)
}
With:
firstly {
URLSession.GET(url1)
}.then {
URLSession.GET(url2)
}.then {
URLSession.GET(url3)
}
*/
public func firstly<T>(execute body: () throws -> Promise<T>) -> Promise<T> {
return firstly(execute: body) { $0 }
}
/**
Judicious use of `firstly` *may* make chains more readable.
Firstly allows to return tuple of promises
Compare:
when(fulfilled: URLSession.GET(url1), URLSession.GET(url2)).then {
URLSession.GET(url3)
}.then {
URLSession.GET(url4)
}
With:
firstly {
(URLSession.GET(url1), URLSession.GET(url2))
}.then { _, _ in
URLSession.GET(url2)
}.then {
URLSession.GET(url3)
}
- Note: At maximum 5 promises may be returned in a tuple
- Note: If *any* of the tuple-provided promises reject, the returned promise is immediately rejected with that error.
*/
public func firstly<T, U>(execute body: () throws -> (Promise<T>, Promise<U>)) -> Promise<(T, U)> {
return firstly(execute: body) { when(fulfilled: $0.0, $0.1) }
}
/// Firstly allows to return tuple of promises
public func firstly<T, U, V>(execute body: () throws -> (Promise<T>, Promise<U>, Promise<V>)) -> Promise<(T, U, V)> {
return firstly(execute: body) { when(fulfilled: $0.0, $0.1, $0.2) }
}
/// Firstly allows to return tuple of promises
public func firstly<T, U, V, W>(execute body: () throws -> (Promise<T>, Promise<U>, Promise<V>, Promise<W>)) -> Promise<(T, U, V, W)> {
return firstly(execute: body) { when(fulfilled: $0.0, $0.1, $0.2, $0.3) }
}
/// Firstly allows to return tuple of promises
public func firstly<T, U, V, W, X>(execute body: () throws -> (Promise<T>, Promise<U>, Promise<V>, Promise<W>, Promise<X>)) -> Promise<(T, U, V, W, X)> {
return firstly(execute: body) { when(fulfilled: $0.0, $0.1, $0.2, $0.3, $0.4) }
}
/// utility function to serve `firstly` implementations with `body` returning tuple of promises
fileprivate func firstly<U, V>(execute body: () throws -> V, when: (V) -> Promise<U>) -> Promise<U> {
do {
return when(try body())
} catch {
return Promise(error: error)
}
}
@available(*, unavailable, message: "instead of returning the error; throw")
public func firstly<T: Error>(execute body: () throws -> T) -> Promise<T> { fatalError() }
@available(*, unavailable, message: "use DispatchQueue.promise")
public func firstly<T>(on: DispatchQueue, execute body: () throws -> Promise<T>) -> Promise<T> { fatalError() }
/**
- SeeAlso: `DispatchQueue.promise(group:qos:flags:execute:)`
*/
@available(*, deprecated: 4.0, renamed: "DispatchQueue.promise")
public func dispatch_promise<T>(_ on: DispatchQueue, _ body: @escaping () throws -> T) -> Promise<T> {
return Promise(value: ()).then(on: on, execute: body)
}
/**
The underlying resolved state of a promise.
- Remark: Same as `Resolution<T>` but without the associated `ErrorConsumptionToken`.
*/
public enum Result<T> {
/// Fulfillment
case fulfilled(T)
/// Rejection
case rejected(Error)
init(_ resolution: Resolution<T>) {
switch resolution {
switch result! {
case .rejected(let error):
throw error
case .fulfilled(let value):
self = .fulfilled(value)
case .rejected(let error, _):
self = .rejected(error)
}
}
/**
- Returns: `true` if the result is `fulfilled` or `false` if it is `rejected`.
*/
public var boolValue: Bool {
switch self {
case .fulfilled:
return true
case .rejected:
return false
return value
}
}
}
/**
An object produced by `Promise.joint()`, along with a promise to which it is bound.
Joining with a promise via `Promise.join(_:)` will pipe the resolution of that promise to
the joint's bound promise.
- SeeAlso: `Promise.joint()`
- SeeAlso: `Promise.join(_:)`
*/
public class PMKJoint<T> {
fileprivate var resolve: ((Resolution<T>) -> Void)!
}
extension Promise {
/**
Provides a safe way to instantiate a `Promise` and resolve it later via its joint and another
promise.
class Engine {
static func make() -> Promise<Engine> {
let (enginePromise, joint) = Promise<Engine>.joint()
let cylinder: Cylinder = Cylinder(explodeAction: {
// We *could* use an IUO, but there are no guarantees about when
// this callback will be called. Having an actual promise is safe.
enginePromise.then { engine in
engine.checkOilPressure()
}
})
firstly {
Ignition.default.start()
}.then { plugs in
Engine(cylinders: [cylinder], sparkPlugs: plugs)
}.join(joint)
return enginePromise
}
}
- Returns: A new promise and its joint.
- SeeAlso: `Promise.join(_:)`
*/
public final class func joint() -> (Promise<T>, PMKJoint<T>) {
let pipe = PMKJoint<T>()
let promise = Promise(sealant: { pipe.resolve = $0 })
return (promise, pipe)
}
/**
Pipes the value of this promise to the promise created with the joint.
- Parameter joint: The joint on which to join.
- SeeAlso: `Promise.joint()`
*/
public func join(_ joint: PMKJoint<T>) {
state.pipe(joint.resolve)
}
}
extension Promise where T: Collection {
/**
Transforms a `Promise` where `T` is a `Collection` into a `Promise<[U]>`
URLSession.shared.dataTask(url: /**/).asArray().map { result in
return download(result)
}.then { images in
// images is `[UIImage]`
}
- Parameter on: The queue to which the provided closure dispatches.
- Parameter transform: The closure that executes when this promise resolves.
- Returns: A new promise, resolved with this promises resolution.
*/
public func map<U>(on: DispatchQueue = .default, transform: @escaping (T.Iterator.Element) throws -> Promise<U>) -> Promise<[U]> {
return Promise<[U]> { resolve in
return state.then(on: zalgo, else: resolve) { tt in
when(fulfilled: try tt.map(transform)).state.pipe(resolve)
}
}
}
}
#if swift(>=3.1)
public extension Promise where T == Void {
convenience init() {
self.init(value: ())
extension Promise where T == Void {
/// Initializes a new promise fulfilled with `Void`
public convenience init() {
self.init(box: SealedBox(value: .fulfilled(Void())))
}
/// Returns a new promise fulfilled with `Void`
public static var value: Promise<Void> {
return .value(Void())
}
}
#endif
public extension DispatchQueue {
/**
Asynchronously executes the provided closure on a dispatch queue.
DispatchQueue.global().async(.promise) {
try md5(input)
}.done { md5 in
//
}
- Parameter body: The closure that resolves this promise.
- Returns: A new `Promise` resolved by the result of the provided closure.
- Note: There is no Promise/Thenable version of this due to Swift compiler ambiguity issues.
*/
@available(macOS 10.10, iOS 8.0, tvOS 9.0, watchOS 2.0, *)
final func async<T>(_: PMKNamespacer, group: DispatchGroup? = nil, qos: DispatchQoS = .default, flags: DispatchWorkItemFlags = [], execute body: @escaping () throws -> T) -> Promise<T> {
let promise = Promise<T>(.pending)
async(group: group, qos: qos, flags: flags) {
do {
promise.box.seal(.fulfilled(try body()))
} catch {
promise.box.seal(.rejected(error))
}
}
return promise
}
}
/// used by our extensions to provide unambiguous functions with the same name as the original function
public enum PMKNamespacer {
case promise
}
enum PMKUnambiguousInitializer {
case pending
}

View File

@ -1,5 +1,5 @@
#import "fwd.h"
#import "AnyPromise.h"
#import <PromiseKit/fwd.h>
#import <PromiseKit/AnyPromise.h>
#import <Foundation/NSObjCRuntime.h> // `FOUNDATION_EXPORT`

99
Sources/Resolver.swift Normal file
View File

@ -0,0 +1,99 @@
/// An object for resolving promises
public final class Resolver<T> {
let box: Box<Result<T>>
init(_ box: Box<Result<T>>) {
self.box = box
}
deinit {
if case .pending = box.inspect() {
conf.logHandler(.pendingPromiseDeallocated)
}
}
}
public extension Resolver {
/// Fulfills the promise with the provided value
func fulfill(_ value: T) {
box.seal(.fulfilled(value))
}
/// Rejects the promise with the provided error
func reject(_ error: Error) {
box.seal(.rejected(error))
}
/// Resolves the promise with the provided result
func resolve(_ result: Result<T>) {
box.seal(result)
}
/// Resolves the promise with the provided value or error
func resolve(_ obj: T?, _ error: Error?) {
if let error = error {
reject(error)
} else if let obj = obj {
fulfill(obj)
} else {
reject(PMKError.invalidCallingConvention)
}
}
/// Fulfills the promise with the provided value unless the provided error is non-nil
func resolve(_ obj: T, _ error: Error?) {
if let error = error {
reject(error)
} else {
fulfill(obj)
}
}
/// Resolves the promise, provided for non-conventional value-error ordered completion handlers.
func resolve(_ error: Error?, _ obj: T?) {
resolve(obj, error)
}
}
#if swift(>=3.1)
extension Resolver where T == Void {
/// Fulfills the promise unless error is non-nil
public func resolve(_ error: Error?) {
if let error = error {
reject(error)
} else {
fulfill(())
}
}
#if false
// disabled https://github.com/mxcl/PromiseKit/issues/990
/// Fulfills the promise
public func fulfill() {
self.fulfill(())
}
#else
/// Fulfills the promise
/// - Note: underscore is present due to: https://github.com/mxcl/PromiseKit/issues/990
public func fulfill_() {
self.fulfill(())
}
#endif
}
#endif
public enum Result<T> {
case fulfilled(T)
case rejected(Error)
}
public extension PromiseKit.Result {
var isFulfilled: Bool {
switch self {
case .fulfilled:
return true
case .rejected:
return false
}
}
}

View File

@ -1,219 +0,0 @@
import class Dispatch.DispatchQueue
import func Foundation.NSLog
enum Seal<T> {
case pending(Handlers<T>)
case resolved(Resolution<T>)
}
enum Resolution<T> {
case fulfilled(T)
case rejected(Error, ErrorConsumptionToken)
init(_ error: Error) {
self = .rejected(error, ErrorConsumptionToken(error))
}
}
class State<T> {
// would be a protocol, but you can't have typed variables of generic
// protocols in Swift 2. That is, I couldnt do var state: State<R> when
// it was a protocol. There is no work around. Update: nor Swift 3
func get() -> Resolution<T>? { fatalError("Abstract Base Class") }
func get(body: @escaping (Seal<T>) -> Void) { fatalError("Abstract Base Class") }
final func pipe(_ body: @escaping (Resolution<T>) -> Void) {
get { seal in
switch seal {
case .pending(let handlers):
handlers.append(body)
case .resolved(let resolution):
body(resolution)
}
}
}
final func pipe(on q: DispatchQueue, to body: @escaping (Resolution<T>) -> Void) {
pipe { resolution in
contain_zalgo(q) {
body(resolution)
}
}
}
final func then<U>(on q: DispatchQueue, else rejecter: @escaping (Resolution<U>) -> Void, execute body: @escaping (T) throws -> Void) {
pipe { resolution in
switch resolution {
case .fulfilled(let value):
contain_zalgo(q, rejecter: rejecter) {
try body(value)
}
case .rejected(let error, let token):
rejecter(.rejected(error, token))
}
}
}
final func always(on q: DispatchQueue, body: @escaping (Resolution<T>) -> Void) {
pipe { resolution in
contain_zalgo(q) {
body(resolution)
}
}
}
final func `catch`(on q: DispatchQueue, policy: CatchPolicy, else resolve: @escaping (Resolution<T>) -> Void, execute body: @escaping (Error) throws -> Void) {
pipe { resolution in
switch (resolution, policy) {
case (.fulfilled, _):
resolve(resolution)
case (.rejected(let error, _), .allErrorsExceptCancellation) where error.isCancelledError:
resolve(resolution)
case (let .rejected(error, token), _):
contain_zalgo(q, rejecter: resolve) {
token.consumed = true
try body(error)
}
}
}
}
}
class UnsealedState<T>: State<T> {
private let barrier = DispatchQueue(label: "org.promisekit.barrier", attributes: .concurrent)
private var seal: Seal<T>
/**
Quick return, but will not provide the handlers array because
it could be modified while you are using it by another thread.
If you need the handlers, use the second `get` variant.
*/
override func get() -> Resolution<T>? {
var result: Resolution<T>?
barrier.sync {
if case .resolved(let resolution) = self.seal {
result = resolution
}
}
return result
}
override func get(body: @escaping (Seal<T>) -> Void) {
var sealed = false
barrier.sync {
switch self.seal {
case .resolved:
sealed = true
case .pending:
sealed = false
}
}
if !sealed {
barrier.sync(flags: .barrier) {
switch (self.seal) {
case .pending:
body(self.seal)
case .resolved:
sealed = true // welcome to race conditions
}
}
}
if sealed {
body(seal) // as much as possible we do things OUTSIDE the barrier_sync
}
}
required init(resolver: inout ((Resolution<T>) -> Void)!) {
seal = .pending(Handlers<T>())
super.init()
resolver = { resolution in
var handlers: Handlers<T>?
self.barrier.sync(flags: .barrier) {
if case .pending(let hh) = self.seal {
self.seal = .resolved(resolution)
handlers = hh
}
}
if let handlers = handlers {
for handler in handlers {
handler(resolution)
}
}
}
}
#if !PMKDisableWarnings
deinit {
if case .pending = seal {
NSLog("PromiseKit: Pending Promise deallocated! This is usually a bug")
}
}
#endif
}
class SealedState<T>: State<T> {
fileprivate let resolution: Resolution<T>
init(resolution: Resolution<T>) {
self.resolution = resolution
}
override func get() -> Resolution<T>? {
return resolution
}
override func get(body: @escaping (Seal<T>) -> Void) {
body(.resolved(resolution))
}
}
class Handlers<T>: Sequence {
var bodies: [(Resolution<T>) -> Void] = []
func append(_ body: @escaping (Resolution<T>) -> Void) {
bodies.append(body)
}
func makeIterator() -> IndexingIterator<[(Resolution<T>) -> Void]> {
return bodies.makeIterator()
}
var count: Int {
return bodies.count
}
}
extension Resolution: CustomStringConvertible {
var description: String {
switch self {
case .fulfilled(let value):
return "Fulfilled with value: \(value)"
case .rejected(let error):
return "Rejected with error: \(error)"
}
}
}
extension UnsealedState: CustomStringConvertible {
var description: String {
var rv: String!
get { seal in
switch seal {
case .pending(let handlers):
rv = "Pending with \(handlers.count) handlers"
case .resolved(let resolution):
rv = "\(resolution)"
}
}
return "UnsealedState: \(rv)"
}
}
extension SealedState: CustomStringConvertible {
var description: String {
return "SealedState: \(resolution)"
}
}

View File

@ -1,16 +0,0 @@
public enum CatchPolicy {
case allErrorsExceptCancellation
case allErrors
}
func PMKUnhandledErrorHandler(_ error: Error)
{}
import class Dispatch.DispatchQueue
func __PMKDefaultDispatchQueue() -> DispatchQueue {
return DispatchQueue.main
}
func __PMKSetDefaultDispatchQueue(_: DispatchQueue)
{}

533
Sources/Thenable.swift Normal file
View File

@ -0,0 +1,533 @@
import Dispatch
/// Thenable represents an asynchronous operation that can be chained.
public protocol Thenable: class {
/// The type of the wrapped value
associatedtype T
/// `pipe` is immediately executed when this `Thenable` is resolved
func pipe(to: @escaping(Result<T>) -> Void)
/// The resolved result or nil if pending.
var result: Result<T>? { get }
}
public extension Thenable {
/**
The provided closure executes when this promise is fulfilled.
This allows chaining promises. The promise returned by the provided closure is resolved before the promise returned by this closure resolves.
- Parameter on: The queue to which the provided closure dispatches.
- Parameter body: The closure that executes when this promise is fulfilled. It must return a promise.
- Returns: A new promise that resolves when the promise returned from the provided closure resolves. For example:
firstly {
URLSession.shared.dataTask(.promise, with: url1)
}.then { response in
transform(data: response.data)
}.done { transformation in
//
}
*/
func then<U: Thenable>(on: DispatchQueue? = conf.Q.map, flags: DispatchWorkItemFlags? = nil, _ body: @escaping(T) throws -> U) -> Promise<U.T> {
let rp = Promise<U.T>(.pending)
pipe {
switch $0 {
case .fulfilled(let value):
on.async(flags: flags) {
do {
let rv = try body(value)
guard rv !== rp else { throw PMKError.returnedSelf }
rv.pipe(to: rp.box.seal)
} catch {
rp.box.seal(.rejected(error))
}
}
case .rejected(let error):
rp.box.seal(.rejected(error))
}
}
return rp
}
/**
The provided closure is executed when this promise is fulfilled.
This is like `then` but it requires the closure to return a non-promise.
- Parameter on: The queue to which the provided closure dispatches.
- Parameter transform: The closure that is executed when this Promise is fulfilled. It must return a non-promise.
- Returns: A new promise that is fulfilled with the value returned from the provided closure or rejected if the provided closure throws. For example:
firstly {
URLSession.shared.dataTask(.promise, with: url1)
}.map { response in
response.data.length
}.done { length in
//
}
*/
func map<U>(on: DispatchQueue? = conf.Q.map, flags: DispatchWorkItemFlags? = nil, _ transform: @escaping(T) throws -> U) -> Promise<U> {
let rp = Promise<U>(.pending)
pipe {
switch $0 {
case .fulfilled(let value):
on.async(flags: flags) {
do {
rp.box.seal(.fulfilled(try transform(value)))
} catch {
rp.box.seal(.rejected(error))
}
}
case .rejected(let error):
rp.box.seal(.rejected(error))
}
}
return rp
}
#if swift(>=4) && !swift(>=5.2)
/**
Similar to func `map<U>(on: DispatchQueue? = conf.Q.map, flags: DispatchWorkItemFlags? = nil, _ transform: @escaping(T) throws -> U) -> Promise<U>`, but accepts a key path instead of a closure.
- Parameter on: The queue to which the provided key path for value dispatches.
- Parameter keyPath: The key path to the value that is using when this Promise is fulfilled.
- Returns: A new promise that is fulfilled with the value for the provided key path.
*/
func map<U>(on: DispatchQueue? = conf.Q.map, flags: DispatchWorkItemFlags? = nil, _ keyPath: KeyPath<T, U>) -> Promise<U> {
let rp = Promise<U>(.pending)
pipe {
switch $0 {
case .fulfilled(let value):
on.async(flags: flags) {
rp.box.seal(.fulfilled(value[keyPath: keyPath]))
}
case .rejected(let error):
rp.box.seal(.rejected(error))
}
}
return rp
}
#endif
/**
The provided closure is executed when this promise is fulfilled.
In your closure return an `Optional`, if you return `nil` the resulting promise is rejected with `PMKError.compactMap`, otherwise the promise is fulfilled with the unwrapped value.
firstly {
URLSession.shared.dataTask(.promise, with: url)
}.compactMap {
try JSONSerialization.jsonObject(with: $0.data) as? [String: String]
}.done { dictionary in
//
}.catch {
// either `PMKError.compactMap` or a `JSONError`
}
*/
func compactMap<U>(on: DispatchQueue? = conf.Q.map, flags: DispatchWorkItemFlags? = nil, _ transform: @escaping(T) throws -> U?) -> Promise<U> {
let rp = Promise<U>(.pending)
pipe {
switch $0 {
case .fulfilled(let value):
on.async(flags: flags) {
do {
if let rv = try transform(value) {
rp.box.seal(.fulfilled(rv))
} else {
throw PMKError.compactMap(value, U.self)
}
} catch {
rp.box.seal(.rejected(error))
}
}
case .rejected(let error):
rp.box.seal(.rejected(error))
}
}
return rp
}
#if swift(>=4) && !swift(>=5.2)
/**
Similar to func `compactMap<U>(on: DispatchQueue? = conf.Q.map, flags: DispatchWorkItemFlags? = nil, _ transform: @escaping(T) throws -> U?) -> Promise<U>`, but accepts a key path instead of a closure.
- Parameter on: The queue to which the provided key path for value dispatches.
- Parameter keyPath: The key path to the value that is using when this Promise is fulfilled. If the value for `keyPath` is `nil` the resulting promise is rejected with `PMKError.compactMap`.
- Returns: A new promise that is fulfilled with the value for the provided key path.
*/
func compactMap<U>(on: DispatchQueue? = conf.Q.map, flags: DispatchWorkItemFlags? = nil, _ keyPath: KeyPath<T, U?>) -> Promise<U> {
let rp = Promise<U>(.pending)
pipe {
switch $0 {
case .fulfilled(let value):
on.async(flags: flags) {
do {
if let rv = value[keyPath: keyPath] {
rp.box.seal(.fulfilled(rv))
} else {
throw PMKError.compactMap(value, U.self)
}
} catch {
rp.box.seal(.rejected(error))
}
}
case .rejected(let error):
rp.box.seal(.rejected(error))
}
}
return rp
}
#endif
/**
The provided closure is executed when this promise is fulfilled.
Equivalent to `map { x -> Void in`, but since we force the `Void` return Swift
is happier and gives you less hassle about your closures qualification.
- Parameter on: The queue to which the provided closure dispatches.
- Parameter body: The closure that is executed when this Promise is fulfilled.
- Returns: A new promise fulfilled as `Void` or rejected if the provided closure throws.
firstly {
URLSession.shared.dataTask(.promise, with: url)
}.done { response in
print(response.data)
}
*/
func done(on: DispatchQueue? = conf.Q.return, flags: DispatchWorkItemFlags? = nil, _ body: @escaping(T) throws -> Void) -> Promise<Void> {
let rp = Promise<Void>(.pending)
pipe {
switch $0 {
case .fulfilled(let value):
on.async(flags: flags) {
do {
try body(value)
rp.box.seal(.fulfilled(()))
} catch {
rp.box.seal(.rejected(error))
}
}
case .rejected(let error):
rp.box.seal(.rejected(error))
}
}
return rp
}
/**
The provided closure is executed when this promise is fulfilled.
This is like `done` but it returns the same value that the handler is fed.
`get` immutably accesses the fulfilled value; the returned Promise maintains that value.
- Parameter on: The queue to which the provided closure dispatches.
- Parameter body: The closure that is executed when this Promise is fulfilled.
- Returns: A new promise that is fulfilled with the value that the handler is fed or rejected if the provided closure throws. For example:
firstly {
.value(1)
}.get { foo in
print(foo, " is 1")
}.done { foo in
print(foo, " is 1")
}.done { foo in
print(foo, " is Void")
}
*/
func get(on: DispatchQueue? = conf.Q.return, flags: DispatchWorkItemFlags? = nil, _ body: @escaping (T) throws -> Void) -> Promise<T> {
return map(on: on, flags: flags) {
try body($0)
return $0
}
}
/**
The provided closure is executed with promise result.
This is like `get` but provides the Result<T> of the Promise so you can inspect the value of the chain at this point without causing any side effects.
- Parameter on: The queue to which the provided closure dispatches.
- Parameter body: The closure that is executed with Result of Promise.
- Returns: A new promise that is resolved with the result that the handler is fed. For example:
promise.tap{ print($0) }.then{ /**/ }
*/
func tap(on: DispatchQueue? = conf.Q.map, flags: DispatchWorkItemFlags? = nil, _ body: @escaping(Result<T>) -> Void) -> Promise<T> {
return Promise { seal in
pipe { result in
on.async(flags: flags) {
body(result)
seal.resolve(result)
}
}
}
}
/// - Returns: a new promise chained off this promise but with its value discarded.
func asVoid() -> Promise<Void> {
return map(on: nil) { _ in }
}
}
public extension Thenable {
/**
- Returns: The error with which this promise was rejected; `nil` if this promise is not rejected.
*/
var error: Error? {
switch result {
case .none:
return nil
case .some(.fulfilled):
return nil
case .some(.rejected(let error)):
return error
}
}
/**
- Returns: `true` if the promise has not yet resolved.
*/
var isPending: Bool {
return result == nil
}
/**
- Returns: `true` if the promise has resolved.
*/
var isResolved: Bool {
return !isPending
}
/**
- Returns: `true` if the promise was fulfilled.
*/
var isFulfilled: Bool {
return value != nil
}
/**
- Returns: `true` if the promise was rejected.
*/
var isRejected: Bool {
return error != nil
}
/**
- Returns: The value with which this promise was fulfilled or `nil` if this promise is pending or rejected.
*/
var value: T? {
switch result {
case .none:
return nil
case .some(.fulfilled(let value)):
return value
case .some(.rejected):
return nil
}
}
}
public extension Thenable where T: Sequence {
/**
`Promise<[T]>` => `T` -> `U` => `Promise<[U]>`
firstly {
.value([1,2,3])
}.mapValues { integer in
integer * 2
}.done {
// $0 => [2,4,6]
}
*/
func mapValues<U>(on: DispatchQueue? = conf.Q.map, flags: DispatchWorkItemFlags? = nil, _ transform: @escaping(T.Iterator.Element) throws -> U) -> Promise<[U]> {
return map(on: on, flags: flags){ try $0.map(transform) }
}
#if swift(>=4) && !swift(>=5.2)
/**
`Promise<[T]>` => `KeyPath<T, U>` => `Promise<[U]>`
firstly {
.value([Person(name: "Max"), Person(name: "Roman"), Person(name: "John")])
}.mapValues(\.name).done {
// $0 => ["Max", "Roman", "John"]
}
*/
func mapValues<U>(on: DispatchQueue? = conf.Q.map, flags: DispatchWorkItemFlags? = nil, _ keyPath: KeyPath<T.Iterator.Element, U>) -> Promise<[U]> {
return map(on: on, flags: flags){ $0.map { $0[keyPath: keyPath] } }
}
#endif
/**
`Promise<[T]>` => `T` -> `[U]` => `Promise<[U]>`
firstly {
.value([1,2,3])
}.flatMapValues { integer in
[integer, integer]
}.done {
// $0 => [1,1,2,2,3,3]
}
*/
func flatMapValues<U: Sequence>(on: DispatchQueue? = conf.Q.map, flags: DispatchWorkItemFlags? = nil, _ transform: @escaping(T.Iterator.Element) throws -> U) -> Promise<[U.Iterator.Element]> {
return map(on: on, flags: flags){ (foo: T) in
try foo.flatMap{ try transform($0) }
}
}
/**
`Promise<[T]>` => `T` -> `U?` => `Promise<[U]>`
firstly {
.value(["1","2","a","3"])
}.compactMapValues {
Int($0)
}.done {
// $0 => [1,2,3]
}
*/
func compactMapValues<U>(on: DispatchQueue? = conf.Q.map, flags: DispatchWorkItemFlags? = nil, _ transform: @escaping(T.Iterator.Element) throws -> U?) -> Promise<[U]> {
return map(on: on, flags: flags) { foo -> [U] in
#if !swift(>=3.3) || (swift(>=4) && !swift(>=4.1))
return try foo.flatMap(transform)
#else
return try foo.compactMap(transform)
#endif
}
}
#if swift(>=4) && !swift(>=5.2)
/**
`Promise<[T]>` => `KeyPath<T, U?>` => `Promise<[U]>`
firstly {
.value([Person(name: "Max"), Person(name: "Roman", age: 26), Person(name: "John", age: 23)])
}.compactMapValues(\.age).done {
// $0 => [26, 23]
}
*/
func compactMapValues<U>(on: DispatchQueue? = conf.Q.map, flags: DispatchWorkItemFlags? = nil, _ keyPath: KeyPath<T.Iterator.Element, U?>) -> Promise<[U]> {
return map(on: on, flags: flags) { foo -> [U] in
#if !swift(>=4.1)
return foo.flatMap { $0[keyPath: keyPath] }
#else
return foo.compactMap { $0[keyPath: keyPath] }
#endif
}
}
#endif
/**
`Promise<[T]>` => `T` -> `Promise<U>` => `Promise<[U]>`
firstly {
.value([1,2,3])
}.thenMap { integer in
.value(integer * 2)
}.done {
// $0 => [2,4,6]
}
*/
func thenMap<U: Thenable>(on: DispatchQueue? = conf.Q.map, flags: DispatchWorkItemFlags? = nil, _ transform: @escaping(T.Iterator.Element) throws -> U) -> Promise<[U.T]> {
return then(on: on, flags: flags) {
when(fulfilled: try $0.map(transform))
}
}
/**
`Promise<[T]>` => `T` -> `Promise<[U]>` => `Promise<[U]>`
firstly {
.value([1,2,3])
}.thenFlatMap { integer in
.value([integer, integer])
}.done {
// $0 => [1,1,2,2,3,3]
}
*/
func thenFlatMap<U: Thenable>(on: DispatchQueue? = conf.Q.map, flags: DispatchWorkItemFlags? = nil, _ transform: @escaping(T.Iterator.Element) throws -> U) -> Promise<[U.T.Iterator.Element]> where U.T: Sequence {
return then(on: on, flags: flags) {
when(fulfilled: try $0.map(transform))
}.map(on: nil) {
$0.flatMap{ $0 }
}
}
/**
`Promise<[T]>` => `T` -> Bool => `Promise<[T]>`
firstly {
.value([1,2,3])
}.filterValues {
$0 > 1
}.done {
// $0 => [2,3]
}
*/
func filterValues(on: DispatchQueue? = conf.Q.map, flags: DispatchWorkItemFlags? = nil, _ isIncluded: @escaping (T.Iterator.Element) -> Bool) -> Promise<[T.Iterator.Element]> {
return map(on: on, flags: flags) {
$0.filter(isIncluded)
}
}
#if swift(>=4) && !swift(>=5.2)
/**
`Promise<[T]>` => `KeyPath<T, Bool>` => `Promise<[T]>`
firstly {
.value([Person(name: "Max"), Person(name: "Roman", age: 26, isStudent: false), Person(name: "John", age: 23, isStudent: true)])
}.filterValues(\.isStudent).done {
// $0 => [Person(name: "John", age: 23, isStudent: true)]
}
*/
func filterValues(on: DispatchQueue? = conf.Q.map, flags: DispatchWorkItemFlags? = nil, _ keyPath: KeyPath<T.Iterator.Element, Bool>) -> Promise<[T.Iterator.Element]> {
return map(on: on, flags: flags) {
$0.filter { $0[keyPath: keyPath] }
}
}
#endif
}
public extension Thenable where T: Collection {
/// - Returns: a promise fulfilled with the first value of this `Collection` or, if empty, a promise rejected with PMKError.emptySequence.
var firstValue: Promise<T.Iterator.Element> {
return map(on: nil) { aa in
if let a1 = aa.first {
return a1
} else {
throw PMKError.emptySequence
}
}
}
func firstValue(on: DispatchQueue? = conf.Q.map, flags: DispatchWorkItemFlags? = nil, where test: @escaping (T.Iterator.Element) -> Bool) -> Promise<T.Iterator.Element> {
return map(on: on, flags: flags) {
for x in $0 where test(x) {
return x
}
throw PMKError.emptySequence
}
}
/// - Returns: a promise fulfilled with the last value of this `Collection` or, if empty, a promise rejected with PMKError.emptySequence.
var lastValue: Promise<T.Iterator.Element> {
return map(on: nil) { aa in
if aa.isEmpty {
throw PMKError.emptySequence
} else {
let i = aa.index(aa.endIndex, offsetBy: -1)
return aa[i]
}
}
}
}
public extension Thenable where T: Sequence, T.Iterator.Element: Comparable {
/// - Returns: a promise fulfilled with the sorted values of this `Sequence`.
func sortedValues(on: DispatchQueue? = conf.Q.map, flags: DispatchWorkItemFlags? = nil) -> Promise<[T.Iterator.Element]> {
return map(on: on, flags: flags){ $0.sorted() }
}
}

View File

@ -1,80 +0,0 @@
import class Dispatch.DispatchQueue
import class Foundation.Thread
/**
`zalgo` causes your handlers to be executed as soon as their promise resolves.
Usually all handlers are dispatched to a queue (the main queue by default); the `on:` parameter of `then` configures this. Its default value is `DispatchQueue.main`.
- Important: `zalgo` is dangerous.
Compare:
var x = 0
foo.then {
print(x) // => 1
}
x++
With:
var x = 0
foo.then(on: zalgo) {
print(x) // => 0 or 1
}
x++
In the latter case the value of `x` may be `0` or `1` depending on whether `foo` is resolved. This is a race-condition that is easily avoided by not using `zalgo`.
- Important: you cannot control the queue that your handler executes if using `zalgo`.
- Note: `zalgo` is provided for libraries providing promises that have good tests that prove Unleashing Zalgo is safe. You can also use it in your application code in situations where performance is critical, but be careful: read the essay liked below to understand the risks.
- SeeAlso: [Designing APIs for Asynchrony](http://blog.izs.me/post/59142742143/designing-apis-for-asynchrony)
- SeeAlso: `waldo`
*/
public let zalgo = DispatchQueue(label: "Zalgo")
/**
`waldo` is dangerous.
`waldo` is `zalgo`, unless the current queue is the main thread, in which
case we dispatch to the default background queue.
If your block is likely to take more than a few milliseconds to execute,
then you should use waldo: 60fps means the main thread cannot hang longer
than 17 milliseconds: dont contribute to UI lag.
Conversely if your then block is trivial, use zalgo: GCD is not free and
for whatever reason you may already be on the main thread so just do what
you are doing quickly and pass on execution.
It is considered good practice for asynchronous APIs to complete onto the
main thread. Apple do not always honor this, nor do other developers.
However, they *should*. In that respect waldo is a good choice if your
then is going to take some time and doesnt interact with the UI.
Please note (again) that generally you should not use `zalgo` or `waldo`.
The performance gains are negligible and we provide these functions only out
of a misguided sense that library code should be as optimized as possible.
If you use either without tests proving their correctness you may
unwillingly introduce horrendous, near-impossible-to-trace bugs.
- SeeAlso: `zalgo`
*/
public let waldo = DispatchQueue(label: "Waldo")
@inline(__always) func contain_zalgo(_ q: DispatchQueue, body: @escaping () -> Void) {
if q === zalgo || q === waldo && !Thread.isMainThread {
body()
} else {
q.async(execute: body)
}
}
@inline(__always) func contain_zalgo<T>(_ q: DispatchQueue, rejecter reject: @escaping (Resolution<T>) -> Void, block: @escaping () throws -> Void) {
contain_zalgo(q) {
do { try block() } catch { reject(Resolution(error)) }
}
}

View File

@ -2,36 +2,45 @@ import struct Foundation.TimeInterval
import Dispatch
/**
- Returns: A new promise that fulfills after the specified duration.
after(seconds: 1.5).then {
//
}
- Returns: A guarantee that resolves after the specified duration.
*/
@available(*, deprecated: 4.3, message: "Use after(seconds:)")
public func after(interval: TimeInterval) -> Promise<Void> {
return after(seconds: interval)
public func after(seconds: TimeInterval) -> Guarantee<Void> {
let (rg, seal) = Guarantee<Void>.pending()
let when = DispatchTime.now() + seconds
#if swift(>=4.0)
q.asyncAfter(deadline: when) { seal(()) }
#else
q.asyncAfter(deadline: when, execute: seal)
#endif
return rg
}
/**
after(.seconds(2)).then {
//
}
- Returns: A new promise that fulfills after the specified duration.
- Returns: A guarantee that resolves after the specified duration.
*/
public func after(seconds: TimeInterval) -> Promise<Void> {
return Promise { fulfill, _ in
let when = DispatchTime.now() + seconds
DispatchQueue.global().asyncAfter(deadline: when) { fulfill(()) }
}
public func after(_ interval: DispatchTimeInterval) -> Guarantee<Void> {
let (rg, seal) = Guarantee<Void>.pending()
let when = DispatchTime.now() + interval
#if swift(>=4.0)
q.asyncAfter(deadline: when) { seal(()) }
#else
q.asyncAfter(deadline: when, execute: seal)
#endif
return rg
}
/**
- Returns: A new promise that fulfills after the specified duration.
*/
public func after(interval: DispatchTimeInterval) -> Promise<Void> {
return Promise { fulfill, _ in
let when = DispatchTime.now() + interval
#if swift(>=4.0)
DispatchQueue.global().asyncAfter(deadline: when) { fulfill(()) }
#else
DispatchQueue.global().asyncAfter(deadline: when, execute: fulfill)
#endif
private var q: DispatchQueue {
if #available(macOS 10.10, iOS 8.0, tvOS 9.0, watchOS 2.0, *) {
return DispatchQueue.global(qos: .default)
} else {
return DispatchQueue.global(priority: .default)
}
}

39
Sources/firstly.swift Normal file
View File

@ -0,0 +1,39 @@
import Dispatch
/**
Judicious use of `firstly` *may* make chains more readable.
Compare:
URLSession.shared.dataTask(url: url1).then {
URLSession.shared.dataTask(url: url2)
}.then {
URLSession.shared.dataTask(url: url3)
}
With:
firstly {
URLSession.shared.dataTask(url: url1)
}.then {
URLSession.shared.dataTask(url: url2)
}.then {
URLSession.shared.dataTask(url: url3)
}
- Note: the block you pass executes immediately on the current thread/queue.
*/
public func firstly<U: Thenable>(execute body: () throws -> U) -> Promise<U.T> {
do {
let rp = Promise<U.T>(.pending)
try body().pipe(to: rp.box.seal)
return rp
} catch {
return Promise(error: error)
}
}
/// - See: firstly()
public func firstly<T>(execute body: () -> Guarantee<T>) -> Guarantee<T> {
return body()
}

View File

@ -10,7 +10,6 @@ extern NSString * __nonnull const PMKErrorDomain;
#define PMKUnexpectedError 1l
#define PMKInvalidUsageError 3l
#define PMKAccessDeniedError 4l
#define PMKOperationCancelled 5l
#define PMKOperationFailed 8l
#define PMKTaskError 9l
#define PMKJoinError 10l
@ -117,51 +116,6 @@ extern id __nullable PMKHang(AnyPromise * __nonnull promise);
/**
Sets the unhandled exception handler.
If an exception is thrown inside an AnyPromise handler it is caught and
this handler is executed to determine if the promise is rejected.
The default handler rejects the promise if an NSError or an NSString is
thrown.
The default handler in PromiseKit 1.x would reject whatever object was
thrown (including nil).
@warning *Important* This handler is provided to allow you to customize
which exceptions cause rejection and which abort. You should either
return a fully-formed NSError object or nil. Returning nil causes the
exception to be re-thrown.
@warning *Important* The handler is executed on an undefined queue.
@warning *Important* This function is thread-safe, but to facilitate this
it can only be called once per application lifetime and it must be called
before any promise in the app throws an exception. Subsequent calls will
silently fail.
*/
extern void PMKSetUnhandledExceptionHandler(NSError * __nullable (^__nonnull handler)(id __nullable));
/**
If an error cascades through a promise chain and is not handled by any
`catch`, the unhandled error handler is called. The default logs all
non-cancelled errors.
This handler can only be set once, and must be set before any promises
are rejected in your application.
PMKSetUnhandledErrorHandler({ error in
mylogf("Unhandled error: \(error)")
})
- Warning: *Important* The handler is executed on an undefined queue.
- Warning: *Important* Dont use promises in your handler, or you risk an infinite error loop.
*/
extern void PMKSetUnhandledErrorHandler(void (^__nonnull handler)(NSError * __nonnull));
extern void PMKUnhandledErrorHandler(NSError * __nonnull error);
/**
Executes the provided block on a background queue.
@ -201,40 +155,11 @@ extern AnyPromise * __nonnull dispatch_promise(id __nonnull block) NS_SWIFT_UNAV
*/
extern AnyPromise * __nonnull dispatch_promise_on(dispatch_queue_t __nonnull queue, id __nonnull block) NS_SWIFT_UNAVAILABLE("Use our `DispatchQueue.async` override instead");
#define PMKJSONDeserializationOptions ((NSJSONReadingOptions)(NSJSONReadingAllowFragments | NSJSONReadingMutableContainers))
/**
Really we shouldnt assume JSON for (application|text)/(x-)javascript,
really we should return a String of Javascript. However in practice
for the apps we write it *will be* JSON. Thus if you actually want
a Javascript String, use the promise variant of our category functions.
Returns a new promise that resolves when the value of the first resolved promise in the provided array of promises.
*/
#define PMKHTTPURLResponseIsJSON(rsp) [@[@"application/json", @"text/json", @"text/javascript", @"application/x-javascript", @"application/javascript"] containsObject:[rsp MIMEType]]
#define PMKHTTPURLResponseIsImage(rsp) [@[@"image/tiff", @"image/jpeg", @"image/gif", @"image/png", @"image/ico", @"image/x-icon", @"image/bmp", @"image/x-bmp", @"image/x-xbitmap", @"image/x-win-bitmap"] containsObject:[rsp MIMEType]]
#define PMKHTTPURLResponseIsText(rsp) [[rsp MIMEType] hasPrefix:@"text/"]
/**
The default queue for all calls to `then`, `catch` etc. is the main queue.
By default this returns dispatch_get_main_queue()
*/
extern __nonnull dispatch_queue_t PMKDefaultDispatchQueue(void) NS_REFINED_FOR_SWIFT;
/**
You may alter the default dispatch queue, but you may only alter it once, and you must alter it before any `then`, etc. calls are made in your app.
The primary motivation for this function is so that your tests can operate off the main thread preventing dead-locking, or with `zalgo` to speed them up.
*/
extern void PMKSetDefaultDispatchQueue(__nonnull dispatch_queue_t) NS_REFINED_FOR_SWIFT;
extern AnyPromise * __nonnull PMKRace(NSArray * __nonnull promises) NS_REFINED_FOR_SWIFT;
#ifdef __cplusplus
} // Extern C
#endif
typedef NS_OPTIONS(NSInteger, PMKAnimationOptions) {
PMKAnimationOptionsNone = 1 << 0,
PMKAnimationOptionsAppear = 1 << 1,
PMKAnimationOptionsDisappear = 1 << 2,
};

View File

@ -15,7 +15,7 @@ id PMKHang(AnyPromise *promise) {
CFRunLoopSourceRef runLoopSource = CFRunLoopSourceCreate(NULL, 0, &context);
CFRunLoopAddSource(runLoop, runLoopSource, kCFRunLoopDefaultMode);
promise.always(^{
promise.ensure(^{
CFRunLoopStop(runLoop);
});
while (promise.pending) {

55
Sources/hang.swift Normal file
View File

@ -0,0 +1,55 @@
import Foundation
import CoreFoundation
/**
Runs the active run-loop until the provided promise resolves.
This is for debug and is not a generally safe function to use in your applications. We mostly provide it for use in testing environments.
Still if you like, study how it works (by reading the sources!) and use at your own risk.
- Returns: The value of the resolved promise
- Throws: An error, should the promise be rejected
- See: `wait()`
*/
public func hang<T>(_ promise: Promise<T>) throws -> T {
#if os(Linux) || os(Android)
#if swift(>=4.2)
let runLoopMode: CFRunLoopMode = kCFRunLoopDefaultMode
#else
// isMainThread is not yet implemented on Linux.
let runLoopModeRaw = RunLoopMode.defaultRunLoopMode.rawValue._bridgeToObjectiveC()
let runLoopMode: CFString = unsafeBitCast(runLoopModeRaw, to: CFString.self)
#endif
#else
guard Thread.isMainThread else {
// hang doesn't make sense on threads that aren't the main thread.
// use `.wait()` on those threads.
fatalError("Only call hang() on the main thread.")
}
let runLoopMode: CFRunLoopMode = CFRunLoopMode.defaultMode
#endif
if promise.isPending {
var context = CFRunLoopSourceContext()
let runLoop = CFRunLoopGetCurrent()
let runLoopSource = CFRunLoopSourceCreate(nil, 0, &context)
CFRunLoopAddSource(runLoop, runLoopSource, runLoopMode)
_ = promise.ensure {
CFRunLoopStop(runLoop)
}
while promise.isPending {
CFRunLoopRun()
}
CFRunLoopRemoveSource(runLoop, runLoopSource, runLoopMode)
}
switch promise.result! {
case .rejected(let error):
throw error
case .fulfilled(let value):
return value
}
}

View File

@ -1,60 +0,0 @@
import Dispatch
/**
Waits on all provided promises.
`when` rejects as soon as one of the provided promises rejects. `join` waits on all provided promises, then rejects if any of those promises rejected, otherwise it fulfills with values from the provided promises.
join(promise1, promise2, promise3).then { results in
//
}.catch { error in
switch error {
case Error.Join(let promises):
//
}
}
- Returns: A new promise that resolves once all the provided promises resolve.
- SeeAlso: `PromiseKit.Error.join`
*/
@available(*, deprecated: 4.0, message: "Use when(resolved:)")
public func join<T>(_ promises: Promise<T>...) -> Promise<[T]> {
return join(promises)
}
/// Waits on all provided promises.
@available(*, deprecated: 4.0, message: "Use when(resolved:)")
public func join(_ promises: [Promise<Void>]) -> Promise<Void> {
return join(promises).then(on: zalgo) { (_: [Void]) in return Promise(value: ()) }
}
/// Waits on all provided promises.
@available(*, deprecated: 4.0, message: "Use when(resolved:)")
public func join<T>(_ promises: [Promise<T>]) -> Promise<[T]> {
guard !promises.isEmpty else { return Promise(value: []) }
var countdown = promises.count
let barrier = DispatchQueue(label: "org.promisekit.barrier.join", attributes: .concurrent)
var rejected = false
return Promise { fulfill, reject in
for promise in promises {
promise.state.pipe { resolution in
barrier.sync(flags: .barrier) {
if case .rejected(_, let token) = resolution {
token.consumed = true // the parent Error.Join consumes all
rejected = true
}
countdown -= 1
if countdown == 0 {
if rejected {
reject(PMKError.join(promises))
} else {
fulfill(promises.map{ $0.value! })
}
}
}
}
}
}
}

9
Sources/race.m Normal file
View File

@ -0,0 +1,9 @@
#import "AnyPromise+Private.h"
AnyPromise *PMKRace(NSArray *promises) {
return [AnyPromise promiseWithResolverBlock:^(PMKResolver resolve) {
for (AnyPromise *promise in promises) {
[promise __pipe:resolve];
}
}];
}

View File

@ -1,40 +1,57 @@
/**
Resolves with the first resolving promise from a set of promises.
race(promise1, promise2, promise3).then { winner in
//
}
- Returns: A new promise that resolves when the first promise in the provided promises resolves.
- Warning: If any of the provided promises reject, the returned promise is rejected.
- Warning: aborts if the array is empty.
*/
public func race<T>(promises: [Promise<T>]) -> Promise<T> {
guard promises.count > 0 else {
fatalError("Cannot race with an empty array of promises")
@inline(__always)
private func _race<U: Thenable>(_ thenables: [U]) -> Promise<U.T> {
let rp = Promise<U.T>(.pending)
for thenable in thenables {
thenable.pipe(to: rp.box.seal)
}
return _race(promises: promises)
return rp
}
/**
Resolves with the first resolving promise from a set of promises.
Waits for one promise to resolve
race(promise1, promise2, promise3).then { winner in
//
}
- Returns: A new promise that resolves when the first promise in the provided promises resolves.
- Warning: If any of the provided promises reject, the returned promise is rejected.
- Warning: aborts if the array is empty.
- Returns: The promise that resolves first
- Warning: If the first resolution is a rejection, the returned promise is rejected
*/
public func race<T>(_ promises: Promise<T>...) -> Promise<T> {
return _race(promises: promises)
public func race<U: Thenable>(_ thenables: U...) -> Promise<U.T> {
return _race(thenables)
}
private func _race<T>(promises: [Promise<T>]) -> Promise<T> {
return Promise(sealant: { resolve in
for promise in promises {
promise.state.pipe(resolve)
}
})
/**
Waits for one promise to resolve
race(promise1, promise2, promise3).then { winner in
//
}
- Returns: The promise that resolves first
- Warning: If the first resolution is a rejection, the returned promise is rejected
- Remark: If the provided array is empty the returned promise is rejected with PMKError.badInput
*/
public func race<U: Thenable>(_ thenables: [U]) -> Promise<U.T> {
guard !thenables.isEmpty else {
return Promise(error: PMKError.badInput)
}
return _race(thenables)
}
/**
Waits for one guarantee to resolve
race(promise1, promise2, promise3).then { winner in
//
}
- Returns: The guarantee that resolves first
*/
public func race<T>(_ guarantees: Guarantee<T>...) -> Guarantee<T> {
let rg = Guarantee<T>(.pending)
for guarantee in guarantees {
guarantee.pipe(to: rg.box.seal)
}
return rg
}

View File

@ -6,6 +6,11 @@
@import Foundation.NSNull;
#import "PromiseKit.h"
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
// ^^ OSAtomicDecrement32 is deprecated on watchOS
// NSProgress resources:
// * https://robots.thoughtbot.com/asynchronous-nsprogress
// * http://oleb.net/blog/2014/03/nsprogress/
@ -67,7 +72,7 @@ AnyPromise *PMKWhen(id promises) {
if (IsError(value)) {
progress.completedUnitCount = progress.totalUnitCount;
NSMutableDictionary *userInfo = [NSMutableDictionary dictionaryWithDictionary:[value userInfo] ?: @{}];
NSMutableDictionary *userInfo = [NSMutableDictionary dictionaryWithDictionary:[(NSError *)value userInfo] ?: @{}];
userInfo[PMKFailingPromiseIndexKey] = key;
[userInfo setObject:value forKey:NSUnderlyingErrorKey];
id err = [[NSError alloc] initWithDomain:[value domain] code:[value code] userInfo:userInfo];
@ -98,3 +103,5 @@ AnyPromise *PMKWhen(id promises) {
}
}];
}
#pragma GCC diagnostic pop

View File

@ -1,55 +1,46 @@
import Foundation
import Dispatch
private func _when<T>(_ promises: [Promise<T>]) -> Promise<Void> {
let root = Promise<Void>.pending()
var countdown = promises.count
private func _when<U: Thenable>(_ thenables: [U]) -> Promise<Void> {
var countdown = thenables.count
guard countdown > 0 else {
#if swift(>=4.0)
root.fulfill(())
#else
root.fulfill()
#endif
return root.promise
return .value(Void())
}
let rp = Promise<Void>(.pending)
#if PMKDisableProgress || os(Linux)
var progress: (completedUnitCount: Int, totalUnitCount: Int) = (0, 0)
#else
let progress = Progress(totalUnitCount: Int64(promises.count))
let progress = Progress(totalUnitCount: Int64(thenables.count))
progress.isCancellable = false
progress.isPausable = false
#endif
let barrier = DispatchQueue(label: "org.promisekit.barrier.when", attributes: .concurrent)
for promise in promises {
promise.state.pipe { resolution in
for promise in thenables {
promise.pipe { result in
barrier.sync(flags: .barrier) {
switch resolution {
case .rejected(let error, let token):
token.consumed = true
if root.promise.isPending {
switch result {
case .rejected(let error):
if rp.isPending {
progress.completedUnitCount = progress.totalUnitCount
root.reject(error)
rp.box.seal(.rejected(error))
}
case .fulfilled:
guard root.promise.isPending else { return }
guard rp.isPending else { return }
progress.completedUnitCount += 1
countdown -= 1
if countdown == 0 {
#if swift(>=4.0)
root.fulfill(())
#else
root.fulfill()
#endif
rp.box.seal(.fulfilled(()))
}
}
}
}
}
return root.promise
return rp
}
/**
@ -75,38 +66,38 @@ private func _when<T>(_ promises: [Promise<T>]) -> Promise<Void> {
- Note: `when` provides `NSProgress`.
- SeeAlso: `when(resolved:)`
*/
public func when<T>(fulfilled promises: [Promise<T>]) -> Promise<[T]> {
return _when(promises).then(on: zalgo) { promises.map{ $0.value! } }
public func when<U: Thenable>(fulfilled thenables: [U]) -> Promise<[U.T]> {
return _when(thenables).map(on: nil) { thenables.map{ $0.value! } }
}
/// Wait for all promises in a set to fulfill.
public func when(fulfilled promises: Promise<Void>...) -> Promise<Void> {
public func when<U: Thenable>(fulfilled promises: U...) -> Promise<Void> where U.T == Void {
return _when(promises)
}
/// Wait for all promises in a set to fulfill.
public func when(fulfilled promises: [Promise<Void>]) -> Promise<Void> {
public func when<U: Thenable>(fulfilled promises: [U]) -> Promise<Void> where U.T == Void {
return _when(promises)
}
/// Wait for all promises in a set to fulfill.
public func when<U, V>(fulfilled pu: Promise<U>, _ pv: Promise<V>) -> Promise<(U, V)> {
return _when([pu.asVoid(), pv.asVoid()]).then(on: zalgo) { (pu.value!, pv.value!) }
public func when<U: Thenable, V: Thenable>(fulfilled pu: U, _ pv: V) -> Promise<(U.T, V.T)> {
return _when([pu.asVoid(), pv.asVoid()]).map(on: nil) { (pu.value!, pv.value!) }
}
/// Wait for all promises in a set to fulfill.
public func when<U, V, W>(fulfilled pu: Promise<U>, _ pv: Promise<V>, _ pw: Promise<W>) -> Promise<(U, V, W)> {
return _when([pu.asVoid(), pv.asVoid(), pw.asVoid()]).then(on: zalgo) { (pu.value!, pv.value!, pw.value!) }
public func when<U: Thenable, V: Thenable, W: Thenable>(fulfilled pu: U, _ pv: V, _ pw: W) -> Promise<(U.T, V.T, W.T)> {
return _when([pu.asVoid(), pv.asVoid(), pw.asVoid()]).map(on: nil) { (pu.value!, pv.value!, pw.value!) }
}
/// Wait for all promises in a set to fulfill.
public func when<U, V, W, X>(fulfilled pu: Promise<U>, _ pv: Promise<V>, _ pw: Promise<W>, _ px: Promise<X>) -> Promise<(U, V, W, X)> {
return _when([pu.asVoid(), pv.asVoid(), pw.asVoid(), px.asVoid()]).then(on: zalgo) { (pu.value!, pv.value!, pw.value!, px.value!) }
public func when<U: Thenable, V: Thenable, W: Thenable, X: Thenable>(fulfilled pu: U, _ pv: V, _ pw: W, _ px: X) -> Promise<(U.T, V.T, W.T, X.T)> {
return _when([pu.asVoid(), pv.asVoid(), pw.asVoid(), px.asVoid()]).map(on: nil) { (pu.value!, pv.value!, pw.value!, px.value!) }
}
/// Wait for all promises in a set to fulfill.
public func when<U, V, W, X, Y>(fulfilled pu: Promise<U>, _ pv: Promise<V>, _ pw: Promise<W>, _ px: Promise<X>, _ py: Promise<Y>) -> Promise<(U, V, W, X, Y)> {
return _when([pu.asVoid(), pv.asVoid(), pw.asVoid(), px.asVoid(), py.asVoid()]).then(on: zalgo) { (pu.value!, pv.value!, pw.value!, px.value!, py.value!) }
public func when<U: Thenable, V: Thenable, W: Thenable, X: Thenable, Y: Thenable>(fulfilled pu: U, _ pv: V, _ pw: W, _ px: X, _ py: Y) -> Promise<(U.T, V.T, W.T, X.T, Y.T)> {
return _when([pu.asVoid(), pv.asVoid(), pw.asVoid(), px.asVoid(), py.asVoid()]).map(on: nil) { (pu.value!, pv.value!, pw.value!, px.value!, py.value!) }
}
/**
@ -125,30 +116,32 @@ public func when<U, V, W, X, Y>(fulfilled pu: Promise<U>, _ pv: Promise<V>, _ pw
guard url = urlGenerator.next() else {
return nil
}
return downloadFile(url)
}
when(generator, concurrently: 3).then { datum: [Data] -> Void in
when(generator, concurrently: 3).done { datas in
// ...
}
No more than three downloads will occur simultaneously.
- Note: The generator is called *serially* on a *background* queue.
- Warning: Refer to the warnings on `when(fulfilled:)`
- Parameter promiseGenerator: Generator of promises.
- Returns: A new promise that resolves when all the provided promises fulfill or one of the provided promises rejects.
- SeeAlso: `when(resolved:)`
*/
public func when<T, PromiseIterator: IteratorProtocol>(fulfilled promiseIterator: PromiseIterator, concurrently: Int) -> Promise<[T]> where PromiseIterator.Element == Promise<T> {
public func when<It: IteratorProtocol>(fulfilled promiseIterator: It, concurrently: Int) -> Promise<[It.Element.T]> where It.Element: Thenable {
guard concurrently > 0 else {
return Promise(error: PMKError.whenConcurrentlyZero)
return Promise(error: PMKError.badInput)
}
var generator = promiseIterator
var root = Promise<[T]>.pending()
let root = Promise<[It.Element.T]>.pending()
var pendingPromises = 0
var promises: [Promise<T>] = []
var promises: [It.Element] = []
let barrier = DispatchQueue(label: "org.promisekit.barrier.when", attributes: [.concurrent])
@ -161,15 +154,11 @@ public func when<T, PromiseIterator: IteratorProtocol>(fulfilled promiseIterator
}
guard shouldDequeue else { return }
var index: Int!
var promise: Promise<T>!
var promise: It.Element!
barrier.sync(flags: .barrier) {
guard let next = generator.next() else { return }
promise = next
index = promises.count
pendingPromises += 1
promises.append(next)
}
@ -177,7 +166,11 @@ public func when<T, PromiseIterator: IteratorProtocol>(fulfilled promiseIterator
func testDone() {
barrier.sync {
if pendingPromises == 0 {
root.fulfill(promises.flatMap{ $0.value })
#if !swift(>=3.3) || (swift(>=4) && !swift(>=4.1))
root.resolver.fulfill(promises.flatMap{ $0.value })
#else
root.resolver.fulfill(promises.compactMap{ $0.value })
#endif
}
}
}
@ -186,7 +179,7 @@ public func when<T, PromiseIterator: IteratorProtocol>(fulfilled promiseIterator
return testDone()
}
promise.state.pipe { resolution in
promise.pipe { resolution in
barrier.sync(flags: .barrier) {
pendingPromises -= 1
}
@ -195,9 +188,8 @@ public func when<T, PromiseIterator: IteratorProtocol>(fulfilled promiseIterator
case .fulfilled:
dequeue()
testDone()
case .rejected(let error, let token):
token.consumed = true
root.reject(error)
case .rejected(let error):
root.resolver.reject(error)
}
}
@ -212,7 +204,7 @@ public func when<T, PromiseIterator: IteratorProtocol>(fulfilled promiseIterator
/**
Waits on all provided promises.
`when(fulfilled:)` rejects as soon as one of the provided promises rejects. `when(resolved:)` waits on all provided promises and **never** rejects.
`when(fulfilled:)` rejects as soon as one of the provided promises rejects. `when(resolved:)` waits on all provided promises whatever their result, and then provides an array of `Result<T>` so you can individually inspect the results. As a consequence this function returns a `Guarantee`, ie. errors are lifted from the individual promises into the results array of the returned `Guarantee`.
when(resolved: promise1, promise2, promise3).then { results in
for result in results where case .fulfilled(let value) {
@ -222,36 +214,45 @@ public func when<T, PromiseIterator: IteratorProtocol>(fulfilled promiseIterator
// invalid! Never rejects
}
- Returns: A new promise that resolves once all the provided promises resolve.
- Warning: The returned promise can *not* be rejected.
- Note: Any promises that error are implicitly consumed, your UnhandledErrorHandler will not be called.
- Returns: A new promise that resolves once all the provided promises resolve. The array is ordered the same as the input, ie. the result order is *not* resolution order.
- Note: we do not provide tuple variants for `when(resolved:)` but will accept a pull-request
- Remark: Doesn't take Thenable due to protocol `associatedtype` paradox
*/
public func when<T>(resolved promises: Promise<T>...) -> Promise<[Result<T>]> {
public func when<T>(resolved promises: Promise<T>...) -> Guarantee<[Result<T>]> {
return when(resolved: promises)
}
/// Waits on all provided promises.
public func when<T>(resolved promises: [Promise<T>]) -> Promise<[Result<T>]> {
guard !promises.isEmpty else { return Promise(value: []) }
/// - See: `when(resolved: Promise<T>...)`
public func when<T>(resolved promises: [Promise<T>]) -> Guarantee<[Result<T>]> {
guard !promises.isEmpty else {
return .value([])
}
var countdown = promises.count
let barrier = DispatchQueue(label: "org.promisekit.barrier.join", attributes: .concurrent)
return Promise { fulfill, reject in
for promise in promises {
promise.state.pipe { resolution in
if case .rejected(_, let token) = resolution {
token.consumed = true // all errors are implicitly consumed
}
var done = false
barrier.sync(flags: .barrier) {
countdown -= 1
done = countdown == 0
}
if done {
fulfill(promises.map { Result($0.state.get()!) })
let rg = Guarantee<[Result<T>]>(.pending)
for promise in promises {
promise.pipe { result in
barrier.sync(flags: .barrier) {
countdown -= 1
}
barrier.sync {
if countdown == 0 {
rg.box.seal(promises.map{ $0.result! })
}
}
}
}
return rg
}
/// Waits on all provided Guarantees.
public func when(_ guarantees: Guarantee<Void>...) -> Guarantee<Void> {
return when(guarantees: guarantees)
}
// Waits on all provided Guarantees.
public func when(guarantees: [Guarantee<Void>]) -> Guarantee<Void> {
return when(fulfilled: guarantees).recover{ _ in }.asVoid()
}

View File

@ -1,79 +0,0 @@
/**
Create a new pending promise by wrapping another asynchronous system.
This initializer is convenient when wrapping asynchronous systems that
use common patterns. For example:
func fetchKitten() -> Promise<UIImage> {
return PromiseKit.wrap { resolve in
KittenFetcher.fetchWithCompletionBlock(resolve)
}
}
- SeeAlso: Promise.init(resolvers:)
*/
public func wrap<T>(_ body: (@escaping (T?, Error?) -> Void) throws -> Void) -> Promise<T> {
return Promise { fulfill, reject in
try body { obj, err in
if let err = err {
reject(err)
} else if let obj = obj {
fulfill(obj)
} else {
reject(PMKError.invalidCallingConvention)
}
}
}
}
/// For completion-handlers that eg. provide an enum or an error.
public func wrap<T>(_ body: (@escaping (T, Error?) -> Void) throws -> Void) -> Promise<T> {
return Promise { fulfill, reject in
try body { obj, err in
if let err = err {
reject(err)
} else {
fulfill(obj)
}
}
}
}
/// Some APIs unwisely invert the Cocoa standard for completion-handlers.
public func wrap<T>(_ body: (@escaping (Error?, T?) -> Void) throws -> Void) -> Promise<T> {
return Promise { fulfill, reject in
try body { err, obj in
if let err = err {
reject(err)
} else if let obj = obj {
fulfill(obj)
} else {
reject(PMKError.invalidCallingConvention)
}
}
}
}
/// For completion-handlers with just an optional Error
public func wrap(_ body: (@escaping (Error?) -> Void) throws -> Void) -> Promise<Void> {
return Promise { fulfill, reject in
try body { error in
if let error = error {
reject(error)
} else {
#if swift(>=4.0)
fulfill(())
#else
fulfill()
#endif
}
}
}
}
/// For completions that cannot error.
public func wrap<T>(_ body: (@escaping (T) -> Void) throws -> Void) -> Promise<T> {
return Promise { fulfill, _ in
try body(fulfill)
}
}

View File

@ -1,4 +1,5 @@
import PromiseKit
import Dispatch
import XCTest
enum Error: Swift.Error {
@ -10,6 +11,9 @@ private let timeout: TimeInterval = 10
extension XCTestCase {
func describe(_ description: String, file: StaticString = #file, line: UInt = #line, body: () throws -> Void) {
PromiseKit.conf.Q.map = .main
do {
try body()
} catch {
@ -17,12 +21,12 @@ extension XCTestCase {
}
}
func specify(_ description: String, file: StaticString = #file, line: UInt = #line, body: (Promise<Void>.PendingTuple, XCTestExpectation) throws -> Void) {
func specify(_ description: String, file: StaticString = #file, line: UInt = #line, body: ((promise: Promise<Void>, fulfill: () -> Void, reject: (Error) -> Void), XCTestExpectation) throws -> Void) {
let expectation = self.expectation(description: description)
let pending = Promise<Void>.pending()
let (pending, seal) = Promise<Void>.pending()
do {
try body(pending, expectation)
try body((pending, seal.fulfill_, seal.reject), expectation)
waitForExpectations(timeout: timeout) { err in
if let _ = err {
XCTFail("wait failed: \(description)", file: file, line: line)
@ -50,19 +54,19 @@ extension XCTestCase {
let specify = mkspecify(withExpectationCount, file: file, line: line, body: body)
specify("already-fulfilled") { value in
return (Promise(value: value), {})
return (.value(value), {})
}
specify("immediately-fulfilled") { value in
let (promise, fulfill, _) = Promise<UInt32>.pending()
let (promise, seal) = Promise<UInt32>.pending()
return (promise, {
fulfill(value)
seal.fulfill(value)
})
}
specify("eventually-fulfilled") { value in
let (promise, fulfill, _) = Promise<UInt32>.pending()
let (promise, seal) = Promise<UInt32>.pending()
return (promise, {
after(ticks: 5) {
fulfill(value)
seal.fulfill(value)
}
})
}
@ -76,16 +80,16 @@ extension XCTestCase {
return (Promise(error: Error.sentinel(sentinel)), {})
}
specify("immediately-rejected") { sentinel in
let (promise, _, reject) = Promise<UInt32>.pending()
let (promise, seal) = Promise<UInt32>.pending()
return (promise, {
reject(Error.sentinel(sentinel))
seal.reject(Error.sentinel(sentinel))
})
}
specify("eventually-rejected") { sentinel in
let (promise, _, reject) = Promise<UInt32>.pending()
let (promise, seal) = Promise<UInt32>.pending()
return (promise, {
after(ticks: 50) {
reject(Error.sentinel(sentinel))
seal.reject(Error.sentinel(sentinel))
}
})
}
@ -144,7 +148,7 @@ extension Promise {
case .rejected:
onRejected()
}
}
}.silenceWarning()
}
}
@ -152,3 +156,30 @@ prefix func ++(a: inout Int) -> Int {
a += 1
return a
}
extension Promise {
func silenceWarning() {}
}
#if os(Linux)
import func Glibc.random
func arc4random() -> UInt32 {
return UInt32(random())
}
extension XCTestExpectation {
func fulfill() {
fulfill(#file, line: #line)
}
}
extension XCTestCase {
func wait(for: [XCTestExpectation], timeout: TimeInterval, file: StaticString = #file, line: UInt = #line) {
#if !(swift(>=4.0) && !swift(>=4.1))
let line = Int(line)
#endif
waitForExpectations(timeout: timeout, file: file, line: line)
}
}
#endif

View File

@ -10,14 +10,14 @@ class Test212: XCTestCase {
specify("trying to fulfill then immediately reject") { d, expectation in
d.promise.test(onFulfilled: expectation.fulfill, onRejected: { XCTFail() })
d.fulfill(())
d.fulfill()
d.reject(Error.dummy)
}
specify("trying to fulfill then reject, delayed") { d, expectation in
d.promise.test(onFulfilled: expectation.fulfill, onRejected: { XCTFail() })
after(ticks: 1) {
d.fulfill(())
d.fulfill()
d.reject(Error.dummy)
}
}

View File

@ -11,14 +11,14 @@ class Test213: XCTestCase {
specify("trying to reject then immediately fulfill") { d, expectation in
d.promise.test(onFulfilled: { XCTFail() }, onRejected: expectation.fulfill)
d.reject(Error.dummy)
d.fulfill(())
d.fulfill()
}
specify("trying to reject then fulfill, delayed") { d, expectation in
d.promise.test(onFulfilled: { XCTFail() }, onRejected: expectation.fulfill)
after(ticks: 1) {
d.reject(Error.dummy)
d.fulfill(())
d.fulfill()
}
}
@ -26,7 +26,7 @@ class Test213: XCTestCase {
d.promise.test(onFulfilled: { XCTFail() }, onRejected: expectation.fulfill)
d.reject(Error.dummy)
after(ticks: 1) {
d.fulfill(())
d.fulfill()
}
}
}

View File

@ -6,27 +6,27 @@ class Test222: XCTestCase {
describe("2.2.2: If `onFulfilled` is a function,") {
describe("2.2.2.1: it must be called after `promise` is fulfilled, with `promise`s fulfillment value as its first argument.") {
testFulfilled { promise, expectation, sentinel in
promise.then { value -> Void in
XCTAssertEqual(sentinel, value)
promise.done {
XCTAssertEqual(sentinel, $0)
expectation.fulfill()
}
}.silenceWarning()
}
}
describe("2.2.2.2: it must not be called before `promise` is fulfilled") {
specify("fulfilled after a delay") { d, expectation in
var called = false
d.promise.then { _ -> Void in
d.promise.done {
called = true
expectation.fulfill()
}
}.silenceWarning()
after(ticks: 5) {
XCTAssertFalse(called)
d.fulfill(())
d.fulfill()
}
}
specify("never fulfilled") { d, expectation in
d.promise.then{ XCTFail() }
d.promise.done{ XCTFail() }.silenceWarning()
after(ticks: 1000, execute: expectation.fulfill)
}
}
@ -34,57 +34,57 @@ class Test222: XCTestCase {
describe("2.2.2.3: it must not be called more than once.") {
specify("already-fulfilled") { _, expectation in
let ex = (expectation, mkex())
Promise(value: ()).then {
Promise().done {
ex.0.fulfill()
}
}.silenceWarning()
after(ticks: 1000) {
ex.1.fulfill()
}
}
specify("trying to fulfill a pending promise more than once, immediately") { d, expectation in
d.promise.then(execute: expectation.fulfill)
d.fulfill(())
d.fulfill(())
d.promise.done(expectation.fulfill).silenceWarning()
d.fulfill()
d.fulfill()
}
specify("trying to fulfill a pending promise more than once, delayed") { d, expectation in
d.promise.then(execute: expectation.fulfill)
d.promise.done(expectation.fulfill).silenceWarning()
after(ticks: 5) {
d.fulfill(())
d.fulfill(())
d.fulfill()
d.fulfill()
}
}
specify("trying to fulfill a pending promise more than once, immediately then delayed") { d, expectation in
let ex = (expectation, mkex())
d.promise.then(execute: ex.0.fulfill)
d.fulfill(())
d.promise.done(ex.0.fulfill).silenceWarning()
d.fulfill()
after(ticks: 5) {
d.fulfill(())
d.fulfill()
}
after(ticks: 10, execute: ex.1.fulfill)
}
specify("when multiple `then` calls are made, spaced apart in time") { d, expectation in
var ex = (expectation, self.expectation(description: ""), self.expectation(description: ""), self.expectation(description: ""))
let ex = (expectation, self.expectation(description: ""), self.expectation(description: ""), self.expectation(description: ""))
do {
d.promise.then(execute: ex.0.fulfill)
d.promise.done(ex.0.fulfill).silenceWarning()
}
after(ticks: 5) {
d.promise.then(execute: ex.1.fulfill)
d.promise.done(ex.1.fulfill).silenceWarning()
}
after(ticks: 10) {
d.promise.then(execute: ex.2.fulfill)
d.promise.done(ex.2.fulfill).silenceWarning()
}
after(ticks: 15) {
d.fulfill(())
d.fulfill()
ex.3.fulfill()
}
}
specify("when `then` is interleaved with fulfillment") { d, expectation in
var ex = (expectation, self.expectation(description: ""), self)
let ex = (expectation, self.expectation(description: ""), self)
d.promise.then(execute: ex.0.fulfill)
d.fulfill(())
d.promise.then(execute: ex.1.fulfill)
d.promise.done(ex.0.fulfill).silenceWarning()
d.fulfill()
d.promise.done(ex.1.fulfill).silenceWarning()
}
}
}

View File

@ -45,7 +45,6 @@ class Test223: XCTestCase {
}
}
specify("trying to reject a pending promise more than once, immediately") { d, expectation in
var timesCalled = 0
d.promise.catch{_ in expectation.fulfill() }
d.reject(Error.dummy)
d.reject(Error.dummy)
@ -66,7 +65,7 @@ class Test223: XCTestCase {
}
specify("when multiple `then` calls are made, spaced apart in time") { d, expectation in
let mk = { self.expectation(description: "") }
var ex = (expectation, mk(), mk(), mk())
let ex = (expectation, mk(), mk(), mk())
do {
d.promise.catch{ _ in ex.0.fulfill() }
@ -83,7 +82,7 @@ class Test223: XCTestCase {
}
}
specify("when `then` is interleaved with rejection") { d, expectation in
var ex = (expectation, self.expectation(description: ""))
let ex = (expectation, self.expectation(description: ""))
d.promise.catch{ _ in ex.0.fulfill() }
d.reject(Error.dummy)
d.promise.catch{ _ in ex.1.fulfill() }

View File

@ -8,10 +8,10 @@ class Test224: XCTestCase {
describe("`then` returns before the promise becomes fulfilled or rejected") {
testFulfilled { promise, expectation, dummy in
var thenHasReturned = false
promise.then { _ -> Void in
promise.done { _ in
XCTAssert(thenHasReturned)
expectation.fulfill()
}
}.silenceWarning()
thenHasReturned = true
}
testRejected { promise, expectation, memo in
@ -28,44 +28,44 @@ class Test224: XCTestCase {
describe("Clean-stack execution ordering tests (fulfillment case)") {
specify("when `onFulfilled` is added immediately before the promise is fulfilled") { d, expectation in
var onFulfilledCalled = false
d.promise.then { _ -> Void in
d.promise.done {
onFulfilledCalled = true
expectation.fulfill()
}
d.fulfill(())
}.silenceWarning()
d.fulfill()
XCTAssertFalse(onFulfilledCalled)
}
specify("when `onFulfilled` is added immediately after the promise is fulfilled") { d, expectation in
var onFulfilledCalled = false
d.fulfill(())
d.promise.then { _ -> Void in
d.fulfill()
d.promise.done {
onFulfilledCalled = true
expectation.fulfill()
}
}.silenceWarning()
XCTAssertFalse(onFulfilledCalled)
}
specify("when one `onFulfilled` is added inside another `onFulfilled`") { _, expectation in
var firstOnFulfilledFinished = false
let promise = Promise(value: ())
promise.then { _ -> Void in
promise.then { _ -> Void in
let promise = Promise()
promise.done {
promise.done {
XCTAssertTrue(firstOnFulfilledFinished)
expectation.fulfill()
}
}.silenceWarning()
firstOnFulfilledFinished = true
}
}.silenceWarning()
}
specify("when `onFulfilled` is added inside an `onRejected`") { _, expectation in
var promise1 = Promise<Void>(error: Error.dummy)
var promise2 = Promise(value: ())
let promise1 = Promise<Void>(error: Error.dummy)
let promise2 = Promise()
var firstOnRejectedFinished = false
promise1.catch { _ in
promise2.then { _ -> Void in
promise2.done {
XCTAssertTrue(firstOnRejectedFinished)
expectation.fulfill()
}
}.silenceWarning()
firstOnRejectedFinished = true
}
}
@ -74,14 +74,14 @@ class Test224: XCTestCase {
var firstStackFinished = false
after(ticks: 1) {
d.fulfill(())
d.fulfill()
firstStackFinished = true
}
d.promise.then { _ -> Void in
d.promise.done {
XCTAssertTrue(firstStackFinished)
expectation.fulfill()
}
}.silenceWarning()
}
}
@ -105,20 +105,20 @@ class Test224: XCTestCase {
XCTAssertFalse(onRejectedCalled)
}
specify("when `onRejected` is added inside an `onFulfilled`") { d, expectation in
var promise1 = Promise(value: ())
var promise2 = Promise<Void>(error: Error.dummy)
let promise1 = Promise()
let promise2 = Promise<Void>(error: Error.dummy)
var firstOnFulfilledFinished = false
promise1.then { _ -> Void in
promise1.done { _ in
promise2.catch { _ in
XCTAssertTrue(firstOnFulfilledFinished)
expectation.fulfill()
}
firstOnFulfilledFinished = true
}
}.silenceWarning()
}
specify("when one `onRejected` is added inside another `onRejected`") { d, expectation in
var promise = Promise<Void>(error: Error.dummy)
let promise = Promise<Void>(error: Error.dummy)
var firstOnRejectedFinished = false;
promise.catch { _ in

View File

@ -6,126 +6,131 @@ class Test226: XCTestCase {
describe("2.2.6: `then` may be called multiple times on the same promise.") {
describe("2.2.6.1: If/when `promise` is fulfilled, all respective `onFulfilled` callbacks must execute in the order of their originating calls to `then`.") {
describe("multiple boring fulfillment handlers") {
testFulfilled(withExpectationCount: 4) { promise, exes, sentinel -> () in
testFulfilled(withExpectationCount: 4) { promise, exes, sentinel -> Void in
var orderValidator = 0
promise.then { value -> Void in
XCTAssertEqual(value, sentinel)
promise.done {
XCTAssertEqual($0, sentinel)
XCTAssertEqual(++orderValidator, 1)
exes[0].fulfill()
}
}.silenceWarning()
promise.catch { _ in XCTFail() }
promise.then { value -> Void in
XCTAssertEqual(value, sentinel)
promise.done {
XCTAssertEqual($0, sentinel)
XCTAssertEqual(++orderValidator, 2)
exes[1].fulfill()
}
}.silenceWarning()
promise.catch { _ in XCTFail() }
promise.then { value -> Void in
XCTAssertEqual(value, sentinel)
promise.done {
XCTAssertEqual($0, sentinel)
XCTAssertEqual(++orderValidator, 3)
exes[2].fulfill()
}
}.silenceWarning()
promise.catch { _ in XCTFail() }
promise.then { value -> Void in
XCTAssertEqual(value, sentinel)
promise.done {
XCTAssertEqual($0, sentinel)
XCTAssertEqual(++orderValidator, 4)
exes[3].fulfill()
}
}.silenceWarning()
}
}
describe("multiple fulfillment handlers, one of which throws") {
testFulfilled(withExpectationCount: 4) { promise, exes, sentinel in
var orderValidator = 0
promise.then { value -> Void in
XCTAssertEqual(value, sentinel)
promise.done {
XCTAssertEqual($0, sentinel)
XCTAssertEqual(++orderValidator, 1)
exes[0].fulfill()
}
}.silenceWarning()
promise.catch { _ in XCTFail() }
promise.then { value -> Void in
XCTAssertEqual(value, sentinel)
promise.done {
XCTAssertEqual($0, sentinel)
XCTAssertEqual(++orderValidator, 2)
exes[1].fulfill()
}
}.silenceWarning()
promise.catch { _ in XCTFail() }
promise.then { value -> Void in
XCTAssertEqual(value, sentinel)
promise.done {
XCTAssertEqual($0, sentinel)
XCTAssertEqual(++orderValidator, 3)
exes[2].fulfill()
throw Error.dummy
}
}.silenceWarning()
promise.catch { value in XCTFail() }
promise.then { value -> Void in
XCTAssertEqual(value, sentinel)
promise.done {
XCTAssertEqual($0, sentinel)
XCTAssertEqual(++orderValidator, 4)
exes[3].fulfill()
}
}.silenceWarning()
}
}
describe("results in multiple branching chains with their own fulfillment values") {
testFulfilled(withExpectationCount: 3) { promise, exes, memo in
let sentinel1 = 671
let sentinel2 = 672
let sentinel2: UInt32 = 672
let sentinel3 = 673
promise.then { _ -> Int in
promise.map { _ in
return sentinel1
}.then { value -> Void in
XCTAssertEqual(sentinel1, value)
exes[0].fulfill()
}.done { value in
XCTAssertEqual(sentinel1, value)
exes[0].fulfill()
}.silenceWarning()
promise.done { _ in
throw Error.sentinel(sentinel2)
}.catch { err in
switch err {
case Error.sentinel(let err) where err == sentinel2:
break
default:
XCTFail()
}
exes[1].fulfill()
}
promise.then { _ -> Int in
throw NSError(domain: PMKErrorDomain, code: sentinel2, userInfo: nil)
}.catch { err in
XCTAssertEqual((err as NSError).code, sentinel2)
exes[1].fulfill()
}
promise.then{ _ -> Int in
return sentinel3
}.then { value -> Void in
XCTAssertEqual(value, sentinel3)
exes[2].fulfill()
}
promise.map { _ in
sentinel3
}.done {
XCTAssertEqual($0, sentinel3)
exes[2].fulfill()
}.silenceWarning()
}
}
describe("`onFulfilled` handlers are called in the original order") {
testFulfilled(withExpectationCount: 3) { promise, exes, memo in
var orderValidator = 0
promise.then { _ -> Void in
promise.done { _ in
XCTAssertEqual(++orderValidator, 1)
exes[0].fulfill()
}
promise.then { _ -> Void in
}.silenceWarning()
promise.done { _ in
XCTAssertEqual(++orderValidator, 2)
exes[1].fulfill()
}
promise.then { _ -> Void in
}.silenceWarning()
promise.done { _ in
XCTAssertEqual(++orderValidator, 3)
exes[2].fulfill()
}
}.silenceWarning()
}
}
describe("even when one handler is added inside another handler") {
testFulfilled(withExpectationCount: 3) { promise, exes, memo in
var x = 0
promise.then { _ -> Void in
promise.done { _ in
XCTAssertEqual(x, 0)
x += 1
exes[0].fulfill()
promise.then{ _ -> Void in
promise.done { _ in
XCTAssertEqual(x, 2)
x += 1
exes[1].fulfill()
}
}
promise.then { _ -> Void in
}.silenceWarning()
}.silenceWarning()
promise.done { _ in
XCTAssertEqual(x, 1)
x += 1
exes[2].fulfill()
}
}.silenceWarning()
}
}
}
@ -139,19 +144,19 @@ class Test226: XCTestCase {
XCTAssertEqual(++ticket, 1)
exes[0].fulfill()
}
promise.then { _ in XCTFail() }
promise.done { _ in XCTFail() }.silenceWarning()
promise.catch { err in
guard case Error.sentinel(let x) = err, x == sentinel else { return XCTFail() }
XCTAssertEqual(++ticket, 2)
exes[1].fulfill()
}
promise.then { _ in XCTFail() }
promise.done { _ in XCTFail() }.silenceWarning()
promise.catch { err in
guard case Error.sentinel(let x) = err, x == sentinel else { return XCTFail() }
XCTAssertEqual(++ticket, 3)
exes[2].fulfill()
}
promise.then { _ in XCTFail() }
promise.done { _ in XCTFail() }.silenceWarning()
promise.catch { err in
guard case Error.sentinel(let x) = err, x == sentinel else { return XCTFail() }
XCTAssertEqual(++ticket, 4)
@ -168,20 +173,24 @@ class Test226: XCTestCase {
XCTAssertEqual(++orderValidator, 1)
exes[0].fulfill()
}
promise.then { _ in XCTFail() }
promise.done { _ in XCTFail() }.silenceWarning()
promise.catch { err in
guard case Error.sentinel(let x) = err, x == sentinel else { return XCTFail() }
XCTAssertEqual(++orderValidator, 2)
exes[1].fulfill()
}
promise.then { _ in XCTFail() }
promise.recover { err -> UInt32 in
guard case Error.sentinel(let x) = err, x == sentinel else { XCTFail(); return 123 }
promise.done { _ in XCTFail() }.silenceWarning()
promise.recover { err -> Promise<UInt32> in
if case Error.sentinel(let x) = err {
XCTAssertEqual(x, sentinel)
} else {
XCTFail()
}
XCTAssertEqual(++orderValidator, 3)
exes[2].fulfill()
throw Error.dummy
}
promise.then { _ in XCTFail() }
}.silenceWarning()
promise.done { _ in XCTFail() }.silenceWarning()
promise.catch { err in
guard case Error.sentinel(let x) = err, x == sentinel else { return XCTFail() }
XCTAssertEqual(++orderValidator, 4)
@ -195,26 +204,26 @@ class Test226: XCTestCase {
let sentinel2 = arc4random()
let sentinel3 = arc4random()
promise.recover { _ -> UInt32 in
return sentinel1
}.then { value -> Void in
XCTAssertEqual(sentinel1, value)
exes[0].fulfill()
promise.recover { _ in
return .value(sentinel1)
}.done { value in
XCTAssertEqual(sentinel1, value)
exes[0].fulfill()
}
promise.recover { _ -> UInt32 in
promise.recover { _ -> Promise<UInt32> in
throw Error.sentinel(sentinel2)
}.catch { err in
if case Error.sentinel(let x) = err, x == sentinel2 {
exes[1].fulfill()
}
}.catch { err in
if case Error.sentinel(let x) = err, x == sentinel2 {
exes[1].fulfill()
}
}
promise.recover { _ -> UInt32 in
return sentinel3
}.then { value -> Void in
XCTAssertEqual(value, sentinel3)
exes[2].fulfill()
promise.recover { _ in
.value(sentinel3)
}.done { value in
XCTAssertEqual(value, sentinel3)
exes[2].fulfill()
}
}
}

Some files were not shown because too many files have changed in this diff Show More