Compare commits

..

57 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
198 changed files with 7205 additions and 12222 deletions

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

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

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 %}])

277
.github/PromiseKit.podspec vendored Normal file
View File

@ -0,0 +1,277 @@
Pod::Spec.new do |s|
s.name = "PromiseKit"
s.version = '0.0.1'
s.source = {
:git => "https://github.com/mxcl/#{s.name}.git",
:tag => s.version,
:submodules => true
}
s.license = 'MIT'
s.summary = 'Promises for Swift & ObjC.'
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://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.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/*'
ss.ios.frameworks = ss.osx.frameworks = 'Accounts'
ss.dependency 'PromiseKit/CorePromise'
ss.ios.deployment_target = '8.0'
ss.osx.deployment_target = '10.10'
end
s.subspec 'Alamofire' do |ss|
ss.source_files = 'Extensions/Alamofire/Sources/*'
ss.dependency 'Alamofire', '~> 4.0'
ss.dependency 'PromiseKit/CorePromise'
ss.ios.deployment_target = '8.0'
ss.osx.deployment_target = '10.11'
ss.watchos.deployment_target = '2.0'
ss.tvos.deployment_target = '9.0'
end
s.subspec 'AddressBook' do |ss|
ss.ios.source_files = 'Extensions/AddressBook/Sources/*'
ss.ios.frameworks = 'AddressBook'
ss.dependency 'PromiseKit/CorePromise'
ss.ios.deployment_target = '8.0'
end
s.subspec 'AssetsLibrary' do |ss|
ss.ios.source_files = 'Extensions/AssetsLibrary/Sources/*'
ss.ios.frameworks = 'AssetsLibrary'
ss.dependency 'PromiseKit/CorePromise'
ss.ios.deployment_target = '8.0'
end
s.subspec 'AVFoundation' do |ss|
ss.ios.source_files = 'Extensions/AVFoundation/Sources/*'
ss.ios.frameworks = 'AVFoundation'
ss.dependency 'PromiseKit/CorePromise'
ss.ios.deployment_target = '8.0'
end
s.subspec 'Bolts' do |ss|
ss.source_files = 'Extensions/Bolts/Sources/*'
ss.dependency 'PromiseKit/CorePromise'
ss.dependency 'Bolts', '~> 1.9.0'
ss.ios.deployment_target = '8.0'
ss.osx.deployment_target = '10.10'
ss.watchos.deployment_target = '2.0'
ss.tvos.deployment_target = '9.0'
end
s.subspec 'CloudKit' do |ss|
ss.source_files = 'Extensions/CloudKit/Sources/*'
ss.frameworks = 'CloudKit'
ss.dependency 'PromiseKit/CorePromise'
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
s.subspec 'CoreBluetooth' do |ss|
ss.ios.source_files = ss.osx.source_files = ss.tvos.source_files = 'Extensions/CoreBluetooth/Sources/*'
ss.ios.frameworks = ss.osx.frameworks = ss.tvos.frameworks = 'CoreBluetooth'
ss.dependency 'PromiseKit/CorePromise'
ss.ios.deployment_target = '8.0'
ss.osx.deployment_target = '10.10'
ss.tvos.deployment_target = '9.0'
end
s.subspec 'CorePromise' do |ss|
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,race}.m'
cc += hh
ss.source_files = cc
ss.public_header_files = hh
ss.preserve_paths = 'Sources/AnyPromise+Private.h', 'Sources/PMKCallVariadicBlock.m', 'Sources/NSMethodSignatureForBlock.m'
ss.frameworks = 'Foundation'
ss.ios.deployment_target = '8.0'
ss.osx.deployment_target = '10.10'
ss.watchos.deployment_target = '2.0'
ss.tvos.deployment_target = '9.0'
end
s.subspec 'CoreLocation' do |ss|
ss.source_files = 'Extensions/CoreLocation/Sources/*'
ss.watchos.source_files = 'Extensions/CoreLocation/Sources/CLGeocoder*'
ss.dependency 'PromiseKit/CorePromise'
ss.frameworks = 'CoreLocation'
ss.ios.deployment_target = '8.0'
ss.osx.deployment_target = '10.10'
ss.watchos.deployment_target = '3.0'
ss.tvos.deployment_target = '9.0'
end
s.subspec 'EventKit' do |ss|
ss.ios.source_files = ss.osx.source_files = ss.watchos.source_files = 'Extensions/EventKit/Sources/*'
ss.ios.frameworks = ss.osx.frameworks = ss.watchos.frameworks = 'EventKit'
ss.dependency 'PromiseKit/CorePromise'
ss.ios.deployment_target = '8.0'
ss.osx.deployment_target = '10.10'
ss.watchos.deployment_target = '2.0'
end
s.subspec 'Foundation' do |ss|
ss.source_files = Dir['Extensions/Foundation/Sources/*']
ss.dependency 'PromiseKit/CorePromise'
ss.frameworks = 'Foundation'
ss.ios.deployment_target = '8.0'
ss.osx.deployment_target = '10.10'
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'
ss.dependency 'PromiseKit/CorePromise'
ss.ios.deployment_target = '8.0'
ss.osx.deployment_target = '10.10'
ss.watchos.deployment_target = '2.0'
ss.tvos.deployment_target = '9.2'
end
s.subspec 'MessageUI' do |ss|
ss.ios.source_files = 'Extensions/MessagesUI/Sources/*'
ss.ios.frameworks = 'MessageUI'
ss.dependency 'PromiseKit/CorePromise'
ss.ios.deployment_target = '8.0'
end
s.subspec 'OMGHTTPURLRQ' do |ss|
ss.source_files = 'Extensions/OMGHTTPURLRQ/Sources/*'
ss.dependency 'PromiseKit/Foundation'
ss.dependency 'OMGHTTPURLRQ', '~> 3.2'
ss.ios.deployment_target = '8.0'
ss.osx.deployment_target = '10.10'
ss.watchos.deployment_target = '2.0'
ss.tvos.deployment_target = '9.0'
end
s.subspec 'Photos' do |ss|
ss.ios.source_files = ss.tvos.source_files = ss.osx.source_files = 'Extensions/Photos/Sources/*'
ss.ios.frameworks = ss.tvos.frameworks = ss.osx.frameworks = 'Photos'
ss.dependency 'PromiseKit/CorePromise'
ss.ios.deployment_target = '8.0'
ss.osx.deployment_target = '10.13'
ss.tvos.deployment_target = '10.0'
end
s.subspec 'QuartzCore' do |ss|
ss.osx.source_files = ss.ios.source_files = ss.tvos.source_files = 'Extensions/QuartzCore/Sources/*'
ss.osx.frameworks = ss.ios.frameworks = ss.tvos.frameworks = 'QuartzCore'
ss.dependency 'PromiseKit/CorePromise'
ss.ios.deployment_target = '8.0'
ss.osx.deployment_target = '10.10'
ss.tvos.deployment_target = '9.0'
end
s.subspec 'Social' do |ss|
ss.ios.source_files = 'Extensions/Social/Sources/*'
ss.osx.source_files = Dir['Extensions/Social/Sources/*'] - ['Categories/Social/Sources/*SLComposeViewController+Promise.swift']
ss.ios.frameworks = ss.osx.frameworks = 'Social'
ss.dependency 'PromiseKit/Foundation'
ss.ios.deployment_target = '8.0'
ss.osx.deployment_target = '10.10'
end
s.subspec 'StoreKit' do |ss|
ss.ios.source_files = ss.osx.source_files = ss.tvos.source_files = 'Extensions/StoreKit/Sources/*'
ss.ios.frameworks = ss.osx.frameworks = ss.tvos.frameworks = 'StoreKit'
ss.dependency 'PromiseKit/CorePromise'
ss.ios.deployment_target = '8.0'
ss.osx.deployment_target = '10.10'
ss.tvos.deployment_target = '9.0'
end
s.subspec 'SystemConfiguration' do |ss|
ss.ios.source_files = ss.osx.source_files = ss.tvos.source_files = 'Extensions/SystemConfiguration/Sources/*'
ss.ios.frameworks = ss.osx.frameworks = ss.tvos.frameworks = 'SystemConfiguration'
ss.dependency 'PromiseKit/CorePromise'
ss.ios.deployment_target = '8.0'
ss.osx.deployment_target = '10.10'
ss.tvos.deployment_target = '9.0'
end
picker_cc = 'Extensions/UIKit/Sources/UIImagePickerController+Promise.swift'
s.subspec 'UIKit' do |ss|
ss.ios.source_files = ss.tvos.source_files = Dir['Extensions/UIKit/Sources/*'] - [picker_cc]
ss.tvos.frameworks = ss.ios.frameworks = 'UIKit'
ss.dependency 'PromiseKit/CorePromise'
ss.ios.deployment_target = '8.0'
ss.tvos.deployment_target = '9.0'
end
s.subspec 'UIImagePickerController' do |ss|
# Since iOS 10, App Store submissions that contain references to
# UIImagePickerController (even if unused in 3rd party libraries)
# are rejected unless an Info.plist key is specified, thus we
# moved this code to a sub-subspec.
#
# This *was* a subspec of UIKit, but bizarrely CocoaPods would
# include this when specifying *just* UIKit…!
ss.ios.source_files = picker_cc
ss.ios.frameworks = 'UIKit'
ss.ios.xcconfig = { "GCC_PREPROCESSOR_DEFINITIONS" => '$(inherited) PMKImagePickerController=1' }
ss.dependency 'PromiseKit/UIKit'
ss.ios.deployment_target = '8.0'
end
s.subspec 'WatchConnectivity' do |ss|
ss.ios.source_files = ss.watchos.source_files = 'Extensions/WatchConnectivity/Sources/*'
ss.ios.frameworks = ss.watchos.frameworks = 'WatchConnectivity'
ss.dependency 'PromiseKit/CorePromise'
ss.ios.deployment_target = '8.0'
ss.watchos.deployment_target = '2.0'
end
end

2
.github/codecov.yml vendored
View File

@ -1,7 +1,7 @@
ignore:
- "Tests"
- "README.md"
- "Documents"
- "Documentation"
- ".travis.yml"
codecov:

2
.github/jazzy.yml vendored
View File

@ -13,6 +13,6 @@ output:
../output
# output directory is relative to config file… ugh
readme:
Documents/README.md
Documentation/README.md
theme:
fullwidth

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

View File

@ -1,131 +0,0 @@
PromiseKit
NSError
ObjC
AnyPromise
AnyPromises
iOS
Xcode
CocoaPods
Submodules
Agostini.tech
1.x
2.x
3.x
4.x
5.x
6.x
7.x
8.x
9.x
10.x
Alamofire
APIs
PMKConfiguration
Kitura
RxSwift
Zalgo
macOS
watchOS
CoreLocation
CancellablePromiseKit
UIKit
tvOS
SDKs
hair-raisingly
deallocate
deallocated
initializer
initializers
work-arounds
i.e.
e.g.
SwiftPM
CorePromise
submodules
CorePromise
tuplegate
Swift-ese
codebase
subspecs
finalizers
ReactiveSwift
asynchronicity
composable
presentee
Optionals
StackOverflow
backtrace
backport
reframe
runtime
_
misinference
Podfile
Xcodes
TideLift
Gitter
top-100
CocoaPod
conformant
PromiseKits
CancellablePromise
CancellableThenable
thenable
CancellableCatchable
catchable
cancellableRecover
ensureThen
Alamofire.request
responseDecodable
DecodableObject.self
BFTask
CLLocationManager.requestLocation
URLSession.shared.dataTask
MapKit
MKDirections
URLSession.shared.GET
StoreKit
SKProductsRequest
SystemConfiguration
SCNetworkReachability.promise
UIViewPropertyAnimator
startAnimation
Alamofire.DataRequest
responseData
responseString
responseJSON
responsePropertyList
Alamofire.DownloadRequest
CLLocationManager
requestLocation
authorizationType
requestAuthorization
requestedAuthorizationType
NotificationCenter
NSObject
keyPath
URLSession
dataTask
uploadTask
fromFile
downloadTask
HomeKit
HMPromiseAccessoryBrowser
scanInterval
HMHomeManager
calculateETA
MKMapSnapshotter
SKReceiptRefreshRequest
SCNetworkReachability
Cancellability
HealthKit
enum
cancellize
UIImage
PMKFinalizercancelthendonePromise
VoidPending
PromiseURLSessionresumewait
unusedResult
discardableResultcatchreturncauterize
PMKFinalizercancelthenMapthenFlatMapthendonePromise
Accio

10
.gitignore vendored
View File

@ -1,7 +1,9 @@
/PromiseKit.xcodeproj
*.xcodeproj/**/xcuserdata/
*.xcscmblueprint
/Carthage
/.build
.DS_Store
/build
/Tests/A+/JavaScript/build
/.swiftpm
DerivedData
/PromiseKit.podspec
/Extensions/Carthage
/Tests/JS-A+/build

69
.gitmodules vendored Normal file
View File

@ -0,0 +1,69 @@
[submodule "Extensions/Foundation"]
path = Extensions/Foundation
url = https://github.com/PromiseKit/Foundation.git
[submodule "Extensions/UIKit"]
path = Extensions/UIKit
url = https://github.com/PromiseKit/UIKit.git
[submodule "Extensions/Accounts"]
path = Extensions/Accounts
url = https://github.com/PromiseKit/Accounts.git
[submodule "Extensions/MessagesUI"]
path = Extensions/MessagesUI
url = https://github.com/PromiseKit/MessagesUI.git
[submodule "Extensions/WatchConnectivity"]
path = Extensions/WatchConnectivity
url = https://github.com/PromiseKit/WatchConnectivity.git
[submodule "Extensions/Photos"]
path = Extensions/Photos
url = https://github.com/PromiseKit/Photos.git
[submodule "Extensions/MapKit"]
path = Extensions/MapKit
url = https://github.com/PromiseKit/MapKit.git
[submodule "Extensions/CloudKit"]
path = Extensions/CloudKit
url = https://github.com/PromiseKit/CloudKit.git
[submodule "Extensions/AddressBook"]
path = Extensions/AddressBook
url = https://github.com/PromiseKit/AddressBook.git
[submodule "Extensions/AssetsLibrary"]
path = Extensions/AssetsLibrary
url = https://github.com/PromiseKit/AssetsLibrary.git
[submodule "Extensions/CoreLocation"]
path = Extensions/CoreLocation
url = https://github.com/PromiseKit/CoreLocation.git
[submodule "Extensions/QuartzCore"]
path = Extensions/QuartzCore
url = https://github.com/PromiseKit/QuartzCore.git
[submodule "Extensions/Social"]
path = Extensions/Social
url = https://github.com/PromiseKit/Social.git
[submodule "Extensions/StoreKit"]
path = Extensions/StoreKit
url = https://github.com/PromiseKit/StoreKit.git
[submodule "Extensions/Bolts"]
path = Extensions/Bolts
url = https://github.com/PromiseKit/Bolts.git
[submodule "Extensions/CoreBluetooth"]
path = Extensions/CoreBluetooth
url = https://github.com/PromiseKit/CoreBluetooth.git
[submodule "Extensions/EventKit"]
path = Extensions/EventKit
url = https://github.com/PromiseKit/EventKit.git
[submodule "Extensions/SystemConfiguration"]
path = Extensions/SystemConfiguration
url = https://github.com/PromiseKit/SystemConfiguration
[submodule "Extensions/Alamofire"]
path = Extensions/Alamofire
url = https://github.com/PromiseKit/Alamofire
[submodule "Extensions/OMGHTTPURLRQ"]
path = Extensions/OMGHTTPURLRQ
url = https://github.com/PromiseKit/OMGHTTPURLRQ
[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,102 +1,201 @@
os: osx
osx_image: xcode10.3
language: swift
osx_image: xcode10.2
branches:
only:
- /^v7/
- v7
- master
- v6
- v4
- legacy-1.x
- /^\d+\.\d+\.\d+$/
stages:
- name: pretest
- name: permutations
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+$
xcode_project: PromiseKit.xcodeproj
xcode_scheme: PromiseKit-Package
xcode_destination: 'platform=macOS'
jobs:
include:
- stage: pretest
name: Validate Linux test coverage completeness
install: swift test --generate-linuxmain
install: swift test --generate-linuxmain -Xswiftc -target -Xswiftc x86_64-apple-macosx10.12
script: git diff --exit-code
- &swiftpm
name: macOS SwiftPM 5.0
stage: permutations
script: swift build
- <<: *swiftpm
name: macOS SwiftPM 5.1
- &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
- &test
name: macOS
stage: test
install: swift package generate-xcodeproj --enable-code-coverage
after_success: bash <(curl -s https://codecov.io/bash)
- <<: *test
name: iOS
xcode_destination: 'OS=12.2,name=iPhone SE'
- <<: *test
name: tvOS
xcode_destination: 'OS=12.2,name=Apple TV'
- <<: *test
name: Xcode 11
name: Swift 4.1.52 / Xcode 11
- &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
- &linux
name: Linux
env: SWIFT_VERSION='5.0'
stage: swiftpm
env: SWIFT_BUILD_VERSION=3 SWIFT_VERSION=4.0.3
name: Linux / Swift 3.2
os: linux
dist: trusty
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
install: eval "$(curl -sL https://swiftenv.fuller.li/install.sh)"
script: swift test
- <<: *linux
env: SWIFT_VERSION=5.1-DEVELOPMENT-SNAPSHOT-2019-07-03-a
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
- name: JavaScript Promises/A+
install: |
bash -c "
cd Tests/A+/JavaScript
npm ci &>/dev/null
npm run --hide-modules build"
script: swift test --filter A__js.AllTests
- &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
- stage: lint
name: Spell Check Markdown
install: npm install markdown-spellcheck --global
before_script: mv .github/spelling-skip-words .spelling
script: mdspell -r -n -a --en-us *.md **/*.md
os: linux
language: generic
- &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'
- stage: deploy
before_install: |
curl -O https://raw.githubusercontent.com/mxcl/Path.swift/master/.github/deploy
chmod u+x deploy
install: brew install mxcl/made/swift-sh
before_script: ./deploy generate-podspec
script: pod trunk push --allow-warnings
after_success: |
./deploy publish-release
- 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
before_script: swift package generate-xcodeproj
script: |
set -exo pipefail
jazzy --config .github/jazzy.yml \
@ -105,9 +204,9 @@ jobs:
git remote update
git fetch origin gh-pages:gh-pages --depth 1
git checkout gh-pages
rm -rf reference/v7
mv foo reference/v7
git add reference/v7
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"

View File

@ -209,45 +209,3 @@ 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.
## `Dispatcher` Objects
Some issues are best addressed at the level of dispatching. For example, you may need
to perform a series of HTTP transactions on a server that allows only a certain number of
API calls per minute. Or, you might want to allow only a certain number of memory-hungry
background tasks to run at once. These aren't really promise-level concerns; they just have
to do with the details of how closures are invoked once they become eligible to run.
Unfortunately, `DispatchQueue`s don't directly support these types of restrictions, and because
of the way `DispatchQueue` is implemented, you can't create your own subclasses. PromiseKit 7
adds an abstract `Dispatcher` protocol that generalizes the idea of a dispatch queue and allows for
alternate implementations:
```swift
public protocol Dispatcher {
func dispatch(_ body: @escaping () -> Void)
}
```
Anywhere in PromiseKit that you can use a `DispatchQueue`, you're free to substitute a `Dispatcher`.
PromiseKit doesn't care how you implement `dispatch()`. You can run the provided closure synchronously or
asynchronously, now or at some point in the future, on any thread you wish. Of course, your own code
must have a thread safety strategy and not create deadlocks.
If you're setting a default dispatcher, assign your dispatcher to `PromiseKit.conf.D` rather than `PromiseKit.conf.Q`; the latter
accepts only `DispatchQueue`s.
**FIXME: Check locations and availability of Dispatcher implementations before release**
A few handy `Dispatcher` types are available in the `Dispatchers` extension library:
* `RateLimitedDispatcher` implements general dispatch rate limits with an approximate "token bucket" strategy.
* `StrictRateLimitedDispatcher` is an exact and optimal "sliding window" rate limiter, but requires O(n) space (n = # of events/time)
* `ConcurrencyLimitedDispatcher` allows only *n* asynchronous closures to run at once.
* `CoreDataDispatcher` lets you dispatch onto threads associated with `NSManagedObjectContext`s.
A couple of `Dispatcher`s are also included in the PromiseKit core:
* `CurrentThreadDispatcher` runs closures immediately, on the current thread.
* `DispatchQueueDispatcher` forwards to a `DispatchQueue`, applying a static `DispatchGroup`, quality of service, and flag set.

View File

@ -82,10 +82,8 @@ class MyRestAPI {
}
```
All PromiseKit handlers take an `on` parameter that lets you designate a `Dispatcher` that
will run the handler. Usually, the dispatcher is just a plain-vanilla `DispatchQueue`, but you
can write your own if you like. The default is always `DispatchQueue.main`, which is a
serial (nonconcurrent) 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.
@ -175,7 +173,7 @@ 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 = Guarantee()
var fade = Guarantee()
for cell in tableView.visibleCells {
fade = fade.then {
UIView.animate(.promise, duration: 0.1) {
@ -188,12 +186,14 @@ fade.done {
}
```
Or if you have an array of promises:
Or if you have an array of closures that return promises:
```swift
var foo = Promise()
for nextPromise in arrayOfPromises {
foo = foo.then { nextPromise }
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
@ -212,42 +212,22 @@ one promise at a time if you need to.
```swift
let fetches: [Promise<T>] = makeFetches()
let timeout = after(seconds: 4)
race(when(fulfilled: fetches).asVoid(), timeout(seconds: 4)).then {
race(when(fulfilled: fetches).asVoid(), timeout).then {
//…
}.catch(policy: .allErrors) {
// Rejects with 'PMKError.timedOut' if the timeout is exceeded
}
```
`race` continues as soon as one of the promises it is watching finishes.
`timeout(seconds: TimeInterval)` returns a promise that throws
`PMKError.timedOut` when the time interval is exceeded. Note that `PMKError.timedOut`
is a cancellation error therefore the `.allErrors` catch policy must be specified
to handle this exception.
Make sure the promises you pass to `race` are all of the same type. The easiest way
to ensure this is to use `asVoid()`.
Note that if any component promise rejects, the `race` will reject, too.
When used with cancellable promises, all promises will be cancelled if either the timeout is
exceeded or if any promise rejects.
```swift
let fetches: [Promise<T>] = makeFetches()
let cancellableFetches: [CancellablePromise<T>] = fetches.map { return $0.cancellize() }
// All promises are automatically cancelled if any of them reject.
race(when(fulfilled: cancellableFetches).asVoid(), timeout(seconds: 4).cancellize()).then {
//…
}.catch(policy: .allErrors) {
// Rejects with 'PMKError.timedOut' if the timeout is exceeded.
}
```
## Minimum Duration
# 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
@ -265,22 +245,61 @@ firstly {
}
```
The code above works because we create the delay *before* we do work in `foo()`. By the
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
Starting with version 7, PromiseKit explicitly supports cancellation of promises and
promise chains. There is a new class called `CancellablePromise` that defines a `cancel`
method. Use the `cancellize` method on `Thenable` to obtain a `CancellablePromise` from a
`Promise` or `Guarantee`.
Promises dont have a `cancel` function, but they do support cancellation through a
special error type that conforms to the `CancellableError` protocol.
Invoking `cancel` will both reject the promise with `PMKError.cancelled` and cancel any
underlying asynchronous task(s).
```swift
func foo() -> (Promise<Void>, cancel: () -> Void) {
let task = Task(…)
var cancelme = false
For full details see [Cancelling Promises](Cancel.md).
let promise = Promise<Void> { seal in
task.completion = { value in
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*, 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 `catch` handlers by default. However you can
intercept cancellation if you like:
```swift
foo.then {
//…
}.catch(policy: .allErrors) {
// cancelled errors are handled *as well*
}
```
**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
@ -464,7 +483,7 @@ Use `when(resolved:)`:
```swift
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`

View File

@ -8,7 +8,7 @@
* 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, ObjC, iOS, tvOS, watchOS, macOS, Android & Linux? 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?
@ -229,8 +229,8 @@ So, RxSwift tries hard to supply every operator you might ever want to use right
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,
in RxSwift, the current dispatching state is an attribute of the chain, not the specific block, as it is in PromiseKit.
* 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,
@ -256,25 +256,35 @@ 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 { seal in
Alamofire.request(rq).responseJSON { rsp in
seal.resolve(rsp.value, rsp.error)
}
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
@ -307,56 +317,36 @@ 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` or `PromiseKit.conf.D`. These
variables both access the same underlying state. However, `conf.Q` presents it in terms of
`DispatchQueue`s, while `conf.D` presents it in terms of the more general
`Dispatcher`-protocol objects that PromiseKit uses internally. (`DispatchQueue`s are
just one possible implementation of `Dispatcher`, although they are the ones that account
for nearly all actual use.)
Each of these configuration variables is a two-tuple that identifies two separate dispatchers named `map` and `return`.
```swift
public var Q: (map: DispatchQueue?, return: DispatchQueue?)
public var D: (map: Dispatcher, return: Dispatcher)
```
The `return` dispatcher is the default for chain-finalizing methods such as `done`
and `catch`. The `map` dispatcher is the default for everything else. A
typical pattern is to change all your `then`-type handlers to run on a background queue
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
PromiseKit.conf.Q.return = .main //NOTE this is the default
```
Note that `DispatchQueue.main` is the default for _both_ dispatchers.
Be very careful about setting either part of `conf.Q` to `nil`. It has the
effect of running closures *immediately*, and this is not what you usually want to do in
your application. It is useful, however, when you are running specs and want
your promises to resolve immediately. (It's basically the same idea as "stubbing"
HTTP requests.)
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, return: nil)
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 tell PromiseKit not to dispatch there by default. This is easy enough:
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: .global(), return: .global())
```
If you want to emulate the serializing behavior of `DispatchQueue.main`, just create and label
a new `DispatchQueue`. It'll be serial by default.
```swift
PromiseKit.conf.Q.return = DispatchQueue(label: "virtual main queue")
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.
@ -392,12 +382,12 @@ Kitura.run()
## How do I control console output?
By default, PromiseKit emits warning messages on the console when certain events occur. These events include:
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:
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 }

View File

@ -58,7 +58,7 @@ 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` was introduced in PromiseKit 5. We previously defined a variant of `then` that
> *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
@ -311,10 +311,10 @@ extra disambiguation for the Swift compiler. Sorry; we tried.
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*: `Guarantee`s (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. Just do `seal(value)` rather than `seal.fulfill(value)`. It's
different because there is only one way to seal guarantees; they can
> *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>`
@ -346,7 +346,7 @@ if you find an issue.
---
If you are creating your own guarantees the syntax is simpler than that of promises:
If you are creating your own guarantees the syntax is simpler than that of promises;
```swift
func fetch() -> Promise<String> {
@ -507,7 +507,7 @@ However, this shorthand is both a blessing and a curse. You may find that the Sw
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 were able to blunt many of these common
> By adding `done` to PromiseKit 5, we have managed to avoid many of these common
pain points in using PromiseKit and Swift.
@ -527,9 +527,9 @@ Here are some recent articles that document PromiseKit 5+:
* [Using Promises - Agostini.tech](https://agostini.tech/2018/10/08/using-promisekit)
Be careful when consulting general online references, as 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 as well.)
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).
[API Reference]: https://mxcl.dev/PromiseKit/reference/v7/Classes/Promise.html
[API Reference]: https://mxcl.dev/PromiseKit/reference/v6/Classes/Promise.html

View File

@ -45,7 +45,7 @@ to Xcode 10.2 dropping support for Swift 3.
## Accio
Add the following to your `Package.swift`:
Add the following to your Package.swift:
```swift
.package(url: "https://github.com/mxcl/PromiseKit.git", .upToNextMajor(from: "6.8.4")),

View File

@ -4,7 +4,6 @@
* Handbook
* [Getting Started](GettingStarted.md)
* [Promises: Common Patterns](CommonPatterns.md)
* [Cancelling Promises](Cancel.md)
* [Frequently Asked Questions](FAQ.md)
* Manual
* [Installation Guide](Installation.md)
@ -12,4 +11,4 @@
* [Troubleshooting](Troubleshooting.md)
* [Appendix](Appendix.md)
* [Examples](Examples)
* [API Reference](https://mxcl.dev/PromiseKit/reference/v7/Classes/Promise.html)
* [API Reference](https://mxcl.dev/PromiseKit/reference/v6/Classes/Promise.html)

View File

@ -68,7 +68,7 @@ return firstly {
}
```
We have made great effort to reduce the need for explicit typing in PromiseKit,
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.
@ -163,131 +163,6 @@ 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.
### Cancellable promise embedded in the middle of a standard promise chain
Error: ***Cannot convert value of type 'Promise<>' to closure result type 'Guarantee<>'***. Fixed by adding `cancellize` to `firstly { login() }`.
```swift
/// 'login()' returns 'Promise<Creds>'
/// 'fetch(avatar:)' returns 'CancellablePromise<UIImage>'
let promise = firstly {
login() /// <-- ERROR: Cannot convert value of type 'Promise<Creds>' to closure result type 'Guarantee<Creds>'
}.then { creds in /// CHANGE TO: "}.cancellize().then { creds in"
fetch(avatar: creds.user) /// <-- ERROR: Cannot convert value of type 'CancellablePromise<UIImage>' to
/// closure result type 'Guarantee<UIImage>'
}.done { image in
self.imageView = image
}.catch(policy: .allErrors) { error in
if error.isCancelled {
// the chain has been cancelled!
}
}
// …
promise.cancel()
```
### The return type for a multi-line closure returning `CancellablePromise` is not explicitly stated
The Swift compiler cannot (yet) determine the return type of a multi-line closure.
The following example gives the unhelpful error: ***'()' is not convertible to 'UIImage'***. Many other strange errors can result from not explicitly declaring the return type of a multi-line closure. These kinds of errors are fixed by explicitly declaring the return type, which in the following example is a `CancellablePromise<UIImage>``.
```swift
/// 'login()' returns 'Promise<Creds>'
/// 'fetch(avatar:)' returns 'CancellablePromise<UIImage>'
let promise = firstly {
login()
}.cancellize().then { creds in /// CHANGE TO: "}.cancellize().then { creds -> CancellablePromise<UIImage> in"
let f = fetch(avatar: creds.user)
return f
}.done { image in
self.imageView = image /// <-- ERROR: '()' is not convertible to 'UIImage'
}.catch(policy: .allErrors) { error in
if error.isCancelled {
// the chain has been cancelled!
}
}
// …
promise.cancel()
```
### Trying to cancel a standard promise chain
Error: ***Value of type `PMKFinalizer` has no member `cancel`***. Fixed by using cancellable promises instead of standard promises.
```swift
/// 'login()' returns 'Promise<Creds>'
/// 'fetch(avatar:)' returns 'CancellablePromise<UIImage>'
let promise = firstly {
login()
}.then { creds in /// CHANGE TO: "}.cancellize().then { creds in"
fetch(avatar: creds.user).promise /// CHANGE TO: fetch(avatar: creds.user)
}.done { image in
self.imageView = image
}.catch(policy: .allErrors) { error in
if error.isCancelled {
// the chain has been cancelled!
}
}
// …
promise.cancel() /// <-- ERROR: Value of type 'PMKFinalizer' has no member 'cancel'
```
### Compilation for long chains is very slow or times out, especially with cancellable promises
If a cancellable promise chain has more than a few (> 3) calls to `thenMap` and
`thenFlatMap` or has an extensive number of calls in the chain (> 6), you
may need to specify the return types for all closures in the chain. Standard
(non-cancellable) promise chains typically only see this problem if they are
extremely long (> 15 calls).
And if compilation with promises is generally sluggish, the time may be greatly
improved by specifying return types for all closures.
For example:
```swift
/// Compilation timeout error:
Promise.value([42, 52]).cancellize().then {
Promise.value($0)
}.then {
Promise.value($0)
}.thenMap {
Promise.value($0 + 10).cancellize()
}.thenMap {
Promise.value($0 + 10)
}.thenFlatMap {
Promise.value([$0 + 10]).cancellize()
}.thenFlatMap { /// <-- Error: The compiler is unable to type-check this expression
/// in reasonable time; try breaking up the expression
/// into distinct sub-expressions
Promise.value([$0 + 10])
}
/// Compiles very quickly:
Promise.value([42, 52]).cancellize().then { v -> Promise<[Int]> in
Promise.value(v)
}.then { v -> Promise<[Int]> in
Promise.value(v)
}.thenMap { v -> CancellablePromise<Int> in
Promise.value(v + 10).cancellize()
}.thenMap { v -> Promise<Int> in
Promise.value(v + 10)
}.thenFlatMap { v -> CancellablePromise<[Int]> in
Promise.value([v + 10]).cancellize()
}.thenFlatMap { v -> Promise<[Int]> in
Promise.value([v + 10])
}
```
## You copied code off the Internet that doesnt work
@ -300,17 +175,17 @@ All PromiseKit functions are documented and provide examples.
You have a `then`; you want a `done`.
## "Missing argument for parameter #1 in call" "Unable to infer closure type in the current context"
## "Missing argument for parameter #1 in call"
This is part of Swift 4s “tuplegate”.
You must fulfill a `Promise<Void>` with an explicit `Void` parameter:
You must specify your `Void` parameter:
```swift
seal.fulfill(())
```
This wart remains in Swift 5, too. It's probably not going to change.
Yes: we hope they revert this change in Swift 5 too.
## "Ambiguous reference to 'firstly(execute:)'"

View File

@ -1,464 +0,0 @@
# Cancelling Promises
PromiseKit 7 adds clear and concise cancellation abilities to promises and to the [PromiseKit extensions](#extensions-pane). Cancelling promises and their associated tasks is now simple and straightforward. Promises and promise chains can safely and efficiently be cancelled from any thread at any time.
```swift
UIApplication.shared.isNetworkActivityIndicatorVisible = true
let fetchImage = URLSession.shared.dataTask(.promise, with: url).cancellize().compactMap{ UIImage(data: $0.data) }
let fetchLocation = CLLocationManager.requestLocation().cancellize().lastValue
let finalizer = firstly {
when(fulfilled: fetchImage, fetchLocation)
}.done { image, location in
self.imageView.image = image
self.label.text = "\(location)"
}.ensure {
UIApplication.shared.isNetworkActivityIndicatorVisible = false
}.catch(policy: .allErrors) { error in
/* 'catch' will be invoked with 'PMKError.cancelled' when cancel is called on the context.
Use the default policy of '.allErrorsExceptCancellation' to ignore cancellation errors. */
self.show(UIAlertController(for: error), sender: self)
}
//…
// Cancel currently active tasks and reject all cancellable promises with 'PMKError.cancelled'.
// 'cancel()' can be called from any thread at any time.
finalizer.cancel()
/* 'finalizer' here refers to the 'CancellableFinalizer' for the chain. Calling 'cancel' on
any promise in the chain or on the finalizer cancels the entire chain. Therefore
calling 'cancel' on the finalizer cancels everything. */
```
# Cancel Chains
Promises can be cancelled using a `CancellablePromise`. The `cancellize()` method on `Promise` is used to convert a `Promise` into a `CancellablePromise`. If a promise chain is initialized with a `CancellablePromise`, then the entire chain is cancellable. Calling `cancel()` on any promise in the chain cancels the entire chain.
Creating a chain where the entire chain can be cancelled is the recommended usage for cancellable promises.
The `CancellablePromise` contains a `CancelContext` that keeps track of the tasks and promises for the chain. Promise chains can be cancelled either by calling the `cancel()` method on any `CancellablePromise` in the chain, or by calling `cancel()` on the `CancelContext` for the chain. It may be desirable to hold on to the `CancelContext` directly rather than a promise so that the promise can be deallocated by ARC when it is resolved.
For example:
```swift
let context = firstly {
login()
/* The 'Thenable.cancellize' method initiates a cancellable promise chain by
returning a 'CancellablePromise'. */
}.cancellize().then { creds in
fetch(avatar: creds.user)
}.done { image in
self.imageView = image
}.catch(policy: .allErrors) { error in
if error.isCancelled {
// the chain has been cancelled!
}
}.cancelContext
// …
/* Note: Promises can be cancelled using the 'cancel()' method on the 'CancellablePromise'.
However, it may be desirable to hold on to the 'CancelContext' directly rather than a
promise so that the promise can be deallocated by ARC when it is resolved. */
context.cancel()
```
### Creating a partially cancellable chain
A `CancellablePromise` can be placed at the start of a chain, but it cannot be embedded directly in the middle of a standard (non-cancellable) promise chain. Instead, a partially cancellable promise chain can be used. A partially cancellable chain is not the recommended way to use cancellable promises, although there may be cases where this is useful.
**Convert a cancellable chain to a standard chain**
`CancellablePromise` wraps a delegate `Promise`, which can be accessed with the `promise` property. The above example can be modified as follows so that once `login()` completes, the chain can no longer be cancelled:
```swift
/* Here, by calling 'promise.then' rather than 'then' the chain is converted from a cancellable
promise chain to a standard promise chain. In this example, calling 'cancel()' during 'login'
will cancel the chain but calling 'cancel()' during the 'fetch' operation will have no effect: */
let cancellablePromise = firstly {
login().cancellize()
}
cancellablePromise.promise.then {
fetch(avatar: creds.user)
}.done { image in
self.imageView = image
}.catch(policy: .allErrors) { error in
if error.isCancelled {
// the chain has been cancelled!
}
}
// …
/* This will cancel the 'login' but will not cancel the 'fetch'. So whether or not the
chain is cancelled depends on how far the chain has progressed. */
cancellablePromise.cancel()
```
**Convert a standard chain to a cancellable chain**
A non-cancellable chain can be converted to a cancellable chain in the middle of the chain as follows:
```swift
/* In this example, calling 'cancel()' during 'login' will not cancel the login. However,
the chain will be cancelled immediately, and the 'fetch' will not be executed. If 'cancel()'
is called during the 'fetch' then both the 'fetch' itself and the promise chain will be
cancelled immediately. */
let promise = firstly {
login()
}.then {
fetch(avatar: creds.user).cancellize()
}.done { image in
self.imageView = image
}.catch(policy: .allErrors) { error in
if error.isCancelled {
// the chain has been cancelled!
}
}
// …
promise.cancel()
```
# Core Cancellable PromiseKit API
The following classes, methods and functions have been added to PromiseKit to support cancellation. Existing functions or methods with underlying tasks that can be cancelled are indicated by being appended with '.cancellize()'.
<pre><code><b>Thenable</b>
cancellize(_:) - Converts the Promise or Guarantee (Thenable) into a
CancellablePromise, which is a cancellable variant of the given
Promise or Guarantee (Thenable)
<b>Global functions</b>
after(seconds:).<mark><b>cancellize()</b></mark> - 'after' with seconds can be cancelled
after(_:).<mark><b>cancellize</b></mark> - 'after' with interval can be cancelled
firstly(execute:) - Accepts body returning Promise or CancellablePromise
hang(_:) - Accepts Promise and CancellablePromise
race(_:) - Accepts [Promise] and [CancellablePromise]
when(fulfilled:) - Accepts [Promise] and [CancellablePromise]
when(fulfilled:concurrently:) - Accepts iterator of type Promise or CancellablePromise
when(resolved:) - Accepts [Promise] and [CancellablePromise]
<b>CancellablePromise properties and methods</b>
promise - Delegate Promise for this CancellablePromise
result - The current Result
init(_ bridge:<span style="color:gray;"><i>cancelContext</i>:</span>) - Initialize a new cancellable promise bound to the provided Thenable
init(<span style="color:gray;"><i>cancellable</i>:</span>resolver body:). - Initialize a new cancellable promise that can be resolved with
the provided '(Resolver) throws -> Void' body
init(<span style="color:gray;"><i>cancellable</i>:</span>promise:resolver:) - Initialize a new cancellable promise using the given Promise
and its Resolver
init(<span style="color:gray;"><i>cancellable</i>:</span>error:) - Initialize a new rejected cancellable promise
init(<span style="color:gray;"><i>cancellable</i>:</span>) - Initializes a new cancellable promise fulfilled with Void
pending() -> (promise:resolver:) - Returns a tuple of a new cancellable pending promise and its
Resolver
<b>CancellableThenable properties and methods</b>
thenable - Delegate Thenable for this CancellableThenable
cancel(<span style="color:gray;"><i>error</i>:</span>) - Cancels all members of the promise chain
cancelContext - The CancelContext associated with this CancellableThenable
cancelItemList - Tracks the cancel items for this CancellableThenable
isCancelled - True if all members of the promise chain have been successfully
cancelled, false otherwise
cancelAttempted - True if 'cancel' has been called on the promise chain associated
with this CancellableThenable, false otherwise
cancelledError - The error generated when the promise is cancelled
appendCancellable(cancellable:<span style="color:gray;"><i>reject</i>:</span>) - Append the Cancellable task to our cancel context
appendCancelContext(from:) - Append the cancel context associated with 'from' to our
CancelContext
then(<span style="color:gray;"><i>on</i>:</span><span style="color:gray;"><i>flags</i>:</span>_ body:) - Accepts body returning CancellableThenable
cancellableThen(<span style="color:gray;"><i>on</i>:</span><span style="color:gray;"><i>flags</i>:</span>_ body:) - Accepts body returning Thenable
map(<span style="color:gray;"><i>on</i>:</span><span style="color:gray;"><i>flags</i>:</span>_ transform:)
compactMap(<span style="color:gray;"><i>on</i>:</span><span style="color:gray;"><i>flags</i>:</span>_ transform:)
done(<span style="color:gray;"><i>on</i>:</span><span style="color:gray;"><i>flags</i>:</span>_ body:)
get(<span style="color:gray;"><i>on</i>:</span><span style="color:gray;"><i>flags</i>:</span>_ body:)
tap(<span style="color:gray;"><i>on</i>:</span><span style="color:gray;"><i>flags</i>:</span>_ body:)
asVoid()
error
isPending
isResolved
isFulfilled
isRejected
value
mapValues(<span style="color:gray;"><i>on</i>:</span><span style="color:gray;"><i>flags</i>:</span>_ transform:)
flatMapValues(<span style="color:gray;"><i>on</i>:</span><span style="color:gray;"><i>flags</i>:</span>_ transform:)
compactMapValues(<span style="color:gray;"><i>on</i>:</span><span style="color:gray;"><i>flags</i>:</span>_ transform:)
thenMap(<span style="color:gray;"><i>on</i>:</span><span style="color:gray;"><i>flags</i>:</span>_ transform:) - Accepts transform returning CancellableThenable
cancellableThenMap(<span style="color:gray;"><i>on</i>:</span><span style="color:gray;"><i>flags</i>:</span>_ transform:) - Accepts transform returning Thenable
thenFlatMap(<span style="color:gray;"><i>on</i>:</span><span style="color:gray;"><i>flags</i>:</span>_ transform:) - Accepts transform returning CancellableThenable
cancellableThenFlatMap(<span style="color:gray;"><i>on</i>:</span><span style="color:gray;"><i>flags</i>:</span>_ transform:) - Accepts transform returning Thenable
filterValues(<span style="color:gray;"><i>on</i>:</span><span style="color:gray;"><i>flags</i>:</span>_ isIncluded:)
firstValue
lastValue
sortedValues(<span style="color:gray;"><i>on</i>:</span><span style="color:gray;"><i>flags</i>:</span>)
<b>CancellableCatchable properties and methods</b>
catchable - Delegate Catchable for this CancellableCatchable
catch(<span style="color:gray;"><i>on</i>:</span><span style="color:gray;"><i>flags</i>:</span><span style="color:gray;"><i>policy</i>:</span>:_ body:) - Accepts body returning Void
recover(<span style="color:gray;"><i>on</i>:</span><span style="color:gray;"><i>flags</i>:</span><span style="color:gray;"><i>policy</i>:</span>:_ body:) - Accepts body returning CancellableThenable
cancellableRecover(<span style="color:gray;"><i>on</i>:</span><span style="color:gray;"><i>flags</i>:</span><span style="color:gray;"><i>policy</i>:</span>:_ body:) - Accepts body returning Thenable
ensure(<span style="color:gray;"><i>on</i>:</span><span style="color:gray;"><i>flags</i>:</span>_ body:) - Accepts body returning Void
ensureThen(<span style="color:gray;"><i>on</i>:</span><span style="color:gray;"><i>flags</i>:</span>_ body:) - Accepts body returning CancellablePromise
finally(_ body:)
cauterize()
</code></pre>
# <a name="extensions-pane"></a> Extensions
Cancellation support has been added to the PromiseKit extensions, but only where the underlying asynchronous tasks can be cancelled. This example Podfile lists the PromiseKit extensions that support cancellation along with a usage example:
<pre><code>pod "PromiseKit/Alamofire"
# Alamofire.request("http://example.com", method: .get).responseDecodable(DecodableObject.self).<mark><b>cancellize</b></mark>()
pod "PromiseKit/Bolts"
# CancellablePromise(…).then() { _ -> BFTask<NSString> in /*…*/ } // Returns <mark><b>CancellablePromise</b></mark>
pod "PromiseKit/CoreLocation"
# CLLocationManager.requestLocation().<mark><b>cancellize</b></mark>().then { /*…*/ }
pod "PromiseKit/Foundation"
# URLSession.shared.dataTask(.promise, with: request).<mark><b>cancellize</b></mark>().then { /*…*/ }
pod "PromiseKit/MapKit"
# MKDirections(…).calculate().<mark><b>cancellize</b></mark>().then { /*…*/ }
pod "PromiseKit/OMGHTTPURLRQ"
# URLSession.shared.GET("http://example.com").<mark><b>cancellize</b></mark>().then { /*…*/ }
pod "PromiseKit/StoreKit"
# SKProductsRequest(…).start(.promise).<mark><b>cancellize</b></mark>().then { /*…*/ }
pod "PromiseKit/SystemConfiguration"
# SCNetworkReachability.promise().<mark><b>cancellize</b></mark>().then { /*…*/ }
pod "PromiseKit/UIKit"
# UIViewPropertyAnimator(…).startAnimation(.promise).<mark><b>cancellize</b></mark>().then { /*…*/ }
</code></pre>
Here is a complete list of PromiseKit extension methods that support cancellation:
[Alamofire](http://github.com/PromiseKit/Alamofire-)
<pre><code>Alamofire.DataRequest
response(_:<span style="color:gray;"><i>queue</i>:</span>).<mark><b>cancellize</b></mark>()
responseData(<span style="color:gray;"><i>queue</i>:</span>).<mark><b>cancellize</b></mark>()
responseString(<span style="color:gray;"><i>queue</i>:</span>).<mark><b>cancellize</b></mark>()
responseJSON(<span style="color:gray;"><i>queue</i>:</span><span style="color:gray;"><i>options</i>:</span>).<mark><b>cancellize</b></mark>()
responsePropertyList(<span style="color:gray;"><i>queue</i>:</span><span style="color:gray;"><i>options</i>:</span>).<mark><b>cancellize</b></mark>()
responseDecodable<T>(<span style="color:gray;"><i>queue</i>:</span>:<span style="color:gray;"><i>decoder</i>:</span>).<mark><b>cancellize</b></mark>()
responseDecodable<T>(_ type:<span style="color:gray;"><i>queue</i>:</span><span style="color:gray;"><i>decoder</i>:</span>).<mark><b>cancellize</b></mark>()
Alamofire.DownloadRequest
response(_:<span style="color:gray;"><i>queue</i>:</span>).<mark><b>cancellize</b></mark>()
responseData(<span style="color:gray;"><i>queue</i>:</span>).<mark><b>cancellize</b></mark>()
</code></pre>
[Bolts](http://github.com/PromiseKit/Bolts)
<pre><code><mark><b>CancellablePromise</b></mark>&lt;T&gt;
then&lt;U&gt;(<span style="color:gray;"><i>on: DispatchQueue?</i></span>, body: (T) -> BFTask&lt;U&gt;) -> <mark><b>CancellablePromise</b></mark><U?>
</code></pre>
[CoreLocation](http://github.com/PromiseKit/CoreLocation)
<pre><code>CLLocationManager
requestLocation(<span style="color:gray;"><i>authorizationType</i>:</span><span style="color:gray;"><i>satisfying</i>:</span>).<mark><b>cancellize</b></mark>()
requestAuthorization(<span style="color:gray;"><i>type requestedAuthorizationType</i>:</span>).<mark><b>cancellize</b></mark>()
</code></pre>
[Foundation](http://github.com/PromiseKit/Foundation)
<pre><code>NotificationCenter:
observe(<span style="color:gray;"><i>once:object:</i></span>).<mark><b>cancellize</b></mark>()
NSObject
observe(_:keyPath:).<mark><b>cancellize</b></mark>()
Process
launch(_:).<mark><b>cancellize</b></mark>()
URLSession
dataTask(_:with:).<mark><b>cancellize</b></mark>()
uploadTask(_:with:from:).<mark><b>cancellize</b></mark>()
uploadTask(_:with:fromFile:).<mark><b>cancellize</b></mark>()
downloadTask(_:with:to:).<mark><b>cancellize</b></mark>()
<mark><b>CancellablePromise</b></mark>
validate()
</code></pre>
[HomeKit](http://github.com/PromiseKit/HomeKit)
<pre><code>HMPromiseAccessoryBrowser
start(scanInterval:).<mark><b>cancellize</b></mark>()
HMHomeManager
homes().<mark><b>cancellize</b></mark>()
</code></pre>
[MapKit](http://github.com/PromiseKit/MapKit)
<pre><code>MKDirections
calculate().<mark><b>cancellize</b></mark>()
calculateETA().<mark><b>cancellize</b></mark>()
MKMapSnapshotter
start().<mark><b>cancellize</b></mark>()
</code></pre>
[StoreKit](http://github.com/PromiseKit/StoreKit)
<pre><code>SKProductsRequest
start(_:).<mark><b>cancellize</b></mark>()
SKReceiptRefreshRequest
promise().<mark><b>cancellize</b></mark>()
</code></pre>
[SystemConfiguration](http://github.com/PromiseKit/SystemConfiguration)
<pre><code>SCNetworkReachability
promise().<mark><b>cancellize</b></mark>()
</code></pre>
[UIKit](http://github.com/PromiseKit/UIKit)
<pre><code>UIViewPropertyAnimator
startAnimation(_:).<mark><b>cancellize</b></mark>()
</code></pre>
## Choose Your Networking Library
All the networking library extensions supported by PromiseKit are now simple to cancel!
[Alamofire](http://github.com/PromiseKit/Alamofire-)
```swift
// pod 'PromiseKit/Alamofire'
// # https://github.com/PromiseKit/Alamofire
let context = firstly {
Alamofire
.request("http://example.com", method: .post, parameters: params)
.responseDecodable(Foo.self)
}.cancellize().done { foo in
//…
}.catch { error in
//…
}.cancelContext
//…
context.cancel()
```
And (of course) plain `URLSession` from [Foundation](http://github.com/PromiseKit/Foundation):
```swift
// pod 'PromiseKit/Foundation'
// # https://github.com/PromiseKit/Foundation
let context = firstly {
URLSession.shared.dataTask(.promise, with: try makeUrlRequest())
}.cancellize().map {
try JSONDecoder().decode(Foo.self, with: $0.data)
}.done { foo in
//…
}.catch { error in
//…
}.cancelContext
//…
context.cancel()
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 JSONSerialization.jsonData(with: obj)
return rq
}
```
# Cancellability Goals
* Provide a streamlined way to cancel a promise chain, which rejects all associated promises and cancels all associated tasks. For example:
```swift
let promise = firstly {
login()
}.cancellize().then { creds in // Use the 'cancellize' function to initiate a cancellable promise chain
fetch(avatar: creds.user)
}.done { image in
self.imageView = image
}.catch(policy: .allErrors) { error in
if error.isCancelled {
// the chain has been cancelled!
}
}
//…
promise.cancel()
```
* Ensure that subsequent code blocks in a promise chain are _never_ called after the chain has been cancelled
* Fully support concurrency, where all code is thread-safe. Cancellable promises and promise chains can safely and efficiently be cancelled from any thread at any time.
* Provide cancellable support for all PromiseKit extensions whose native tasks can be cancelled (e.g. Alamofire, Bolts, CoreLocation, Foundation, HealthKit, HomeKit, MapKit, StoreKit, SystemConfiguration, UIKit)
* Support cancellation for all PromiseKit primitives such as 'after', 'firstly', 'when', 'race'
* Provide a simple way to make new types of cancellable promises
* Ensure promise branches are properly cancelled. For example:
```swift
import Alamofire
import PromiseKit
func updateWeather(forCity searchName: String) {
refreshButton.startAnimating()
let context = firstly {
getForecast(forCity: searchName)
}.cancellize().done { response in
updateUI(forecast: response)
}.ensure {
refreshButton.stopAnimating()
}.catch { error in
// Cancellation errors are ignored by default
showAlert(error: error)
}.cancelContext
//…
/* **** Cancels EVERYTHING (except... the 'ensure' block always executes regardless)
Note: non-cancellable tasks cannot be interrupted. For example: if 'cancel()' is
called in the middle of 'updateUI()' then the chain will immediately be rejected,
however the 'updateUI' call will complete normally because it is not cancellable.
Its return value (if any) will be discarded. */
context.cancel()
}
func getForecast(forCity name: String) -> CancellablePromise<WeatherInfo> {
return firstly {
Alamofire.request("https://autocomplete.weather.com/\(name)")
.responseDecodable(AutoCompleteCity.self)
}.cancellize().then { city in
Alamofire.request("https://forecast.weather.com/\(city.name)")
.responseDecodable(WeatherResponse.self).cancellize()
}.map { response in
format(response)
}
}
```

@ -0,0 +1 @@
Subproject commit 911b257783efc0aa705481d556c8ecdfcdc055a9

1
Extensions/Accounts Submodule

@ -0,0 +1 @@
Subproject commit 6c01f5719ff08bbbf81d42b7e20c5188cab4a81d

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

1
Extensions/Alamofire Submodule

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

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

1
Extensions/Bolts Submodule

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

1
Extensions/CloudKit Submodule

@ -0,0 +1 @@
Subproject commit 9586f3c06c4934e776c2b96c128f332a168c2c91

@ -0,0 +1 @@
Subproject commit 8e0bc957996daad5c49f8809839f8699883ed364

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

1
Extensions/EventKit Submodule

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

1
Extensions/Foundation Submodule

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

1
Extensions/HealthKit Submodule

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

1
Extensions/HomeKit Submodule

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

1
Extensions/MapKit Submodule

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

1
Extensions/MessagesUI Submodule

@ -0,0 +1 @@
Subproject commit 2a93ce737502e13a3eaedc444b9ecb5abb28ec79

@ -0,0 +1 @@
Subproject commit 5ec40a68f168255dcc339b9620e2dcf62079ae6b

1
Extensions/Photos Submodule

@ -0,0 +1 @@
Subproject commit 48f801454b01c69a1553873cb1d95e90ae2ec4cc

1
Extensions/QuartzCore Submodule

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

1
Extensions/Social Submodule

@ -0,0 +1 @@
Subproject commit 378912a47a206183b931d2008e0f396e9fc390e8

1
Extensions/StoreKit Submodule

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

@ -0,0 +1 @@
Subproject commit 6b009f906fc489346a73759b5668b263a320c51c

1
Extensions/UIKit Submodule

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

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

View File

@ -1,24 +1,30 @@
// swift-tools-version:5.0
// swift-tools-version:4.0
import PackageDescription
let pkg = Package(name: "PromiseKit")
pkg.platforms = [
.macOS(.v10_12), //FIXME strictly 10.10 (only tests need 10.12)
.iOS(.v10), //FIXME strictly 8.0
.tvOS(.v10), //FIXME strictly 9.0
.watchOS(.v2)
]
pkg.products = [
.library(name: "PromiseKit", targets: ["PromiseKit"]),
]
pkg.swiftLanguageVersions = [
.v5 // grab PromiseKit-6.x if you want Swift 3.14.2
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 = [
.target(name: "PromiseKit", path: "Sources"),
.testTarget(name: "Core", dependencies: ["PromiseKit"], path: "Tests/Core"),
.testTarget(name: "Cancel", dependencies: ["PromiseKit"], path: "Tests/Cancel"),
.testTarget(name: "A+.swift", dependencies: ["PromiseKit"], path: "Tests/A+/Swift"),
.testTarget(name: "A+.js", dependencies: ["PromiseKit"], path: "Tests/A+/JavaScript"),
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

@ -0,0 +1,27 @@
import PlaygroundSupport
// Is this erroring? If so open the `.xcodeproj` and build the
// framework for a macOS target (usually labeled: My Mac).
// Then select `PromiseKit.playground` from inside Xcode.
import PromiseKit
func promise3() -> Promise<Int> {
return after(.seconds(1)).map{ 3 }
}
firstly {
Promise.value(1)
}.map { _ in
2
}.then { _ in
promise3()
}.done {
print($0) // => 3
}.catch { error in
// only happens for errors
}.finally {
PlaygroundPage.current.finishExecution()
}
PlaygroundPage.current.needsIndefiniteExecution = true

View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<playground version='3.0' sdk='macosx' auto-termination-delay='2'>
<sections>
<code source-file-name='section-1.swift'/>
</sections>
<timeline fileName='timeline.xctimeline'/>
</playground>

View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "self:">
</FileRef>
</Workspace>

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
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

@ -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>IDEWorkspaceSharedSettings_AutocreateContextsIfNeeded</key>
<false/>
</dict>
</plist>

View File

@ -0,0 +1,137 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1200"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "NO">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "NO"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "63B0AC561D595E1B00FA21D9"
BuildableName = "PromiseKit.framework"
BlueprintName = "PromiseKit"
ReferencedContainer = "container:PromiseKit.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
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">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "630019011D596292003B4E30"
BuildableName = "PMKCoreTests.xctest"
BlueprintName = "PMKCoreTests"
ReferencedContainer = "container:PromiseKit.xcodeproj">
</BuildableReference>
</TestableReference>
<TestableReference
skipped = "NO">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "6317518B1D59766500A9DDDC"
BuildableName = "PMKA+Tests.xctest"
BlueprintName = "PMKA+Tests"
ReferencedContainer = "container:PromiseKit.xcodeproj">
</BuildableReference>
</TestableReference>
<TestableReference
skipped = "NO">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "631411321D59795700E24B9E"
BuildableName = "PMKBridgeTests.xctest"
BlueprintName = "PMKBridgeTests"
ReferencedContainer = "container:PromiseKit.xcodeproj">
</BuildableReference>
</TestableReference>
<TestableReference
skipped = "NO">
<BuildableReference
BuildableIdentifier = "primary"
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>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "63B0AC561D595E1B00FA21D9"
BuildableName = "PromiseKit.framework"
BlueprintName = "PromiseKit"
ReferencedContainer = "container:PromiseKit.xcodeproj">
</BuildableReference>
</MacroExpansion>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "63B0AC561D595E1B00FA21D9"
BuildableName = "PromiseKit.framework"
BlueprintName = "PromiseKit"
ReferencedContainer = "container:PromiseKit.xcodeproj">
</BuildableReference>
</MacroExpansion>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>

View File

@ -35,15 +35,8 @@ pod used in many of the most popular apps in the world.
# PromiseKit 7 Alpha
PromiseKit 7 is prerelease, if youre using it: beware!
PromiseKit 7 uses Swift 5s `Result`, PromiseKit <7 use our own `Result` type.
PromiseKit 7 generalizes `DispatchQueue`s to a `Dispatcher` protocol. However,
`DispatchQueue`s are `Dispatcher`-conformant, so existing code should not need
to change. Please report any issues related to this transition.
PromiseKit 7 adds support for cancelling promises and promise chains.
We are testing PromiseKit 7 alpha, it is Swift 5 only. It is tagged and thus
importable in all package managers.
# PromiseKit 6
@ -51,22 +44,26 @@ PromiseKit 7 adds support for cancelling promises and promise chains.
# Quick Start
In your `Package.swift`:
In your [Podfile]:
```swift
package.dependencies.append(
.package(url: "https://github.com/mxcl/PromiseKit", from: Version(7, 0, 0, prereleaseIdentifiers: [“alpha”, “1”]))
)
```ruby
use_frameworks!
target "Change Me!" do
pod "PromiseKit", "~> 6.8"
end
```
PromiseKit 7 supports Swift 5.x; Xcode >= 10.2; iOS, macOS, tvOS, watchOS, Linux
and Android; SwiftPM.
> The above gives an Xcode warning? See our [Installation Guide].
PromiseKits 6 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 and 5.0; iOS, macOS, tvOS, watchOS, Linux and Android; CocoaPods,
Carthage and SwiftPM; ([CI Matrix](https://travis-ci.org/mxcl/PromiseKit)).
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].
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
@ -95,16 +92,15 @@ help me continue my work, I appreciate it 🙏🏻
# Documentation
* Handbook
* [Getting Started](Documents/GettingStarted.md)
* [Promises: Common Patterns](Documents/CommonPatterns.md)
* [Cancelling Promises](Documents/Cancel.md)
* [Frequently Asked Questions](Documents/FAQ.md)
* [Getting Started](Documentation/GettingStarted.md)
* [Promises: Common Patterns](Documentation/CommonPatterns.md)
* [Frequently Asked Questions](Documentation/FAQ.md)
* Manual
* [Installation Guide](Documents/Installation.md)
* [Objective-C Guide](Documents/ObjectiveC.md)
* [Troubleshooting](Documents/Troubleshooting.md) (e.g., solutions to common compile errors)
* [Appendix](Documents/Appendix.md)
* [API Reference](https://mxcl.dev/PromiseKit/reference/v7/Classes/Promise.html)
* [Installation Guide](Documentation/Installation.md)
* [Objective-C Guide](Documentation/ObjectiveC.md)
* [Troubleshooting](Documentation/Troubleshooting.md) (e.g., solutions to common compile errors)
* [Appendix](Documentation/Appendix.md)
* [API Reference](https://mxcl.dev/PromiseKit/reference/v6/Classes/Promise.html)
# Extensions
@ -115,19 +111,29 @@ extensions are available by specifying additional subspecs in your `Podfile`,
e.g.:
```ruby
pod "PMKMapKit" # MKDirections().calculate().then { /*…*/ }
pod "PMKCoreLocation" # CLLocationManager.requestLocation().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 commonly start with a network operation. Thus, we offer
extensions for `URLSession`:
```swift
// pod 'PMKFoundation' # https://github.com/PromiseKit/PMKFoundation
// pod 'PromiseKit/Foundation' # https://github.com/PromiseKit/Foundation
firstly {
URLSession.shared.dataTask(.promise, with: try makeUrlRequest()).validate()
@ -153,7 +159,7 @@ func makeUrlRequest() throws -> URLRequest {
And [Alamofire]:
```swift
// pod 'PMKAlamofire' # https://github.com/PromiseKit/PMKAlamofire
// pod 'PromiseKit/Alamofire' # https://github.com/PromiseKit/Alamofire-
firstly {
Alamofire
@ -179,19 +185,13 @@ became true, but nowadays it isnt really necessary.
# Support
Please check our [Troubleshooting Guide](Documents/Troubleshooting.md), and
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].
# Contributing
## Security & Vulnerability Reporting or Disclosure
## Xcode 10
https://tidelift.com/security
swift package generate-xcodeproj
open PromiseKit.xcodeproj
## Xcode 11
open Package.swift
[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%20Accio%20%7C%20SwiftPM-green.svg
@ -205,7 +205,7 @@ if after that you still have a question, ask at our [Gitter chat channel] or on
[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]: Documents/Installation.md
[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

@ -0,0 +1,32 @@
@import Foundation.NSError;
@import Foundation.NSPointerArray;
#if TARGET_OS_IPHONE
#define NSPointerArrayMake(N) ({ \
NSPointerArray *aa = [NSPointerArray strongObjectsPointerArray]; \
aa.count = N; \
aa; \
})
#else
static inline NSPointerArray * __nonnull NSPointerArrayMake(NSUInteger count) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
NSPointerArray *aa = [[NSPointerArray class] respondsToSelector:@selector(strongObjectsPointerArray)]
? [NSPointerArray strongObjectsPointerArray]
: [NSPointerArray pointerArrayWithStrongObjects];
#pragma clang diagnostic pop
aa.count = count;
return aa;
}
#endif
#define IsError(o) [o isKindOfClass:[NSError class]]
#define IsPromise(o) [o isKindOfClass:[AnyPromise class]]
#import "AnyPromise.h"
@class PMKArray;
@interface AnyPromise ()
- (void)__pipe:(void(^ __nonnull)(__nullable id))block NS_REFINED_FOR_SWIFT;
@end

306
Sources/AnyPromise.h Normal file
View File

@ -0,0 +1,306 @@
#import <Foundation/Foundation.h>
#import <dispatch/dispatch.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;
/// An Objective-C implementation of the promise pattern.
@interface AnyPromise: NSObject
/**
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.
[NSURLSession GET:url].then(^(NSData *data){
// do something with data
});
@return A new promise that is resolved with the value returned from the provided block. For example:
[NSURLSession GET:url].then(^(NSData *data){
return data.length;
}).then(^(NSNumber *number){
//…
});
@warning *Important* The block passed to `then` may take zero, one, two or three arguments, and return an object or return nothing. This flexibility is why the method signature for then is `id`, which means you will not get completion for the block parameter, and must type it yourself. It is safe to type any block syntax here, so to start with try just: `^{}`.
@warning *Important* If an `NSError` or `NSString` is thrown inside your block, or you return an `NSError` object the next `Promise` will be rejected. See `catch` for documentation on error handling.
@warning *Important* `then` is always executed on the main queue.
@see thenOn
@see thenInBackground
*/
- (AnyPromise * __nonnull (^ __nonnull)(id __nonnull))then NS_REFINED_FOR_SWIFT;
/**
The provided block is executed on the default queue when the receiver is fulfilled.
This method is provided as a convenience for `thenOn`.
@see then
@see thenOn
*/
- (AnyPromise * __nonnull(^ __nonnull)(id __nonnull))thenInBackground NS_REFINED_FOR_SWIFT;
/**
The provided block is executed on the dispatch queue of your choice when the receiver is fulfilled.
@see then
@see thenInBackground
*/
- (AnyPromise * __nonnull(^ __nonnull)(dispatch_queue_t __nonnull, id __nonnull))thenOn NS_REFINED_FOR_SWIFT;
#ifndef __cplusplus
/**
The provided block is executed when the receiver is rejected.
Provide a block of form `^(NSError *){}` or simply `^{}`. The parameter has type `id` to give you the freedom to choose either.
The provided block always runs on the main queue.
@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 catchOn.
@see catchOn
@see catchInBackground
*/
- (AnyPromise * __nonnull(^ __nonnull)(id __nonnull))catch NS_REFINED_FOR_SWIFT;
#endif
/**
The provided block is executed when the receiver is rejected.
Provide a block of form `^(NSError *){}` or simply `^{}`. The parameter has type `id` to give you the freedom to choose either.
The provided block always runs on the global background queue.
@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.
@see catch
@see catchOn
*/
- (AnyPromise * __nonnull(^ __nonnull)(id __nonnull))catchInBackground NS_REFINED_FOR_SWIFT;
/**
The provided block is executed when the receiver is rejected.
Provide a block of form `^(NSError *){}` or simply `^{}`. The parameter has type `id` to give you the freedom to choose either.
The provided block always runs on queue provided.
@warning *Note* Cancellation errors are not caught.
@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 resolved.
The provided block always runs on the main queue.
@see ensureOn
*/
- (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 ensure
*/
- (AnyPromise * __nonnull(^ __nonnull)(dispatch_queue_t __nonnull, dispatch_block_t __nonnull))ensureOn NS_REFINED_FOR_SWIFT;
/**
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.
Use this method when wrapping asynchronous code that does *not* use
promises so that this code can be used in promise chains. Generally,
prefer `promiseWithResolverBlock:` as the resulting code is more elegant.
PMKResolver resolve;
AnyPromise *promise = [[AnyPromise alloc] initWithResolver:&resolve];
// later
resolve(@"foo");
@param resolver A reference to a block pointer of PMKResolver type.
You can then call your resolver to resolve this promise.
@return A new promise.
@warning *Important* The resolver strongly retains the promise.
@see promiseWithResolverBlock:
*/
- (instancetype __nonnull)initWithResolver:(PMKResolver __strong __nonnull * __nonnull)resolver NS_REFINED_FOR_SWIFT;
@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;
@interface AnyPromise (Adapters)
/**
Create a new promise by adapting an existing asynchronous system.
The pattern of a completion block that passes two parameters, the first
the result and the second an `NSError` object is so common that we
provide this convenience adapter to make wrapping such systems more
elegant.
return [PMKPromise promiseWithAdapterBlock:^(PMKAdapter adapter){
PFQuery *query = [PFQuery ];
[query findObjectsInBackgroundWithBlock:adapter];
}];
@warning *Important* If both parameters are nil, the promise fulfills,
if both are non-nil the promise rejects. This is per the convention.
@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;
/**
Create a new promise by adapting an existing asynchronous system.
Adapts asynchronous systems that complete with `^(NSInteger, NSError *)`.
NSInteger will cast to enums provided the enum has been wrapped with
`NS_ENUM`. All of Apples enums are, so if you find one that hasnt you
may need to make a pull-request.
@see promiseWithAdapter
*/
+ (instancetype __nonnull)promiseWithIntegerAdapterBlock:(void (^ __nonnull)(PMKIntegerAdapter __nonnull adapter))block NS_REFINED_FOR_SWIFT;
/**
Create a new promise by adapting an existing asynchronous system.
Adapts asynchronous systems that complete with `^(BOOL, NSError *)`.
@see promiseWithAdapter
*/
+ (instancetype __nonnull)promiseWithBooleanAdapterBlock:(void (^ __nonnull)(PMKBooleanAdapter __nonnull adapter))block NS_REFINED_FOR_SWIFT;
@end
#ifdef __cplusplus
extern "C" {
#endif
/**
Whenever resolving a promise you may resolve with a tuple, eg.
returning from a `then` or `catch` handler or resolving a new promise.
Consumers of your Promise are not compelled to consume any arguments and
in fact will often only consume the first parameter. Thus ensure the
order of parameters is: from most-important to least-important.
Currently PromiseKit limits you to THREE parameters to the manifold.
*/
#define PMKManifold(...) __PMKManifold(__VA_ARGS__, 3, 2, 1)
#define __PMKManifold(_1, _2, _3, N, ...) __PMKArrayWithCount(N, _1, _2, _3)
extern id __nonnull __PMKArrayWithCount(NSUInteger, ...);
#ifdef __cplusplus
} // Extern C
#endif
@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.")));
- (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

179
Sources/AnyPromise.m Normal file
View File

@ -0,0 +1,179 @@
#if __has_include("PromiseKit-Swift.h")
#import "PromiseKit-Swift.h"
#else
#import <PromiseKit/PromiseKit-Swift.h>
#endif
#import "PMKCallVariadicBlock.m"
#import "AnyPromise+Private.h"
#import "AnyPromise.h"
NSString *const PMKErrorDomain = @"PMKErrorDomain";
@implementation AnyPromise {
__AnyPromise *d;
}
- (instancetype)initWith__D:(__AnyPromise *)dd {
self = [super init];
if (self) self->d = dd;
return self;
}
- (instancetype)initWithResolver:(PMKResolver __strong *)resolver {
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->d __thenOn:dispatch_get_main_queue() execute:^(id obj) {
return PMKCallVariadicBlock(block, obj);
}];
};
}
- (AnyPromise *(^)(dispatch_queue_t, id))thenOn {
return ^(dispatch_queue_t queue, id block) {
return [self->d __thenOn:queue execute:^(id obj) {
return PMKCallVariadicBlock(block, obj);
}];
};
}
- (AnyPromise *(^)(id))thenInBackground {
return ^(id block) {
return [self->d __thenOn:dispatch_get_global_queue(0, 0) execute:^(id obj) {
return PMKCallVariadicBlock(block, obj);
}];
};
}
- (AnyPromise *(^)(dispatch_queue_t, id))catchOn {
return ^(dispatch_queue_t q, id block) {
return [self->d __catchOn:q execute:^(id obj) {
return PMKCallVariadicBlock(block, obj);
}];
};
}
- (AnyPromise *(^)(id))catch {
return ^(id block) {
return [self->d __catchOn:dispatch_get_main_queue() execute:^(id obj) {
return PMKCallVariadicBlock(block, obj);
}];
};
}
- (AnyPromise *(^)(id))catchInBackground {
return ^(id block) {
return [self->d __catchOn:dispatch_get_global_queue(0, 0) execute:^(id obj) {
return PMKCallVariadicBlock(block, obj);
}];
};
}
- (AnyPromise *(^)(dispatch_block_t))ensure {
return ^(dispatch_block_t block) {
return [self->d __ensureOn:dispatch_get_main_queue() execute:block];
};
}
- (AnyPromise *(^)(dispatch_queue_t, dispatch_block_t))ensureOn {
return ^(dispatch_queue_t queue, dispatch_block_t 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
@implementation AnyPromise (Adapters)
+ (instancetype)promiseWithAdapterBlock:(void (^)(PMKAdapter))block {
return [self promiseWithResolverBlock:^(PMKResolver resolve) {
block(^(id value, id error){
resolve(error ?: value);
});
}];
}
+ (instancetype)promiseWithIntegerAdapterBlock:(void (^)(PMKIntegerAdapter))block {
return [self promiseWithResolverBlock:^(PMKResolver resolve) {
block(^(NSInteger value, id error){
if (error) {
resolve(error);
} else {
resolve(@(value));
}
});
}];
}
+ (instancetype)promiseWithBooleanAdapterBlock:(void (^)(PMKBooleanAdapter adapter))block {
return [self promiseWithResolverBlock:^(PMKResolver resolve) {
block(^(BOOL value, id error){
if (error) {
resolve(error);
} else {
resolve(@(value));
}
});
}];
}
@end

224
Sources/AnyPromise.swift Normal file
View File

@ -0,0 +1,224 @@
import Foundation
/**
__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 {
fileprivate let box: Box<Any?>
@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)
}
}
}
@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)
}
}
}))
}
@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)
}
}
}))
}
@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)
}
}
}))
}
@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)
}
}
@objc public var __value: Any? {
switch box.inspect() {
case .resolved(let obj):
return obj
default:
return nil
}
}
@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(.pending, resolver: { resolve in
pipe { result in
switch result {
case .rejected(let error):
resolve.reject(error)
case .fulfilled(let obj):
resolve.fulfill(obj)
}
}
})
}
}
#endif

View File

@ -82,3 +82,20 @@ class EmptyBox<T>: Box<T> {
}
}
}
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)
}
}
}
}

View File

@ -1,257 +0,0 @@
import Dispatch
import Foundation
/**
Keeps track of all promises in a promise chain with pending or currently running tasks, and cancels them all when `cancel` is called.
*/
public class CancelContext: Hashable {
public static func == (lhs: CancelContext, rhs: CancelContext) -> Bool {
return lhs === rhs
}
public func hash(into hasher: inout Hasher) {
hasher.combine(ObjectIdentifier(self))
}
// Create a barrier queue that is used as a read/write lock for the CancelContext
// For reads: barrier.sync { }
// For writes: barrier.sync(flags: .barrier) { }
private let barrier = DispatchQueue(label: "org.promisekit.barrier.cancel", attributes: .concurrent)
private var cancelItems = [CancelItem]()
private var cancelItemSet = Set<CancelItem>()
/**
Cancel all members of the promise chain and their associated asynchronous operations.
- Parameter error: Specifies the cancellation error to use for the cancel operation, defaults to `PMKError.cancelled`
*/
public func cancel(with error: Error = PMKError.cancelled) {
self.cancel(with: error, visited: Set<CancelContext>())
}
func cancel(with error: Error = PMKError.cancelled, visited: Set<CancelContext>) {
var items: [CancelItem]!
barrier.sync(flags: .barrier) {
internalCancelledError = error
items = cancelItems
}
for item in items {
item.cancel(with: error, visited: visited)
}
}
/**
True if all members of the promise chain have been successfully cancelled, false otherwise.
*/
public var isCancelled: Bool {
var items: [CancelItem]!
barrier.sync {
items = cancelItems
}
for item in items where !item.isCancelled {
return false
}
return true
}
/**
True if `cancel` has been called on the CancelContext associated with this promise, false otherwise. `cancelAttempted` will be true if `cancel` is called on any promise in the chain.
*/
public var cancelAttempted: Bool {
return cancelledError != nil
}
private var internalCancelledError: Error?
/**
The cancellation error initialized when the promise is cancelled, or `nil` if not cancelled.
*/
public private(set) var cancelledError: Error? {
get {
var err: Error!
barrier.sync {
err = internalCancelledError
}
return err
}
set {
barrier.sync(flags: .barrier) {
internalCancelledError = newValue
}
}
}
func append<Z: CancellableThenable>(cancellable: Cancellable?, reject: ((Error) -> Void)?, thenable: Z) {
if cancellable == nil && reject == nil {
return
}
let item = CancelItem(cancellable: cancellable, reject: reject)
var error: Error?
barrier.sync(flags: .barrier) {
error = internalCancelledError
cancelItems.append(item)
cancelItemSet.insert(item)
thenable.cancelItemList.append(item)
}
if error != nil {
item.cancel(with: error!)
}
}
func append<Z: CancellableThenable>(context childContext: CancelContext, thenable: Z) {
guard childContext !== self else {
return
}
let item = CancelItem(context: childContext)
var error: Error?
barrier.sync(flags: .barrier) {
error = internalCancelledError
cancelItems.append(item)
cancelItemSet.insert(item)
thenable.cancelItemList.append(item)
}
crossCancel(childContext: childContext, parentCancelledError: error)
}
func append(context childContext: CancelContext, thenableCancelItemList: CancelItemList) {
guard childContext !== self else {
return
}
let item = CancelItem(context: childContext)
var error: Error?
barrier.sync(flags: .barrier) {
error = internalCancelledError
cancelItems.append(item)
cancelItemSet.insert(item)
thenableCancelItemList.append(item)
}
crossCancel(childContext: childContext, parentCancelledError: error)
}
private func crossCancel(childContext: CancelContext, parentCancelledError: Error?) {
let parentError = parentCancelledError
let childError = childContext.cancelledError
if parentError != nil {
if childError == nil {
childContext.cancel(with: parentError!)
}
} else if childError != nil {
if parentError == nil {
cancel(with: childError!)
}
}
}
func recover() {
cancelledError = nil
}
func removeItems(_ list: CancelItemList, clearList: Bool) -> Error? {
var error: Error?
barrier.sync(flags: .barrier) {
error = internalCancelledError
if error == nil && list.items.count != 0 {
var currentIndex = 1
// The `list` parameter should match a block of items in the cancelItemList, remove them from the cancelItemList
// in one operation for efficiency
if cancelItemSet.remove(list.items[0]) != nil {
let removeIndex = cancelItems.firstIndex(of: list.items[0])!
while currentIndex < list.items.count {
let item = list.items[currentIndex]
if item != cancelItems[removeIndex + currentIndex] {
break
}
cancelItemSet.remove(item)
currentIndex += 1
}
cancelItems.removeSubrange(removeIndex..<(removeIndex+currentIndex))
}
// Remove whatever falls outside of the block
while currentIndex < list.items.count {
let item = list.items[currentIndex]
if cancelItemSet.remove(item) != nil {
cancelItems.remove(at: cancelItems.firstIndex(of: item)!)
}
currentIndex += 1
}
if clearList {
list.removeAll()
}
}
}
return error
}
}
/// Tracks the cancel items for a CancellablePromise. These items are removed from the associated CancelContext when the promise resolves.
public class CancelItemList {
fileprivate var items: [CancelItem]
init() {
self.items = []
}
fileprivate func append(_ item: CancelItem) {
items.append(item)
}
fileprivate func removeAll() {
items.removeAll()
}
}
fileprivate class CancelItem: Hashable {
static func == (lhs: CancelItem, rhs: CancelItem) -> Bool {
return lhs === rhs
}
public func hash(into hasher: inout Hasher) {
hasher.combine(ObjectIdentifier(self))
}
let cancellable: Cancellable?
var reject: ((Error) -> Void)?
weak var context: CancelContext?
var cancelAttempted = false
init(cancellable: Cancellable?, reject: ((Error) -> Void)?) {
self.cancellable = cancellable
self.reject = reject
}
init(context: CancelContext) {
self.cancellable = nil
self.context = context
}
func cancel(with error: Error, visited: Set<CancelContext>? = nil) {
cancelAttempted = true
cancellable?.cancel()
reject?(error)
if var v = visited, let c = context {
if !v.contains(c) {
v.insert(c)
c.cancel(with: error, visited: v)
}
}
}
var isCancelled: Bool {
return cancellable?.isCancelled ?? cancelAttempted
}
}

View File

@ -1,12 +0,0 @@
import Dispatch
/**
Use this protocol to define cancellable tasks for CancellablePromise.
*/
public protocol Cancellable {
/// Cancel the associated task
func cancel()
/// `true` if the task was successfully cancelled, `false` otherwise
var isCancelled: Bool { get }
}

View File

@ -1,628 +0,0 @@
import Dispatch
/// Provides `catch` and `recover` to your object that conforms to `CancellableThenable`
public protocol CancellableCatchMixin: CancellableThenable {
/// Type of the delegate `catchable`
associatedtype C: CatchMixin
/// Delegate `catchable` for this CancellablePromise
var catchable: C { get }
}
public extension CancellableCatchMixin {
/**
The provided closure executes when this cancellable 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 dispatcher that executes the provided closure.
- 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: Dispatcher = conf.D.return, policy: CatchPolicy = conf.catchPolicy, _ body: @escaping(Error) -> Void) -> CancellableFinalizer {
return CancellableFinalizer(self.catchable.catch(on: on, policy: policy, body), cancel: self.cancelContext)
}
/**
The provided closure executes when this cancellable promise rejects with the specific error passed in.
A final `catch` is still required at the end of the chain.
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 only: The specific error to be caught and handled.
- Parameter on: The queue to which the provided closure dispatches.
- Parameter execute: The handler to execute if this promise is rejected with the provided error.
- Note: Since this method handles only specific errors, supplying a `CatchPolicy` is unsupported. You can instead specify e.g. your cancellable error.
- SeeAlso: [Cancellation](https://github.com/mxcl/PromiseKit/blob/master/Documentation/CommonPatterns.md#cancellation)
*/
func `catch`<E: Swift.Error>(only: E, on: Dispatcher = conf.D.return, _ body: @escaping(E) -> Void) -> CancellableCascadingFinalizer where E: Equatable {
return CancellableCascadingFinalizer(self.catchable.catch(only: only, on: on, body), cancel: self.cancelContext)
}
/**
The provided closure executes when this cancellable promise rejects with an error of the type passed in.
A final `catch` is still required at the end of the chain.
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 only: The error type to be caught and handled.
- Parameter on: The queue to which the provided closure dispatches.
- Parameter execute: The handler to execute if this promise is rejected with the provided error type.
- SeeAlso: [Cancellation](https://github.com/mxcl/PromiseKit/blob/master/Documentation/CommonPatterns.md#cancellation)
*/
func `catch`<E: Swift.Error>(only: E.Type, on: Dispatcher = conf.D.return, policy: CatchPolicy = conf.catchPolicy, _ body: @escaping(E) -> Void) -> CancellableCascadingFinalizer {
return CancellableCascadingFinalizer(self.catchable.catch(only: only, on: on, policy: policy, body), cancel: self.cancelContext)
}
}
public class CancelContextFinalizer {
/// The CancelContext associated with this finalizer
public let cancelContext: CancelContext
init(cancel: CancelContext) {
self.cancelContext = cancel
}
/**
Cancel all members of the promise chain and their associated asynchronous operations.
- Parameter error: Specifies the cancellation error to use for the cancel operation, defaults to `PMKError.cancelled`
*/
public func cancel(with error: Error = PMKError.cancelled) {
cancelContext.cancel(with: error)
}
/**
True if all members of the promise chain have been successfully cancelled, false otherwise.
*/
public var isCancelled: Bool {
return cancelContext.isCancelled
}
/**
True if `cancel` has been called on the CancelContext associated with this promise, false otherwise. `cancelAttempted` will be true if `cancel` is called on any promise in the chain.
*/
public var cancelAttempted: Bool {
return cancelContext.cancelAttempted
}
/**
The cancellation error generated when the promise is cancelled, or `nil` if not cancelled.
*/
public var cancelledError: Error? {
return cancelContext.cancelledError
}
}
/**
Cancellable finalizer returned from `catch`. Use `finally` to specify a code block that executes when the promise chain resolves.
*/
public class CancellableFinalizer: CancelContextFinalizer {
let pmkFinalizer: PMKFinalizer
init(_ pmkFinalizer: PMKFinalizer, cancel: CancelContext) {
self.pmkFinalizer = pmkFinalizer
super.init(cancel: cancel)
}
/// `finally` is the same as `ensure`, but it is not chainable
@discardableResult
public func finally(on: Dispatcher = conf.D.return, _ body: @escaping () -> Void) -> CancelContext {
pmkFinalizer.finally(on: on, body)
return cancelContext
}
}
public class CancellableCascadingFinalizer: CancelContextFinalizer {
let pmkCascadingFinalizer: PMKCascadingFinalizer
init(_ pmkCascadingFinalizer: PMKCascadingFinalizer, cancel: CancelContext) {
self.pmkCascadingFinalizer = pmkCascadingFinalizer
super.init(cancel: cancel)
}
/**
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
public func `catch`(on: Dispatcher = conf.D.return, policy: CatchPolicy = conf.catchPolicy, _ body: @escaping(Error) -> Void) -> CancellableFinalizer {
return CancellableFinalizer(pmkCascadingFinalizer.catch(on: on, policy: policy, body), cancel: cancelContext)
}
/**
The provided closure executes when this promise rejects with the specific error passed in. A final `catch` is still required at the end of the chain.
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 only: The specific error to be caught and handled.
- Parameter on: The queue to which the provided closure dispatches.
- Parameter execute: The handler to execute if this promise is rejected with the provided error.
- Note: Since this method handles only specific errors, supplying a `CatchPolicy` is unsupported. You can instead specify e.g. your cancellable error.
- SeeAlso: [Cancellation](https://github.com/mxcl/PromiseKit/blob/master/Documentation/CommonPatterns.md#cancellation)
*/
public func `catch`<E: Swift.Error>(only: E, on: Dispatcher = conf.D.return, _ body: @escaping(E) -> Void) -> CancellableCascadingFinalizer where E: Equatable {
return CancellableCascadingFinalizer(pmkCascadingFinalizer.catch(only: only, on: on, body), cancel: cancelContext)
}
/**
The provided closure executes when this promise rejects with an error of the type passed in. A final `catch` is still required at the end of the chain.
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 only: The error type to be caught and handled.
- Parameter on: The queue to which the provided closure dispatches.
- Parameter execute: The handler to execute if this promise is rejected with the provided error type.
- SeeAlso: [Cancellation](https://github.com/mxcl/PromiseKit/blob/master/Documentation/CommonPatterns.md#cancellation)
*/
public func `catch`<E: Swift.Error>(only: E.Type, on: Dispatcher = conf.D.return, policy: CatchPolicy = conf.catchPolicy, _ body: @escaping(E) -> Void) -> CancellableCascadingFinalizer {
return CancellableCascadingFinalizer(pmkCascadingFinalizer.catch(only: only, on: on, policy: policy, body), cancel: cancelContext)
}
/**
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
public func cauterize() -> CancellableFinalizer {
return self.catch(policy: .allErrors) {
conf.logHandler(.cauterized($0))
}
}
}
public extension CancellableCatchMixin {
/**
The provided closure executes when this cancellable promise rejects.
Unlike `catch`, `recover` continues the chain. It can return a replacement promise or rethrow.
Use `recover` in circumstances where recovering the chain from certain errors is a possibility. For example:
let context = firstly {
CLLocationManager.requestLocation()
}.recover { error in
guard error == CLError.unknownLocation else { throw error }
return .value(CLLocation.chicago)
}.cancelContext
//
context.cancel()
- Parameter on: The dispatcher that executes the provided closure.
- 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<V: CancellableThenable>(on: Dispatcher = conf.D.map, policy: CatchPolicy = conf.catchPolicy, _ body: @escaping(Error) throws -> V) -> CancellablePromise<C.T> where V.U.T == C.T {
let cancelItemList = CancelItemList()
let cancelBody = { (error: Error) throws -> V.U in
_ = self.cancelContext.removeItems(self.cancelItemList, clearList: true)
let rval = try body(error)
if policy == .allErrors || error.isCancelled {
self.cancelContext.recover()
}
self.cancelContext.append(context: rval.cancelContext, thenableCancelItemList: cancelItemList)
return rval.thenable
}
let promise = self.catchable.recover(on: on, policy: policy, cancelBody)
if thenable.result != nil && policy == .allErrors {
self.cancelContext.recover()
}
return CancellablePromise(promise: promise, context: self.cancelContext, cancelItemList: cancelItemList)
}
/**
The provided closure executes when this cancellable promise rejects.
Unlike `catch`, `recover` continues the chain. It can return a replacement promise or rethrow.
Use `recover` in circumstances where recovering the chain from certain errors is a possibility. For example:
let context = firstly {
CLLocationManager.requestLocation()
}.cancellize().recover { error in
guard error == CLError.unknownLocation else { throw error }
return Promise.value(CLLocation.chicago)
}.cancelContext
//
context.cancel()
- Parameter on: The dispatcher that executes the provided closure.
- Parameter policy: The default policy does not execute your handler for cancellation errors.
- 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<V: Thenable>(on: Dispatcher = conf.D.map, policy: CatchPolicy = conf.catchPolicy, _ body: @escaping(Error) throws -> V) -> CancellablePromise<C.T> where V.T == C.T {
let cancelBody = { (error: Error) throws -> V in
_ = self.cancelContext.removeItems(self.cancelItemList, clearList: true)
let rval = try body(error)
if policy == .allErrors || error.isCancelled {
self.cancelContext.recover()
}
return rval
}
let promise = self.catchable.recover(on: on, policy: policy, cancelBody)
if thenable.result != nil && policy == .allErrors {
self.cancelContext.recover()
}
let cancellablePromise = CancellablePromise(promise: promise, context: self.cancelContext)
if let cancellable = promise.cancellable {
self.cancelContext.append(cancellable: cancellable, reject: promise.rejectIfCancelled, thenable: cancellablePromise)
}
return cancellablePromise
}
/**
The provided closure executes when this cancellable promise rejects with the specific error passed in.
Unlike `catch`, `recover` continues the chain. It can return a replacement promise or rethrow.
Use `recover` in circumstances where recovering the chain from certain errors is a possibility. For example:
firstly {
CLLocationManager.requestLocation()
}.recover(CLError.unknownLocation) {
return .value(CLLocation.chicago)
}
- Parameter only: The specific error to be recovered (e.g., `PMKError.emptySequence`)
- Parameter on: The dispatcher that executes the provided closure.
- Parameter body: The handler to execute if this promise is rejected with the provided error.
- Note: Since this method recovers only specific errors, supplying a `CatchPolicy` is unsupported.
- SeeAlso: [Cancellation](https://github.com/mxcl/PromiseKit/blob/master/Documentation/CommonPatterns.md#cancellation)
*/
func recover<V: CancellableThenable, E: Swift.Error>(only: E, on: Dispatcher = conf.D.map, _ body: @escaping(E) throws -> V) -> CancellablePromise<C.T> where V.U.T == C.T, E: Equatable {
let cancelItemList = CancelItemList()
let cancelBody = { (error: E) throws -> V.U in
_ = self.cancelContext.removeItems(self.cancelItemList, clearList: true)
let rval = try body(error)
if error.isCancelled {
self.cancelContext.recover()
}
self.cancelContext.append(context: rval.cancelContext, thenableCancelItemList: cancelItemList)
return rval.thenable
}
let promise = self.catchable.recover(only: only, on: on, cancelBody)
if thenable.result != nil && only.isCancelled {
self.cancelContext.recover()
}
return CancellablePromise(promise: promise, context: self.cancelContext, cancelItemList: cancelItemList)
}
/**
The provided closure executes when this cancellable promise rejects with the specific error passed in.
Unlike `catch`, `recover` continues the chain. It can return a replacement promise or rethrow.
Use `recover` in circumstances where recovering the chain from certain errors is a possibility. For example:
firstly {
CLLocationManager.requestLocation()
}.recover(CLError.unknownLocation) {
return Promise.value(CLLocation.chicago)
}
- Parameter only: The specific error to be recovered.
- Parameter on: The queue to which the provided closure dispatches.
- Parameter body: The handler to execute if this promise is rejected with the provided error.
- Note: Since this method recovers only specific errors, supplying a `CatchPolicy` is unsupported. You can instead specify e.g. your cancellable error.
- SeeAlso: [Cancellation](https://github.com/mxcl/PromiseKit/blob/master/Documentation/CommonPatterns.md#cancellation)
*/
func recover<V: Thenable, E: Swift.Error>(only: E, on: Dispatcher = conf.D.map, _ body: @escaping(E) throws -> V) -> CancellablePromise<C.T> where V.T == C.T, E: Equatable {
let cancelBody = { (error: E) throws -> V in
_ = self.cancelContext.removeItems(self.cancelItemList, clearList: true)
let rval = try body(error)
if error.isCancelled {
self.cancelContext.recover()
}
return rval
}
let promise = self.catchable.recover(only: only, on: on, cancelBody)
let cancellablePromise = CancellablePromise(promise: promise, context: self.cancelContext)
if let cancellable = promise.cancellable {
self.cancelContext.append(cancellable: cancellable, reject: promise.rejectIfCancelled, thenable: cancellablePromise)
}
return cancellablePromise
}
/**
The provided closure executes when this cancellable promise rejects with an error of the type passed in.
Unlike `catch`, `recover` continues the chain. It can return a replacement promise or rethrow.
Use `recover` in circumstances where recovering the chain from certain errors is a possibility. For example:
firstly {
API.fetchData()
}.recover(FetchError.self) { error in
guard case .missingImage(let partialData) = error else { throw error }
//
return .value(dataWithDefaultImage)
}
- Parameter only: The error type to be recovered.
- Parameter on: The dispatcher that executes the provided closure.
- Parameter policy: The default policy does not execute your handler for cancellation errors.
- Parameter body: The handler to execute if this promise is rejected with the provided error type.
- SeeAlso: [Cancellation](https://github.com/mxcl/PromiseKit/blob/master/Documentation/CommonPatterns.md#cancellation)
*/
func recover<V: CancellableThenable, E: Swift.Error>(only: E.Type, on: Dispatcher = conf.D.map, policy: CatchPolicy = conf.catchPolicy, _ body: @escaping(E) throws -> V) -> CancellablePromise<C.T> where V.U.T == C.T {
let cancelItemList = CancelItemList()
let cancelBody = { (error: E) throws -> V.U in
_ = self.cancelContext.removeItems(self.cancelItemList, clearList: true)
let rval = try body(error)
if policy == .allErrors || error.isCancelled {
self.cancelContext.recover()
}
self.cancelContext.append(context: rval.cancelContext, thenableCancelItemList: cancelItemList)
return rval.thenable
}
let promise = self.catchable.recover(only: only, on: on, policy: policy, cancelBody)
if thenable.result != nil && policy == .allErrors {
self.cancelContext.recover()
}
return CancellablePromise(promise: promise, context: self.cancelContext, cancelItemList: cancelItemList)
}
/**
The provided closure executes when this cancellable promise rejects with an error of the type passed in.
Unlike `catch`, `recover` continues the chain. It can return a replacement promise or rethrow.
Use `recover` in circumstances where recovering the chain from certain errors is a possibility. For example:
firstly {
API.fetchData()
}.recover(FetchError.self) { error in
guard case .missingImage(let partialData) = error else { throw error }
//
return Promise.value(dataWithDefaultImage)
}
- Parameter only: The error type to be recovered.
- Parameter on: The queue to which the provided closure dispatches.
- Parameter body: The handler to execute if this promise is rejected with the provided error type.
- SeeAlso: [Cancellation](https://github.com/mxcl/PromiseKit/blob/master/Documentation/CommonPatterns.md#cancellation)
*/
func recover<V: Thenable, E: Swift.Error>(only: E.Type, on: Dispatcher = conf.D.map, policy: CatchPolicy = conf.catchPolicy, _ body: @escaping(E) throws -> V) -> CancellablePromise<C.T> where V.T == C.T {
let cancelBody = { (error: E) throws -> V in
_ = self.cancelContext.removeItems(self.cancelItemList, clearList: true)
let rval = try body(error)
if policy == .allErrors {
self.cancelContext.recover()
}
return rval
}
let promise = self.catchable.recover(only: only, on: on, policy: policy, cancelBody)
if thenable.result != nil && policy == .allErrors {
self.cancelContext.recover()
}
let cancellablePromise = CancellablePromise(promise: promise, context: self.cancelContext)
if let cancellable = promise.cancellable {
self.cancelContext.append(cancellable: cancellable, reject: promise.rejectIfCancelled, thenable: cancellablePromise)
}
return cancellablePromise
}
/**
The provided closure executes when this cancellable promise resolves, whether it rejects or not.
let context = firstly {
UIApplication.shared.networkActivityIndicatorVisible = true
// returns a cancellable promise
}.done {
//
}.ensure {
UIApplication.shared.networkActivityIndicatorVisible = false
}.catch {
//
}.cancelContext
//
context.cancel()
- Parameter on: The dispatcher that executes the provided closure.
- Parameter body: The closure that executes when this promise resolves.
- Returns: A new promise, resolved with this promises resolution.
*/
func ensure(on: Dispatcher = conf.D.return, _ body: @escaping () -> Void) -> CancellablePromise<C.T> {
let rp = CancellablePromise<C.T>.pending()
rp.promise.cancelContext = self.cancelContext
self.catchable.pipe { result in
on.dispatch {
body()
switch result {
case .success(let value):
if let error = self.cancelContext.cancelledError {
rp.resolver.reject(error)
} else {
rp.resolver.fulfill(value)
}
case .failure(let error):
rp.resolver.reject(error)
}
}
}
return rp.promise
}
/**
The provided closure executes when this cancellable promise resolves, whether it rejects or not.
The chain waits on the returned `CancellablePromise<Void>`.
let context = firstly {
setup() // returns a cancellable promise
}.done {
//
}.ensureThen {
teardown() // -> CancellablePromise<Void>
}.catch {
//
}.cancelContext
//
context.cancel()
- Parameter on: The dispatcher that executes the provided closure.
- Parameter body: The closure that executes when this promise resolves.
- Returns: A new promise, resolved with this promises resolution.
*/
func ensureThen(on: Dispatcher = conf.D.return, _ body: @escaping () -> CancellablePromise<Void>) -> CancellablePromise<C.T> {
let rp = CancellablePromise<C.T>.pending()
rp.promise.cancelContext = cancelContext
self.catchable.pipe { result in
on.dispatch {
let rv = body()
rp.promise.appendCancelContext(from: rv)
rv.done {
switch result {
case .success(let value):
if let error = self.cancelContext.cancelledError {
rp.resolver.reject(error)
} else {
rp.resolver.fulfill(value)
}
case .failure(let error):
rp.resolver.reject(error)
}
}.catch(policy: .allErrors) {
rp.resolver.reject($0)
}
}
}
return rp.promise
}
/**
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() -> CancellableFinalizer {
return self.catch(policy: .allErrors) {
conf.logHandler(.cauterized($0))
}
}
}
public extension CancellableCatchMixin where C.T == Void {
/**
The provided closure executes when this cancellable promise rejects.
This variant of `recover` ensures that no error is thrown from the handler and allows specifying a catch policy.
- Parameter on: The dispatcher that executes the provided closure.
- 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: Dispatcher = conf.D.map, policy: CatchPolicy = conf.catchPolicy, _ body: @escaping(Error) throws -> Void) -> CancellablePromise<Void> {
let cancelBody = { (error: Error) throws -> Void in
_ = self.cancelContext.removeItems(self.cancelItemList, clearList: true)
try body(error)
if policy == .allErrors {
self.cancelContext.recover()
}
}
let promise = self.catchable.recover(on: on, policy: policy, cancelBody)
if thenable.result != nil && policy == .allErrors {
self.cancelContext.recover()
}
return CancellablePromise(promise: promise, context: self.cancelContext)
}
/**
The provided closure executes when this cancellable promise rejects with the specific error passed in.
Unlike `catch`, `recover` continues the chain. It can return a replacement promise or rethrow.
Use `recover` in circumstances where recovering the chain from certain errors is a possibility.
- Parameter only: The specific error to be recovered.
- Parameter on: The queue to which the provided closure dispatches.
- Parameter body: The handler to execute if this promise is rejected with the provided error.
- Note: Since this method recovers only specific errors, supplying a `CatchPolicy` is unsupported. You can instead specify e.g. your cancellable error.
- SeeAlso: [Cancellation](https://github.com/mxcl/PromiseKit/blob/master/Documentation/CommonPatterns.md#cancellation)
*/
func recover<E: Swift.Error>(only: E, on: Dispatcher = conf.D.map, _ body: @escaping(E) throws -> Void)
-> CancellablePromise<Void> where E: Equatable
{
let cancelBody = { (error: E) throws -> Void in
_ = self.cancelContext.removeItems(self.cancelItemList, clearList: true)
try body(error)
if error.isCancelled {
self.cancelContext.recover()
}
}
let promise = self.catchable.recover(only: only, on: on, cancelBody)
if thenable.result != nil && only.isCancelled {
self.cancelContext.recover()
}
return CancellablePromise(promise: promise, context: self.cancelContext)
}
/**
The provided closure executes when this cancellable promise rejects with an error of the type passed in.
Unlike `catch`, `recover` continues the chain. It can return a replacement promise or rethrow.
Use `recover` in circumstances where recovering the chain from certain errors is a possibility.
- Parameter only: The error type to be recovered.
- Parameter on: The queue to which the provided closure dispatches.
- Parameter body: The handler to execute if this promise is rejected with the provided error type.
- SeeAlso: [Cancellation](https://github.com/mxcl/PromiseKit/blob/master/Documentation/CommonPatterns.md#cancellation)
*/
func recover<E: Swift.Error>(only: E.Type, on: Dispatcher = conf.D.map, policy: CatchPolicy = conf.catchPolicy, _ body: @escaping(E) throws -> Void) -> CancellablePromise<Void> {
let cancelBody = { (error: E) throws -> Void in
_ = self.cancelContext.removeItems(self.cancelItemList, clearList: true)
try body(error)
if policy == .allErrors || error.isCancelled {
self.cancelContext.recover()
}
}
let promise = self.catchable.recover(only: only, on: on, policy: policy, cancelBody)
if thenable.result != nil && policy == .allErrors {
self.cancelContext.recover()
}
return CancellablePromise(promise: promise, context: self.cancelContext)
}
}

View File

@ -1,141 +0,0 @@
import class Foundation.Thread
import Dispatch
/**
A `CancellablePromise` is a functional abstraction around a failable and cancellable asynchronous operation.
At runtime the promise can become a member of a chain of promises, where the `cancelContext` is used to track and cancel (if desired) all promises in this chain.
- See: `CancellableThenable`
*/
public class CancellablePromise<T>: CancellableThenable, CancellableCatchMixin {
/// Delegate `promise` for this CancellablePromise
public let promise: Promise<T>
/// Type of the delegate `thenable`
public typealias U = Promise<T>
/// Delegate `thenable` for this CancellablePromise
public var thenable: U {
return promise
}
/// Type of the delegate `catchable`
public typealias C = Promise<T>
/// Delegate `catchable` for this CancellablePromise
public var catchable: C {
return promise
}
/// The CancelContext associated with this CancellablePromise
public var cancelContext: CancelContext
/// Tracks the cancel items for this CancellablePromise. These items are removed from the associated CancelContext when the promise resolves.
public var cancelItemList: CancelItemList
init(promise: Promise<T>, context: CancelContext? = nil, cancelItemList: CancelItemList? = nil) {
self.promise = promise
self.cancelContext = context ?? CancelContext()
self.cancelItemList = cancelItemList ?? CancelItemList()
}
/// Initialize a new rejected cancellable promise.
public convenience init(cancellable: Cancellable? = nil, error: Error) {
var reject: ((Error) -> Void)!
self.init(promise: Promise { seal in
reject = seal.reject
seal.reject(error)
})
self.appendCancellable(cancellable, reject: reject)
}
/// Initialize a new cancellable promise bound to the provided `Thenable`.
public convenience init<U: Thenable>(_ bridge: U, cancelContext: CancelContext? = nil) where U.T == T {
var promise: Promise<U.T>!
let cancellable: Cancellable!
var reject: ((Error) -> Void)!
if let p = bridge as? Promise<U.T> {
cancellable = p.cancellable
if let r = p.rejectIfCancelled {
promise = p
reject = r
}
} else if let g = bridge as? Guarantee<U.T> {
cancellable = g.cancellable
} else {
cancellable = nil
}
if promise == nil {
// Wrapper promise
promise = Promise { seal in
reject = seal.reject
bridge.done(on: CurrentThreadDispatcher()) {
seal.fulfill($0)
}.catch(on: CurrentThreadDispatcher(), policy: .allErrors) {
seal.reject($0)
}
}
}
self.init(promise: promise, context: cancelContext)
self.appendCancellable(cancellable, reject: reject)
}
/// Initialize a new cancellable promise that can be resolved with the provided `Resolver`.
public convenience init(cancellable: Cancellable? = nil, resolver body: (Resolver<T>) throws -> Void) {
var reject: ((Error) -> Void)!
self.init(promise: Promise { seal in
reject = seal.reject
try body(seal)
})
self.appendCancellable(cancellable, reject: reject)
}
/// Initialize a new cancellable promise using the given Promise and its Resolver.
public convenience init(cancellable: Cancellable? = nil, promise: Promise<T>, resolver: Resolver<T>) {
self.init(promise: promise)
self.appendCancellable(cancellable, reject: resolver.reject)
}
/// - Returns: a tuple of a new cancellable pending promise and its `Resolver`.
public class func pending() -> (promise: CancellablePromise<T>, resolver: Resolver<T>) {
let rp = Promise<T>.pending()
return (promise: CancellablePromise(promise: rp.promise), resolver: rp.resolver)
}
/// Internal function required for `Thenable` conformance.
/// - See: `Thenable.pipe`
public func pipe(to: @escaping (Result<T, Error>) -> Void) {
promise.pipe(to: to)
}
/// - Returns: The current `Result` for this cancellable promise.
/// - See: `Thenable.result`
public var result: Result<T, Error>? {
return promise.result
}
/**
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 wait() throws -> T {
return try promise.wait()
}
}
extension CancellablePromise where T == Void {
/// Initializes a new cancellable promise fulfilled with `Void`
public convenience init() {
self.init(promise: Promise())
}
/// Initializes a new cancellable promise fulfilled with `Void` and with the given ` Cancellable`
public convenience init(cancellable: Cancellable) {
self.init()
self.appendCancellable(cancellable, reject: nil)
}
}

View File

@ -1,520 +0,0 @@
import Dispatch
/**
CancellableThenable represents an asynchronous operation that can be both chained and cancelled. When chained, all CancellableThenable members of the chain are cancelled when `cancel` is called on the associated CancelContext.
*/
public protocol CancellableThenable: class {
/// Type of the delegate `thenable`
associatedtype U: Thenable
/// Delegate `thenable` for this `CancellableThenable`
var thenable: U { get }
/// The `CancelContext` associated with this `CancellableThenable`
var cancelContext: CancelContext { get }
/// Tracks the cancel items for this `CancellableThenable`. These items are removed from the associated `CancelContext` when the thenable resolves.
var cancelItemList: CancelItemList { get }
}
public extension CancellableThenable {
/// Append the `task` and `reject` function for a cancellable task to the cancel context
func appendCancellable(_ cancellable: Cancellable?, reject: ((Error) -> Void)?) {
self.cancelContext.append(cancellable: cancellable, reject: reject, thenable: self)
}
/// Append the cancel context associated with `from` to our cancel context. Typically `from` is a branch of our chain.
func appendCancelContext<Z: CancellableThenable>(from: Z) {
self.cancelContext.append(context: from.cancelContext, thenable: self)
}
/**
Cancel all members of the promise chain and their associated asynchronous operations.
- Parameter error: Specifies the cancellation error to use for the cancel operation, defaults to `PMKError.cancelled`
*/
func cancel(with error: Error = PMKError.cancelled) {
self.cancelContext.cancel(with: error)
}
/**
True if all members of the promise chain have been successfully cancelled, false otherwise.
*/
var isCancelled: Bool {
return self.cancelContext.isCancelled
}
/**
True if `cancel` has been called on the CancelContext associated with this promise, false otherwise. `cancelAttempted` will be true if `cancel` is called on any promise in the chain.
*/
var cancelAttempted: Bool {
return self.cancelContext.cancelAttempted
}
/**
The cancellation error generated when the promise is cancelled, or `nil` if not cancelled.
*/
var cancelledError: Error? {
return self.cancelContext.cancelledError
}
/**
The provided closure executes when this cancellable promise resolves.
This allows chaining promises. The cancellable promise returned by the provided closure is resolved before the cancellable promise returned by this closure resolves.
- Parameter on: The dispatcher that executes the provided closure.
- Parameter body: The closure that executes when this cancellable promise fulfills. It must return a cancellable promise.
- Returns: A new cancellable promise that resolves when the cancellable promise returned from the provided closure resolves. For example:
let context = firstly {
URLSession.shared.dataTask(.promise, with: url1)
}.cancellize().then { response in
transform(data: response.data) // returns a CancellablePromise
}.done { transformation in
//
}.cancelContext
//
context.cancel()
*/
func then<V: CancellableThenable>(on: Dispatcher = conf.D.map, _ body: @escaping (U.T) throws -> V) -> CancellablePromise<V.U.T> {
let cancelItemList = CancelItemList()
let cancelBody = { (value: U.T) throws -> V.U in
if let error = self.cancelContext.removeItems(self.cancelItemList, clearList: true) {
throw error
} else {
let rv = try body(value)
self.cancelContext.append(context: rv.cancelContext, thenableCancelItemList: cancelItemList)
return rv.thenable
}
}
let promise = self.thenable.then(on: on, cancelBody)
return CancellablePromise(promise: promise, context: self.cancelContext, cancelItemList: cancelItemList)
}
/**
The provided closure executes when this cancellable promise resolves.
This allows chaining promises. The promise returned by the provided closure is resolved before the cancellable promise returned by this closure resolves.
- Parameter on: The dispatcher that executes the provided closure.
- Parameter body: The closure that executes when this promise fulfills. It must return a promise (not a cancellable promise).
- Returns: A new cancellable promise that resolves when the promise returned from the provided closure resolves. For example:
let context = firstly {
URLSession.shared.dataTask(.promise, with: url1)
}.cancellize().then { response in
transform(data: response.data) // returns a Promise
}.done { transformation in
//
}.cancelContext
//
context.cancel()
*/
func then<V: Thenable>(on: Dispatcher = conf.D.map, _ body: @escaping (U.T) throws -> V) -> CancellablePromise<V.T> {
let cancelBody = { (value: U.T) throws -> V in
if let error = self.cancelContext.removeItems(self.cancelItemList, clearList: true) {
throw error
} else {
return try body(value)
}
}
let promise = self.thenable.then(on: on, cancelBody)
return CancellablePromise(promise, cancelContext: self.cancelContext)
}
/**
The provided closure is executed when this cancellable promise is resolved.
This is like `then` but it requires the closure to return a non-promise and non-cancellable-promise.
- Parameter on: The queue to which the provided closure dispatches.
- Parameter transform: The closure that is executed when this CancellablePromise is fulfilled. It must return a non-promise and non-cancellable-promise.
- Returns: A new cancellable promise that is resolved with the value returned from the provided closure. For example:
let context = firstly {
URLSession.shared.dataTask(.promise, with: url1)
}.cancellize().map { response in
response.data.length
}.done { length in
//
}.cancelContext
//
context.cancel()
*/
func map<V>(on: Dispatcher = conf.D.map, _ transform: @escaping (U.T) throws -> V) -> CancellablePromise<V> {
let cancelTransform = { (value: U.T) throws -> V in
if let error = self.cancelContext.removeItems(self.cancelItemList, clearList: true) {
throw error
} else {
return try transform(value)
}
}
let promise = self.thenable.map(on: on, cancelTransform)
return CancellablePromise(promise: promise, context: self.cancelContext)
}
/**
The provided closure is executed when this cancellable promise is resolved.
In your closure return an `Optional`, if you return `nil` the resulting cancellable promise is rejected with `PMKError.compactMap`, otherwise the cancellable promise is fulfilled with the unwrapped value.
let context = firstly {
URLSession.shared.dataTask(.promise, with: url)
}.cancellize().compactMap {
try JSONSerialization.jsonObject(with: $0.data) as? [String: String]
}.done { dictionary in
//
}.catch {
// either `PMKError.compactMap` or a `JSONError`
}.cancelContext
//
context.cancel()
*/
func compactMap<V>(on: Dispatcher = conf.D.map, _ transform: @escaping (U.T) throws -> V?) -> CancellablePromise<V> {
let cancelTransform = { (value: U.T) throws -> V? in
if let error = self.cancelContext.removeItems(self.cancelItemList, clearList: true) {
throw error
} else {
return try transform(value)
}
}
let promise = self.thenable.compactMap(on: on, cancelTransform)
return CancellablePromise(promise: promise, context: self.cancelContext)
}
/**
The provided closure is executed when this cancellable promise is resolved.
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 dispatcher that executes the provided closure.
- Parameter body: The closure that is executed when this promise is fulfilled.
- Returns: A new cancellable promise fulfilled as `Void`.
let context = firstly {
URLSession.shared.dataTask(.promise, with: url)
}.cancellize().done { response in
print(response.data)
}.cancelContext
//
context.cancel()
*/
func done(on: Dispatcher = conf.D.return, _ body: @escaping (U.T) throws -> Void) -> CancellablePromise<Void> {
let cancelBody = { (value: U.T) throws -> Void in
if let error = self.cancelContext.removeItems(self.cancelItemList, clearList: true) {
throw error
} else {
try body(value)
}
}
let promise = self.thenable.done(on: on, cancelBody)
return CancellablePromise(promise: promise, context: self.cancelContext)
}
/**
The provided closure is executed when this cancellable promise is resolved.
This is like `done` but it returns the same value that the handler is fed.
`get` immutably accesses the fulfilled value; the returned CancellablePromise maintains that value.
- Parameter on: The dispatcher that executes the provided closure.
- Parameter body: The closure that is executed when this promise is fulfilled.
- Returns: A new cancellable promise that is resolved with the value that the handler is fed. For example:
let context = firstly {
cancellize(Promise.value(1))
}.get { foo in
print(foo, " is 1")
}.done { foo in
print(foo, " is 1")
}.done { foo in
print(foo, " is Void")
}.cancelContext
//
context.cancel()
*/
func get(on: Dispatcher = conf.D.return, _ body: @escaping (U.T) throws -> Void) -> CancellablePromise<U.T> {
return map(on: on) {
try body($0)
return $0
}
}
/**
The provided closure is executed with cancellable promise result.
This is like `get` but provides the Result<U.T> of the CancellablePromise so you can inspect the value of the chain at this point without causing any side effects.
- Parameter on: The dispatcher that executes the provided closure.
- Parameter body: The closure that is executed with Result of CancellablePromise.
- Returns: A new cancellable promise that is resolved with the result that the handler is fed. For example:
promise.tap{ print($0) }.then{ /**/ }
*/
func tap(on: Dispatcher = conf.D.map, _ body: @escaping(Result<U.T, Error>) -> Void) -> CancellablePromise<U.T> {
let rp = CancellablePromise<U.T>.pending()
rp.promise.cancelContext = self.cancelContext
self.thenable.pipe { result in
on.dispatch {
if let error = self.cancelContext.removeItems(self.cancelItemList, clearList: true) {
rp.resolver.reject(error)
} else {
body(result)
rp.resolver.resolve(result)
}
}
}
return rp.promise
}
/// - Returns: a new cancellable promise chained off this cancellable promise but with its value discarded.
func asVoid() -> CancellablePromise<Void> {
return map(on: nil) { _ in }
}
}
public extension CancellableThenable {
/**
- Returns: The error with which this cancellable promise was rejected; `nil` if this promise is not rejected.
*/
var error: Error? {
return thenable.error
}
/**
- Returns: `true` if the cancellable promise has not yet resolved.
*/
var isPending: Bool {
return thenable.isPending
}
/**
- Returns: `true` if the cancellable promise has resolved.
*/
var isResolved: Bool {
return thenable.isResolved
}
/**
- Returns: `true` if the cancellable promise was fulfilled.
*/
var isFulfilled: Bool {
return thenable.isFulfilled
}
/**
- Returns: `true` if the cancellable promise was rejected.
*/
var isRejected: Bool {
return thenable.isRejected
}
/**
- Returns: The value with which this cancellable promise was fulfilled or `nil` if this cancellable promise is pending or rejected.
*/
var value: U.T? {
return thenable.value
}
}
public extension CancellableThenable where U.T: Sequence {
/**
`CancellablePromise<[U.T]>` => `U.T` -> `V` => `CancellablePromise<[V]>`
firstly {
cancellize(Promise.value([1,2,3]))
}.mapValues { integer in
integer * 2
}.done {
// $0 => [2,4,6]
}
*/
func mapValues<V>(on: Dispatcher = conf.D.map, _ transform: @escaping(U.T.Iterator.Element) throws -> V) -> CancellablePromise<[V]> {
return map(on: on) { try $0.map(transform) }
}
/**
`CancellablePromise<[U.T]>` => `U.T` -> `[V]` => `CancellablePromise<[V]>`
firstly {
cancellize(Promise.value([1,2,3]))
}.flatMapValues { integer in
[integer, integer]
}.done {
// $0 => [1,1,2,2,3,3]
}
*/
func flatMapValues<V: Sequence>(on: Dispatcher = conf.D.map, _ transform: @escaping(U.T.Iterator.Element) throws -> V) -> CancellablePromise<[V.Iterator.Element]> {
return map(on: on) { (foo: U.T) in
try foo.flatMap { try transform($0) }
}
}
/**
`CancellablePromise<[U.T]>` => `U.T` -> `V?` => `CancellablePromise<[V]>`
firstly {
cancellize(Promise.value(["1","2","a","3"]))
}.compactMapValues {
Int($0)
}.done {
// $0 => [1,2,3]
}
*/
func compactMapValues<V>(on: Dispatcher = conf.D.map, _ transform: @escaping(U.T.Iterator.Element) throws -> V?) -> CancellablePromise<[V]> {
return map(on: on) { foo -> [V] in
return try foo.compactMap(transform)
}
}
/**
`CancellablePromise<[U.T]>` => `U.T` -> `CancellablePromise<V>` => `CancellablePromise<[V]>`
firstly {
cancellize(Promise.value([1,2,3]))
}.thenMap { integer in
cancellize(Promise.value(integer * 2))
}.done {
// $0 => [2,4,6]
}
*/
func thenMap<V: CancellableThenable>(on: Dispatcher = conf.D.map, _ transform: @escaping(U.T.Iterator.Element) throws -> V) -> CancellablePromise<[V.U.T]> {
return then(on: on) {
when(fulfilled: try $0.map(transform))
}
}
/**
`CancellablePromise<[U.T]>` => `U.T` -> `Promise<V>` => `CancellablePromise<[V]>`
firstly {
Promise.value([1,2,3])
}.cancellize().thenMap { integer in
.value(integer * 2)
}.done {
// $0 => [2,4,6]
}
*/
func thenMap<V: Thenable>(on: Dispatcher = conf.D.map, _ transform: @escaping(U.T.Iterator.Element) throws -> V) -> CancellablePromise<[V.T]> {
return then(on: on) {
when(fulfilled: try $0.map(transform))
}
}
/**
`CancellablePromise<[T]>` => `T` -> `CancellablePromise<[U]>` => `CancellablePromise<[U]>`
firstly {
cancellize(Promise.value([1,2,3]))
}.thenFlatMap { integer in
cancellize(Promise.value([integer, integer]))
}.done {
// $0 => [1,1,2,2,3,3]
}
*/
func thenFlatMap<V: CancellableThenable>(on: Dispatcher = conf.D.map, _ transform: @escaping(U.T.Iterator.Element) throws -> V) -> CancellablePromise<[V.U.T.Iterator.Element]> where V.U.T: Sequence {
return then(on: on) {
when(fulfilled: try $0.map(transform))
}.map(on: nil) {
$0.flatMap { $0 }
}
}
/**
`CancellablePromise<[T]>` => `T` -> `Promise<[U]>` => `CancellablePromise<[U]>`
firstly {
Promise.value([1,2,3])
}.cancellize().thenFlatMap { integer in
.value([integer, integer])
}.done {
// $0 => [1,1,2,2,3,3]
}
*/
func thenFlatMap<V: Thenable>(on: Dispatcher = conf.D.map, _ transform: @escaping(U.T.Iterator.Element) throws -> V) -> CancellablePromise<[V.T.Iterator.Element]> where V.T: Sequence {
return then(on: on) {
when(fulfilled: try $0.map(transform))
}.map(on: nil) {
$0.flatMap { $0 }
}
}
/**
`CancellablePromise<[T]>` => `T` -> Bool => `CancellablePromise<[U]>`
firstly {
cancellize(Promise.value([1,2,3]))
}.filterValues {
$0 > 1
}.done {
// $0 => [2,3]
}
*/
func filterValues(on: Dispatcher = conf.D.map, _ isIncluded: @escaping (U.T.Iterator.Element) -> Bool) -> CancellablePromise<[U.T.Iterator.Element]> {
return map(on: on) {
$0.filter(isIncluded)
}
}
}
public extension CancellableThenable where U.T: Collection {
/// - Returns: a cancellable promise fulfilled with the first value of this `Collection` or, if empty, a promise rejected with PMKError.emptySequence.
var firstValue: CancellablePromise<U.T.Iterator.Element> {
return map(on: nil) { aa in
if let a1 = aa.first {
return a1
} else {
throw PMKError.emptySequence
}
}
}
func firstValue(on: Dispatcher = conf.D.map, where test: @escaping (U.T.Iterator.Element) -> Bool) -> CancellablePromise<U.T.Iterator.Element> {
return map(on: on) {
for x in $0 where test(x) {
return x
}
throw PMKError.emptySequence
}
}
/// - Returns: a cancellable promise fulfilled with the last value of this `Collection` or, if empty, a promise rejected with PMKError.emptySequence.
var lastValue: CancellablePromise<U.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 CancellableThenable where U.T: Sequence, U.T.Iterator.Element: Comparable {
/// - Returns: a cancellable promise fulfilled with the sorted values of this `Sequence`.
func sortedValues(on: Dispatcher = conf.D.map) -> CancellablePromise<[U.T.Iterator.Element]> {
return map(on: on) { $0.sorted() }
}
}

View File

@ -14,197 +14,51 @@ public extension CatchMixin {
of a chain. Often utility promises will not have a catch, instead
delegating the error handling to the caller.
- Parameter on: The dispatcher that executes the provided closure.
- Parameter on: The queue to which the provided closure dispatches.
- Parameter policy: The default policy does not execute your handler for cancellation errors.
- Parameter body: The handler to execute if this promise is rejected.
- Parameter execute: The handler to execute if this promise is rejected.
- Returns: A promise finalizer.
- SeeAlso: [Cancellation](https://github.com/mxcl/PromiseKit/blob/master/Documents/CommonPatterns.md#cancellation)
- SeeAlso: [Cancellation](https://github.com/mxcl/PromiseKit/blob/master/Documentation/CommonPatterns.md#cancellation)
*/
@discardableResult
func `catch`(on: Dispatcher = conf.D.return, policy: CatchPolicy = conf.catchPolicy, _ body: @escaping(Error) -> Void) -> PMKFinalizer {
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 .failure(let error):
case .rejected(let error):
guard policy == .allErrors || !error.isCancelled else {
fallthrough
}
on.dispatch {
on.async(flags: flags) {
body(error)
finalizer.pending.resolve(())
}
case .success:
case .fulfilled:
finalizer.pending.resolve(())
}
}
return finalizer
}
/**
The provided closure executes when this promise rejects with the specific error passed in. A final `catch` is still required at the end of the chain.
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 only: The specific error to be caught and handled (e.g., `PMKError.emptySequence`).
- Parameter on: The dispatcher that executes the provided closure.
- Parameter body: The handler to execute if this promise is rejected with the provided error.
- Returns: A promise finalizer that accepts additional `catch` clauses.
- Note: Since this method handles only specific errors, supplying a `CatchPolicy` is unsupported.
- SeeAlso: [Cancellation](http://promisekit.org/docs/)
*/
func `catch`<E: Swift.Error>(only: E, on: Dispatcher = conf.D.return, _ body: @escaping(E) -> Void) -> PMKCascadingFinalizer where E: Equatable {
let finalizer = PMKCascadingFinalizer()
pipe {
switch $0 {
case .failure(let error as E) where error == only:
on.dispatch {
body(error)
finalizer.pending.resolver.fulfill(())
}
case .failure(let error):
finalizer.pending.resolver.reject(error)
case .success:
finalizer.pending.resolver.fulfill(())
}
}
return finalizer
}
/**
The provided closure executes when this promise rejects with an error of the type passed in. A final `catch` is still required at the end of the chain.
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 only: The error type to be caught and handled (e.g., `PMKError`).
- Parameter on: The dispatcher that executes the provided closure.
- Parameter policy: A `CatchPolicy` that further constrains the errors this handler will see. E.g., if
you are receiving `PMKError` errors, do you want to see even those that result from cancellation?
- Parameter body: The handler to execute if this promise is rejected with the provided error type.
- Returns: A promise finalizer that accepts additional `catch` clauses.
- SeeAlso: [Cancellation](http://promisekit.org/docs/)
*/
func `catch`<E: Swift.Error>(only: E.Type, on: Dispatcher = conf.D.return, policy: CatchPolicy = conf.catchPolicy, _ body: @escaping(E) -> Void) -> PMKCascadingFinalizer {
let finalizer = PMKCascadingFinalizer()
pipe {
switch $0 {
case .failure(let error as E):
guard policy == .allErrors || !error.isCancelled else {
return finalizer.pending.resolver.reject(error)
}
on.dispatch {
body(error)
finalizer.pending.resolver.fulfill(())
}
case .failure(let error):
finalizer.pending.resolver.reject(error)
case .success:
finalizer.pending.resolver.fulfill(())
}
}
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: Dispatcher = conf.D.return, _ body: @escaping () -> Void) {
pending.guarantee.done(on: on) {
public func finally(on: DispatchQueue? = conf.Q.return, flags: DispatchWorkItemFlags? = nil, _ body: @escaping () -> Void) {
pending.guarantee.done(on: on, flags: flags) {
body()
}
}
}
public class PMKCascadingFinalizer {
let pending = Promise<Void>.pending()
/**
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 dispatcher that executes the provided closure.
- Parameter policy: The default policy does not execute your handler for cancellation errors.
- Parameter body: The handler to execute if this promise is rejected.
- Returns: A promise finalizer.
- SeeAlso: [Cancellation](http://promisekit.org/docs/)
*/
@discardableResult
public func `catch`(on: Dispatcher = conf.D.return, policy: CatchPolicy = conf.catchPolicy, _ body: @escaping(Error) -> Void) -> PMKFinalizer {
return pending.promise.catch(on: on, policy: policy) {
body($0)
}
}
/**
The provided closure executes when this promise rejects with the specific error passed in. A final `catch` is still required at the end of the chain.
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 only: The specific error to be caught and handled (e.g., `PMKError.emptySequence`).
- Parameter on: The dispatcher that executes the provided closure.
- Parameter body: The handler to execute if this promise is rejected with the provided error.
- Returns: A promise finalizer that accepts additional `catch` clauses.
- Note: Since this method handles only specific errors, supplying a `CatchPolicy` is unsupported.
- SeeAlso: [Cancellation](http://promisekit.org/docs/)
*/
public func `catch`<E: Swift.Error>(only: E, on: Dispatcher = conf.D.return, _ body: @escaping(E) -> Void) -> PMKCascadingFinalizer where E: Equatable {
return pending.promise.catch(only: only, on: on) {
body($0)
}
}
/**
The provided closure executes when this promise rejects with an error of the type passed in. A final `catch` is still required at the end of the chain.
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 only: The error type to be caught and handled (e.g., `PMKError`).
- Parameter on: The dispatcher that executes the provided closure.
- Parameter body: The handler to execute if this promise is rejected with the provided error type.
- Returns: A promise finalizer that accepts additional `catch` clauses.
- SeeAlso: [Cancellation](http://promisekit.org/docs/)
*/
public func `catch`<E: Swift.Error>(only: E.Type, on: Dispatcher = conf.D.return, policy: CatchPolicy = conf.catchPolicy, _ body: @escaping(E) -> Void) -> PMKCascadingFinalizer {
return pending.promise.catch(only: only, on: on, policy: policy) {
body($0)
}
}
/**
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
public func cauterize() -> PMKFinalizer {
return self.catch {
conf.logHandler(.cauterized($0))
}
}
}
public extension CatchMixin {
/**
The provided closure executes when this promise rejects.
Unlike `catch`, `recover` continues the chain. It can return a replacement promise or rethrow.
Unlike `catch`, `recover` continues the chain.
Use `recover` in circumstances where recovering the chain from certain errors is a possibility. For example:
firstly {
@ -214,144 +68,52 @@ public extension CatchMixin {
return .value(CLLocation.chicago)
}
- Parameter on: The dispatcher that executes the provided closure.
- Parameter policy: The default policy does not execute your handler for cancellation errors.
- 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/Documents/CommonPatterns.md#cancellation)
- SeeAlso: [Cancellation](https://github.com/mxcl/PromiseKit/blob/master/Documentation/CommonPatterns.md#cancellation)
*/
func recover<U: Thenable>(on: Dispatcher = conf.D.map, policy: CatchPolicy = conf.catchPolicy, _ body: @escaping(Error) throws -> U) -> Promise<T> where U.T == T {
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 .success(let value):
rp.box.seal(.success(value))
case .failure(let error):
case .fulfilled(let value):
rp.box.seal(.fulfilled(value))
case .rejected(let error):
if policy == .allErrors || !error.isCancelled {
on.dispatch {
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(.failure(error))
rp.box.seal(.rejected(error))
}
}
} else {
rp.box.seal(.failure(error))
rp.box.seal(.rejected(error))
}
}
}
return rp
}
/**
The provided closure executes when this promise rejects with the specific error passed in.
Unlike `catch`, `recover` continues the chain. It can return a replacement promise or rethrow.
Use `recover` in circumstances where recovering the chain from certain errors is a possibility. For example:
firstly {
CLLocationManager.requestLocation()
}.recover(CLError.unknownLocation) {
return .value(CLLocation.chicago)
}
- Parameter only: The specific error to be recovered (e.g., `PMKError.emptySequence`)
- Parameter on: The dispatcher that executes the provided closure.
- Parameter body: The handler to execute if this promise is rejected with the provided error.
- Note: Since this method recovers only specific errors, supplying a `CatchPolicy` is unsupported.
- SeeAlso: [Cancellation](http://promisekit.org/docs/)
*/
func recover<U: Thenable, E: Swift.Error>(only: E, on: Dispatcher = conf.D.map, _ body: @escaping(E) throws -> U) -> Promise<T> where U.T == T, E: Equatable {
let rp = Promise<U.T>(.pending)
pipe {
switch $0 {
case .success(let value):
rp.box.seal(.success(value))
case .failure(let error as E) where error == only:
on.dispatch {
do {
let rv = try body(error)
guard rv !== rp else { throw PMKError.returnedSelf }
rv.pipe(to: rp.box.seal)
} catch {
rp.box.seal(.failure(error))
}
}
case .failure(let error):
rp.box.seal(.failure(error))
}
}
return rp
}
/**
The provided closure executes when this promise rejects with an error of the type passed in.
Unlike `catch`, `recover` continues the chain. It can return a replacement promise or rethrow.
Use `recover` in circumstances where recovering the chain from certain errors is a possibility. For example:
firstly {
API.fetchData()
}.recover(FetchError.self) { error in
guard case .missingImage(let partialData) = error else { throw error }
//
return .value(dataWithDefaultImage)
}
- Parameter only: The error type to be recovered (e.g., `PMKError`).
- Parameter on: The dispatcher that executes the provided closure.
- Parameter policy: The default policy does not execute your handler for cancellation errors.
- Parameter body: The handler to execute if this promise is rejected with the provided error type.
- SeeAlso: [Cancellation](http://promisekit.org/docs/)
*/
func recover<U: Thenable, E: Swift.Error>(only: E.Type, on: Dispatcher = conf.D.map, policy: CatchPolicy = conf.catchPolicy, _ body: @escaping(E) throws -> U) -> Promise<T> where U.T == T {
let rp = Promise<U.T>(.pending)
pipe {
switch $0 {
case .success(let value):
rp.box.seal(.success(value))
case .failure(let error as E):
if policy == .allErrors || !error.isCancelled {
on.dispatch {
do {
let rv = try body(error)
guard rv !== rp else { throw PMKError.returnedSelf }
rv.pipe(to: rp.box.seal)
} catch {
rp.box.seal(.failure(error))
}
}
} else {
rp.box.seal(.failure(error))
}
case .failure(let error):
rp.box.seal(.failure(error))
}
}
return rp
}
/**
The provided closure executes when this promise rejects.
This variant of `recover` requires the handler to return a Guarantee; your closure cannot `throw`.
It is logically impossible for this variant to accept a `catchPolicy`. All errors will be presented
to your closure for processing.
- Parameter on: The dispatcher that executes the provided closure.
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/Documents/CommonPatterns.md#cancellation)
- SeeAlso: [Cancellation](https://github.com/mxcl/PromiseKit/blob/master/Documentation/CommonPatterns.md#cancellation)
*/
@discardableResult
func recover(on: Dispatcher = conf.D.map, _ body: @escaping(Error) -> Guarantee<T>) -> Guarantee<T> {
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 .success(let value):
case .fulfilled(let value):
rg.box.seal(value)
case .failure(let error):
on.dispatch {
case .rejected(let error):
on.async(flags: flags) {
body(error).pipe(to: rg.box.seal)
}
}
@ -372,14 +134,14 @@ public extension CatchMixin {
//
}
- Parameter on: The dispatcher that executes the provided closure.
- 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: Dispatcher = conf.D.return, _ body: @escaping () -> Void) -> Promise<T> {
func ensure(on: DispatchQueue? = conf.Q.return, flags: DispatchWorkItemFlags? = nil, _ body: @escaping () -> Void) -> Promise<T> {
let rp = Promise<T>(.pending)
pipe { result in
on.dispatch {
on.async(flags: flags) {
body()
rp.box.seal(result)
}
@ -401,14 +163,14 @@ public extension CatchMixin {
//
}
- Parameter on: The dispatcher that executes the provided closure.
- 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: Dispatcher = conf.D.return, _ body: @escaping () -> Guarantee<Void>) -> Promise<T> {
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.dispatch {
on.async(flags: flags) {
body().done {
rp.box.seal(result)
}
@ -418,6 +180,7 @@ public extension CatchMixin {
}
/**
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.
@ -436,23 +199,21 @@ 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 error types,
including cancellation.
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 dispatcher that executes the provided closure.
- 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/Documents/CommonPatterns.md#cancellation)
- SeeAlso: [Cancellation](https://github.com/mxcl/PromiseKit/blob/master/Documentation/CommonPatterns.md#cancellation)
*/
@discardableResult
func recover(on: Dispatcher = conf.D.map, _ body: @escaping(Error) -> Void) -> Guarantee<Void> {
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 .success:
case .fulfilled:
rg.box.seal(())
case .failure(let error):
on.dispatch {
case .rejected(let error):
on.async(flags: flags) {
body(error)
rg.box.seal(())
}
@ -464,103 +225,32 @@ public extension CatchMixin where T == Void {
/**
The provided closure executes when this promise rejects.
This variant of `recover` ensures that no error is thrown from the handler
and allows you to specify a catch policy.
This variant of `recover` ensures that no error is thrown from the handler and allows specifying a catch policy.
- Parameter on: The dispatcher that executes the provided closure.
- 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/Documents/CommonPatterns.md#cancellation)
- SeeAlso: [Cancellation](https://github.com/mxcl/PromiseKit/blob/master/Documentation/CommonPatterns.md#cancellation)
*/
func recover(on: Dispatcher = conf.D.map, policy: CatchPolicy = conf.catchPolicy, _ body: @escaping(Error) throws -> Void) -> Promise<Void> {
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 .success:
rg.box.seal(.success(()))
case .failure(let error):
case .fulfilled:
rg.box.seal(.fulfilled(()))
case .rejected(let error):
if policy == .allErrors || !error.isCancelled {
on.dispatch {
on.async(flags: flags) {
do {
rg.box.seal(.success(try body(error)))
rg.box.seal(.fulfilled(try body(error)))
} catch {
rg.box.seal(.failure(error))
rg.box.seal(.rejected(error))
}
}
} else {
rg.box.seal(.failure(error))
rg.box.seal(.rejected(error))
}
}
}
return rg
}
/**
The provided closure executes when this promise rejects with the specific error passed in.
Unlike `catch`, `recover` continues the chain. It can return a replacement promise or rethrow.
Use `recover` in circumstances where recovering the chain from certain errors is a possibility.
- Parameter only: The specific error to be recovered (e.g., `PMKError.emptySequence`)
- Parameter on: The dispatcher that executes the provided closure.
- Parameter body: The handler to execute if this promise is rejected with the provided error.
- Note: Since this method recovers only specific errors, supplying a `CatchPolicy` is unsupported.
- SeeAlso: [Cancellation](http://promisekit.org/docs/)
*/
func recover<E: Swift.Error>(only: E, on: Dispatcher = conf.D.map, _ body: @escaping(E) throws -> Void) -> Promise<Void> where E: Equatable {
let rp = Promise<Void>(.pending)
pipe {
switch $0 {
case .success:
rp.box.seal(.success(()))
case .failure(let error as E) where error == only:
on.dispatch {
do {
rp.box.seal(.success(try body(error)))
} catch {
rp.box.seal(.failure(error))
}
}
case .failure(let error):
rp.box.seal(.failure(error))
}
}
return rp
}
/**
The provided closure executes when this promise rejects with an error of the type passed in.
Unlike `catch`, `recover` continues the chain. It can return a replacement promise or rethrow.
Use `recover` in circumstances where recovering the chain from certain errors is a possibility.
- Parameter only: The error type to be recovered (e.g., `PMKError`).
- Parameter on: The dispatcher that executes the provided closure.
- Parameter policy: The default policy does not execute your handler for cancellation errors.
- Parameter body: The handler to execute if this promise is rejected with the provided error type.
- SeeAlso: [Cancellation](http://promisekit.org/docs/)
*/
func recover<E: Swift.Error>(only: E.Type, on: Dispatcher = conf.D.map, policy: CatchPolicy = conf.catchPolicy, _ body: @escaping(E) throws -> Void) -> Promise<Void> {
let rp = Promise<Void>(.pending)
pipe {
switch $0 {
case .success:
rp.box.seal(.success(()))
case .failure(let error as E):
if policy == .allErrors || !error.isCancelled {
on.dispatch {
do {
rp.box.seal(.success(try body(error)))
} catch {
rp.box.seal(.failure(error))
}
}
} else {
rp.box.seal(.failure(error))
}
case .failure(let error):
rp.box.seal(.failure(error))
}
}
return rp
}
}

View File

@ -8,18 +8,8 @@ import Dispatch
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 {
/// Backward compatibility: the default Dispatcher to which handlers dispatch, represented as DispatchQueues.
public var Q: (map: DispatchQueue?, return: DispatchQueue?) {
get {
let convertedMap = D.map is CurrentThreadDispatcher ? nil : D.map as? DispatchQueue
let convertedReturn = D.return is CurrentThreadDispatcher ? nil : D.return as? DispatchQueue
return (map: convertedMap, return: convertedReturn)
}
set { D = (map: newValue.map ?? CurrentThreadDispatcher(), return: newValue.return ?? CurrentThreadDispatcher()) }
}
/// The default Dispatchers to which promise handlers dispatch
public var D: (map: Dispatcher, return: Dispatcher) = (map: DispatchQueue.main, return: DispatchQueue.main)
/// 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
@ -27,8 +17,17 @@ public struct PMKConfiguration {
/// 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) -> () = { event in
print(event.asString())
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)")
}
}
}

View File

@ -5,9 +5,9 @@ extension Promise: CustomStringConvertible {
switch result {
case nil:
return "Promise(…\(T.self))"
case .failure(let error)?:
case .rejected(let error)?:
return "Promise(\(error))"
case .success(let value)?:
case .fulfilled(let value)?:
return "Promise(\(value))"
}
}
@ -19,10 +19,26 @@ extension Promise: CustomDebugStringConvertible {
switch box.inspect() {
case .pending(let handlers):
return "Promise<\(T.self)>.pending(handlers: \(handlers.bodies.count))"
case .resolved(.failure(let error)):
return "Promise<\(T.self)>.failure(\(type(of: error)).\(error))"
case .resolved(.success(let value)):
return "Promise<\(T.self)>.success(\(value))"
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,120 +0,0 @@
import Dispatch
/// A `PromiseKit` abstraction of a `DispatchQueue` that allows for a more
/// flexible variety of implementations. (For technical reasons,
/// `DispatchQueue` itself cannot be subclassed.)
///
/// `Dispatcher`s define a `dispatch` method that executes a supplied closure.
/// Execution may be synchronous or asynchronous, serial
/// or concurrent, and can occur on any thread.
///
/// All `DispatchQueue`s are also valid `Dispatcher`s.
public protocol Dispatcher {
func dispatch(_ body: @escaping () -> Void)
}
/// A `Dispatcher` that bundles a `DispatchQueue` with
/// a `DispatchGroup`, a set of `DispatchWorkItemFlags`, and a
/// quality-of-service level. Closures dispatched through this
/// `Dispatcher` will be submitted to the underlying `DispatchQueue`
/// with the supplied components.
public struct DispatchQueueDispatcher: Dispatcher {
let queue: DispatchQueue
let group: DispatchGroup?
let qos: DispatchQoS?
let flags: DispatchWorkItemFlags?
public init(queue: DispatchQueue, group: DispatchGroup? = nil, qos: DispatchQoS? = nil, flags: DispatchWorkItemFlags? = nil) {
self.queue = queue
self.group = group
self.qos = qos
self.flags = flags
}
public func dispatch(_ body: @escaping () -> Void) {
queue.asyncD(group: group, qos: qos, flags: flags, execute: body)
}
}
/// A `Dispatcher` class that executes all closures synchronously on
/// the current thread.
///
/// Useful for temporarily disabling asynchrony and
/// multithreading while debugging `PromiseKit` chains.
///
/// You can set `PromiseKit`'s default dispatching behavior to this mode
/// by setting `conf.Q.map` and/or `conf.Q.return` to `nil`. (This is the
/// same as assigning an instance of `CurrentThreadDispatcher` to these
/// variables.)
public struct CurrentThreadDispatcher: Dispatcher {
public func dispatch(_ body: () -> Void) {
body()
}
}
extension DispatchQueue: Dispatcher {
public func dispatch(_ body: @escaping () -> Void) {
async(execute: body)
}
}
// Used as default parameter for backward compatibility since clients may explicitly
// specify "nil" to turn off dispatching. We need to distinguish three cases: explicit
// queue, explicit nil, and no value specified. Dispatchers from conf.D cannot directly
// be used as default parameter values because they are not necessarily DispatchQueues.
public extension DispatchQueue {
static var pmkDefault = DispatchQueue(label: "org.promisekit.sentinel")
}
public extension DispatchQueue {
/// Converts a `DispatchQueue` with given dispatching parameters into a `Dispatcher`
func asDispatcher(group: DispatchGroup? = nil, qos: DispatchQoS? = nil, flags: DispatchWorkItemFlags? = nil) -> Dispatcher {
if group == nil && qos == nil && flags == nil {
return self
}
return DispatchQueueDispatcher(queue: self, group: group, qos: qos, flags: flags)
}
}
// Avoid having to hard-code any particular defaults for qos or flags
internal extension DispatchQueue {
final func asyncD(group: DispatchGroup? = nil, qos: DispatchQoS? = nil, flags: DispatchWorkItemFlags? = nil, execute body: @escaping () -> Void) {
switch (qos, flags) {
case (nil, nil):
async(group: group, execute: body)
case (nil, let flags?):
async(group: group, flags: flags, execute: body)
case (let qos?, nil):
async(group: group, qos: qos, execute: body)
case (let qos?, let flags?):
async(group: group, qos: qos, flags: flags, execute: body)
}
}
}
// This hairball disambiguates all the various combinations of explicit arguments, default
// arguments, and configured defaults. In particular, a method that is given explicit work item
// flags but no DispatchQueue should still work (that is, the dispatcher should use those flags)
// as long as the configured default is actually some kind of DispatchQueue.
internal func selectDispatcher(given: DispatchQueue?, configured: Dispatcher, flags: DispatchWorkItemFlags?) -> Dispatcher {
guard let given = given else {
if flags != nil {
conf.logHandler(.nilDispatchQueueWithFlags)
}
return CurrentThreadDispatcher()
}
if given !== DispatchQueue.pmkDefault {
return given.asDispatcher(flags: flags)
} else if let flags = flags, let configured = configured as? DispatchQueue {
return configured.asDispatcher(flags: flags)
} else if flags != nil {
conf.logHandler(.extraneousFlagsSpecified)
}
return configured
}

View File

@ -1,41 +0,0 @@
import Foundation
/// A PromiseKit Dispatcher that allows no more than X simultaneous
/// executions at once.
public final class ConcurrencyLimitedDispatcher: Dispatcher {
let queue: Dispatcher
let serializer: DispatchQueue = DispatchQueue(label: "CLD serializer")
let semaphore: DispatchSemaphore
/// A `PromiseKit` `Dispatcher` that allows no more than X simultaneous
/// executions at once.
///
/// - Parameters:
/// - limit: The number of executions that may run at once.
/// - queue: The DispatchQueue or Dispatcher on which to perform executions.
/// Should be some form of concurrent queue.
public init(limit: Int, queue: Dispatcher = DispatchQueue.global(qos: .background)) {
self.queue = queue
semaphore = DispatchSemaphore(value: limit)
}
public convenience init(limit: Int, queue: DispatchQueue) {
self.init(limit: limit, queue: queue as Dispatcher)
}
public func dispatch(_ body: @escaping () -> Void) {
serializer.async {
self.semaphore.wait()
self.queue.dispatch {
body()
self.semaphore.signal()
}
}
}
}

View File

@ -1,31 +0,0 @@
#if !os(Linux)
import Foundation
import CoreData
public extension NSManagedObjectContext {
var dispatcher: CoreDataDispatcher {
return CoreDataDispatcher(self)
}
}
/// A `Dispatcher` that dispatches onto the threads associated with
/// `NSManagedObjectContext`s, allowing Core Data operations to be
/// handled using promises.
public struct CoreDataDispatcher: Dispatcher {
let context: NSManagedObjectContext
public init(_ context: NSManagedObjectContext) {
self.context = context
}
public func dispatch(_ body: @escaping () -> Void) {
context.perform(body)
}
}
#endif

View File

@ -1,64 +0,0 @@
import Foundation
// Simple queue implementation with storage recovery
fileprivate let arraySizeWorthCompacting = 100
fileprivate let minUtilization = 0.6
struct Queue<T> {
var elements: [T?] = []
var head = 0
let maxDepth: Int?
init(maxDepth: Int? = nil) {
self.maxDepth = maxDepth
}
var isEmpty: Bool {
return head >= elements.count
}
var count: Int {
return elements.count - head
}
mutating func enqueue(_ item: T) {
elements.append(item)
if let maxDepth = maxDepth, count > maxDepth {
_ = dequeue()
}
}
mutating func dequeue() -> T {
assert(!isEmpty, "Dequeue attempt on an empty Queue")
defer {
elements[head] = nil
head += 1
maybeCompactStorage()
}
return elements[head]!
}
private mutating func maybeCompactStorage() {
let n = elements.count
if n > arraySizeWorthCompacting && head > Int(Double(n) * (1 - minUtilization)) {
compactStorage()
}
}
mutating func compactStorage() {
if isEmpty {
elements.removeAll(keepingCapacity: false)
} else {
elements.removeFirst(head)
}
head = 0
}
mutating func purge() {
elements.removeAll(keepingCapacity: false)
head = 0
}
}

View File

@ -1,110 +0,0 @@
import Foundation
/// A `PromiseKit` `Dispatcher` that dispatches X closures every Y seconds,
/// on average.
///
/// This implementation is O(1) in both space and time, but it uses approximate
/// time accounting. Over the long term, the rate converges to a rate of X/Y,
/// but the transient burst rate will be up to 2X/Y in some situations.
///
/// For a completely accurate rate limiter that dispatches as rapidly as
/// possible, see `StrictRateLimitedDispatcher`. That implementation requires
/// additional storage.
///
/// Executions are paced by start time, not by completion, so it's possible to
/// end up with more than X closures running concurrently in some circumstances.
///
/// There is no guarantee that you will reach a given dispatch rate. There are not
/// an infinite number of threads available, and GCD scheduling has limited accuracy.
///
/// 100% thread safe.
public final class RateLimitedDispatcher: RateLimitedDispatcherBase {
private var tokensInBucket: Double = 0
private var latestAccrual: DispatchTime = DispatchTime.now()
private var retryWorkItem: DispatchWorkItem? { willSet { retryWorkItem?.cancel() }}
private var tokensPerSecond: Double { return Double(maxDispatches) / interval }
/// A `PromiseKit` `Dispatcher` that dispatches X executions every Y
/// seconds, on average.
///
/// This version is O(1) in space and time but uses an approximate algorithm with
/// burst rates up to 2X per Y seconds. For a more accurate implementation, use
/// `StrictRateLimitedDispatcher`.
///
/// - Parameters:
/// - maxDispatches: The number of executions that may be dispatched within a given interval.
/// - perInterval: The length of the reference interval, in seconds.
/// - queue: The DispatchQueue or Dispatcher on which to perform executions. May be serial or concurrent.
override public init(maxDispatches: Int, perInterval interval: TimeInterval, queue: Dispatcher = DispatchQueue.global()) {
latestAccrual = DispatchTime.now()
super.init(maxDispatches: maxDispatches, perInterval: interval, queue: queue)
tokensInBucket = Double(maxDispatches)
}
public convenience init(maxDispatches: Int, perInterval interval: TimeInterval, queue: DispatchQueue) {
self.init(maxDispatches: maxDispatches, perInterval: interval, queue: queue as Dispatcher)
}
override func dispatchFromQueue() {
guard undispatched.count > 0 else { return }
cleanupNonce += 1
let now = DispatchTime.now()
let tokensToAdd = (now - latestAccrual) * tokensPerSecond
tokensInBucket = min(Double(maxDispatches - nDispatched), tokensInBucket + tokensToAdd)
latestAccrual = now
// print("runqueue \(now.rawValue), nDispatched = \(nDispatched), tokens = \(tokensInBucket), undispatched = \(undispatched.count)")
var didDispatch = false
while tokensInBucket >= 1.0 && !undispatched.isEmpty && nDispatched < maxDispatches {
didDispatch = true
tokensInBucket -= 1.0
nDispatched += 1
let body = undispatched.dequeue()
queue.dispatch {
self.serializer.async {
self.recordActualStart()
}
body()
}
}
if !didDispatch {
scheduleRetry()
}
}
private func scheduleRetry() {
guard retryWorkItem == nil && !undispatched.isEmpty && nDispatched < maxDispatches else { return }
let tokenDeficit = 1 - tokensInBucket
let secondsToGo = tokenDeficit / tokensPerSecond
let deadline = latestAccrual + secondsToGo + 1.0E-6
retryWorkItem = DispatchWorkItem { [weak self] in
self?.retryWorkItem = nil
self?.dispatchFromQueue()
}
serializer.asyncAfter(deadline: deadline, execute: retryWorkItem!)
}
override func cleanup(_ nonce: Int64) {
super.cleanup(nonce)
guard nonce == cleanupNonce else { return }
tokensInBucket = Double(maxDispatches) // Avoid accumulating roundoff errors
}
}
extension DispatchTime {
static func -(a: DispatchTime, b: DispatchTime) -> TimeInterval {
let delta = a.uptimeNanoseconds - b.uptimeNanoseconds
return TimeInterval(delta) / 1_000_000_000
}
}

View File

@ -1,57 +0,0 @@
import Foundation
public class RateLimitedDispatcherBase: Dispatcher {
let maxDispatches: Int
let interval: TimeInterval
let queue: Dispatcher
let serializer = DispatchQueue(label: "RLD serializer")
var nDispatched = 0
var undispatched = Queue<() -> Void>()
var cleanupNonce: Int64 = 0
var cleanupWorkItem: DispatchWorkItem? { willSet { cleanupWorkItem?.cancel() }}
public init(maxDispatches: Int, perInterval interval: TimeInterval, queue: Dispatcher = DispatchQueue.global()) {
self.maxDispatches = maxDispatches
self.interval = interval
self.queue = queue
}
public func dispatch(_ body: @escaping () -> Void) {
serializer.async {
self.undispatched.enqueue(body)
self.dispatchFromQueue()
}
}
func dispatchFromQueue() {
fatalError("Subclass responsibility")
}
func recordActualStart() {
nDispatched -= 1
dispatchFromQueue()
if nDispatched == 0 && undispatched.isEmpty {
scheduleCleanup()
}
}
func scheduleCleanup() {
cleanupWorkItem = DispatchWorkItem { [ weak self, nonce = self.cleanupNonce ] in
self?.cleanup(nonce)
}
serializer.asyncAfter(deadline: DispatchTime.now() + interval, execute: cleanupWorkItem!)
}
func cleanup(_ nonce: Int64) {
// Calls to cleanup() have to go through the serializer queue, so by by the time
// we get here, more activity may have occurred. Ergo, verify nonce.
guard nonce == cleanupNonce else { return }
undispatched.compactStorage()
cleanupWorkItem = nil
}
}

View File

@ -1,106 +0,0 @@
import Foundation
/// A `PromiseKit` `Dispatcher` that dispatches no more than X closures every Y
/// seconds. This is a sliding window, so executions occur as rapidly as
/// possible without exceeding X in any Y-second period.
///
/// This version implements perfectly accurate timing, so it must (temporarily)
/// track up to X previous execution times and is thus O(X) in space.
///
/// For a "pretty good" approach to rate limiting that does not consume
/// additional storage, see `RateLimitedDispatcher`.
///
/// Executions are paced by start time, not by completion, so it's possible to
/// end up with more than X closures running concurrently in some circumstances.
///
/// There is no guarantee that you will reach a given dispatch rate. There are not
/// an infinite number of threads available, and GCD scheduling has limited accuracy.
/// The only guarantee is that dispatching will never exceed the requested rate.
///
/// 100% thread safe.
public final class StrictRateLimitedDispatcher: RateLimitedDispatcherBase {
internal var startTimeHistory: Queue<DispatchTime>
private var immediateDispatchesAvailable: Int
private var latestDeadline = DispatchTime(uptimeNanoseconds: 0)
/// A `PromiseKit` `Dispatcher` that dispatches no more than X executions every Y
/// seconds. This is a sliding window, so executions occur as rapidly as
/// possible without exceeding X in any Y-second period. O(X) in space.
///
/// For a "pretty good" approach to rate limiting that does not consume
/// additional storage, see `RateLimitedDispatcher`.
///
/// - Parameters:
/// - maxDispatches: The number of executions that may be dispatched within a given interval.
/// - perInterval: The length of the reference interval, in seconds.
/// - queue: The DispatchQueue or Dispatcher on which to perform executions. May be serial or concurrent.
override public init(maxDispatches: Int, perInterval interval: TimeInterval, queue: Dispatcher = DispatchQueue.global()) {
startTimeHistory = Queue<DispatchTime>(maxDepth: maxDispatches)
immediateDispatchesAvailable = maxDispatches
super.init(maxDispatches: maxDispatches, perInterval: interval, queue: queue)
}
public convenience init(maxDispatches: Int, perInterval interval: TimeInterval, queue: DispatchQueue) {
self.init(maxDispatches: maxDispatches, perInterval: interval, queue: queue as Dispatcher)
}
override func dispatchFromQueue() {
cleanupNonce += 1
guard nDispatched < maxDispatches else { return }
guard !undispatched.isEmpty else { return }
let accountedFor = nDispatched + startTimeHistory.count + immediateDispatchesAvailable
assert(accountedFor == maxDispatches, "Dispatcher bookkeeping problem")
var deadline = DispatchTime.now()
if immediateDispatchesAvailable > 0 {
immediateDispatchesAvailable -= 1
} else {
// Use the start time of a previous closure as a time reference. In practice,
// past start times will normally be reported and recorded in monotonically
// increasing sequence, which yields optimal scheduling. However, this is all
// potentially multithreaded, so there are no order guarantees. However, if any
// start times ARE out of order, the algorithm is still correct: for each
// dispatched item, another may be scheduled interval seconds later. Like kanban.
deadline = max(deadline, startTimeHistory.dequeue() + interval)
}
// Enforce a monotonically increasing outbound schedule to keep calls in order
if deadline <= latestDeadline {
deadline = DispatchTime(uptimeNanoseconds: latestDeadline.uptimeNanoseconds + 1)
}
let body = undispatched.dequeue()
// A Dispatcher has no asyncAfter; use the serializer queue for timing
serializer.asyncAfter(deadline: deadline) {
self.queue.dispatch {
let now = DispatchTime.now()
self.serializer.async {
self.recordActualStartTime(now)
}
body()
}
}
latestDeadline = deadline
nDispatched += 1
}
private func recordActualStartTime(_ time: DispatchTime) {
startTimeHistory.enqueue(time)
super.recordActualStart()
}
override func cleanup(_ nonce: Int64) {
super.cleanup(nonce)
guard nonce == cleanupNonce else { return }
startTimeHistory.purge() // We're at least an interval past last start
immediateDispatchesAvailable = maxDispatches
}
}

View File

@ -18,10 +18,7 @@ public enum PMKError: Error {
/// The operation was cancelled
case cancelled
/// The operation timed out and was cancelled
case timedOut
/// `nil` was returned from `flatMap`
@available(*, deprecated, message: "See: `compactMap`")
case flatMap(Any, Any.Type)
@ -52,8 +49,6 @@ extension PMKError: CustomDebugStringConvertible {
return "Bad input was provided to a PromiseKit function"
case .cancelled:
return "The asynchronous sequence was cancelled"
case .timedOut:
return "The asynchronous sequence timed out"
case .emptySequence:
return "The first or last element was requested for an empty sequence"
}
@ -81,8 +76,6 @@ extension Error {
throw self
} catch PMKError.cancelled {
return true
} catch PMKError.timedOut {
return true
} catch let error as CancellableError {
return error.isCancelled
} catch URLError.cancelled {
@ -90,9 +83,10 @@ extension Error {
} catch CocoaError.userCancelled {
return true
} catch {
#if os(macOS) || os(iOS) || os(tvOS)
let pair = { ($0.domain, $0.code) }(error as NSError)
return ("SKErrorDomain", 2) == pair
#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
#endif

View File

@ -23,15 +23,9 @@ public final class Guarantee<T>: Thenable {
body(box.seal)
}
/// Returns a pending `Guarantee` that can be resolved with the provided closures parameter.
public convenience init(cancellable: Cancellable, resolver body: (@escaping(T) -> Void) -> Void) {
self.init(resolver: body)
setCancellable(cancellable)
}
/// - See: `Thenable.pipe`
public func pipe(to: @escaping(Result<T, Error>) -> Void) {
pipe{ to(.success($0)) }
public func pipe(to: @escaping(Result<T>) -> Void) {
pipe{ to(.fulfilled($0)) }
}
func pipe(to: @escaping(T) -> Void) {
@ -51,23 +45,20 @@ public final class Guarantee<T>: Thenable {
}
/// - See: `Thenable.result`
public var result: Result<T, Error>? {
public var result: Result<T>? {
switch box.inspect() {
case .pending:
return nil
case .resolved(let value):
return .success(value)
return .fulfilled(value)
}
}
final private class Box<T>: EmptyBox<T> {
var cancelled = false
deinit {
switch inspect() {
case .pending:
if !cancelled {
PromiseKit.conf.logHandler(.pendingGuaranteeDeallocated)
}
PromiseKit.conf.logHandler(.pendingGuaranteeDeallocated)
case .resolved:
break
}
@ -82,43 +73,14 @@ public final class Guarantee<T>: Thenable {
public class func pending() -> (guarantee: Guarantee<T>, resolve: (T) -> Void) {
return { ($0, $0.box.seal) }(Guarantee<T>(.pending))
}
var cancellable: Cancellable?
public func setCancellable(_ cancellable: Cancellable) {
if let gb = (box as? Guarantee<T>.Box<T>) {
self.cancellable = CancellableWrapper(box: gb, cancellable: cancellable)
} else {
self.cancellable = cancellable
}
}
final private class CancellableWrapper: Cancellable {
let box: Guarantee<T>.Box<T>
let cancellable: Cancellable
init(box: Guarantee<T>.Box<T>, cancellable: Cancellable) {
self.box = box
self.cancellable = cancellable
}
func cancel() {
box.cancelled = true
cancellable.cancel()
}
var isCancelled: Bool {
return cancellable.isCancelled
}
}
}
public extension Guarantee {
@discardableResult
func done(on: Dispatcher = conf.D.return, _ body: @escaping(T) -> Void) -> Guarantee<Void> {
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.dispatch {
on.async(flags: flags) {
body(value)
rg.box.seal(())
}
@ -126,28 +88,40 @@ public extension Guarantee {
return rg
}
func get(on: Dispatcher = conf.D.return, _ body: @escaping (T) -> Void) -> Guarantee<T> {
return map(on: on) {
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: Dispatcher = conf.D.map, _ body: @escaping(T) -> U) -> Guarantee<U> {
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.dispatch {
on.async(flags: flags) {
rg.box.seal(body(value))
}
}
return rg
}
@discardableResult
func then<U>(on: Dispatcher = conf.D.map, _ body: @escaping(T) -> Guarantee<U>) -> Guarantee<U> {
#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.dispatch {
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)
}
}
@ -182,48 +156,206 @@ public extension Guarantee {
}
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` -> `Guarantee<U>` => `Guarantee<[U]>`
`Guarantee<[T]>` => `T` -> `[U]` => `Guarantee<[U]>`
firstly {
.value([1,2,3])
}.thenMap {
.value($0 * 2)
}.done {
// $0 => [2,4,6]
}
Guarantee.value([1,2,3])
.flatMapValues { integer in [integer, integer] }
.done {
// $0 => [1,1,2,2,3,3]
}
*/
func thenMap<U>(on: Dispatcher = conf.D.map, _ transform: @escaping(T.Iterator.Element) -> Guarantee<U>) -> Guarantee<[U]> {
return then(on: on) {
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()))
}
#if swift(>=5.1)
// ^^ ambiguous in Swift 5.0, testing again in next version
convenience init(resolver body: (@escaping() -> Void) -> Void) {
self.init(resolver: { seal in
body {
seal(())
}
})
static var value: Guarantee<Void> {
return .value(Void())
}
#endif
}
#endif
public extension DispatchQueue {
/**
Asynchronously executes the provided closure on a dispatch queue, yielding a `Guarantee`.
Asynchronously executes the provided closure on a dispatch queue.
DispatchQueue.global().async(.promise) {
md5(input)
@ -231,45 +363,20 @@ public extension DispatchQueue {
//
}
- _: Must be `.promise` to distinguish from standard `DispatchQueue.async`
- group: A `DispatchGroup`, as for standard `DispatchQueue.async`
- qos: A quality-of-service grade, as for standard `DispatchQueue.async`
- flags: Work item flags, as for standard `DispatchQueue.async`
- body: A closure that yields a value to resolve the guarantee.
- 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? = nil, flags: DispatchWorkItemFlags? = nil, execute body: @escaping () -> T) -> Guarantee<T> {
final func async<T>(_: PMKNamespacer, group: DispatchGroup? = nil, qos: DispatchQoS = .default, flags: DispatchWorkItemFlags = [], execute body: @escaping () -> T) -> Guarantee<T> {
let rg = Guarantee<T>(.pending)
asyncD(group: group, qos: qos, flags: flags) {
async(group: group, qos: qos, flags: flags) {
rg.box.seal(body())
}
return rg
}
}
public extension Dispatcher {
/**
Executes the provided closure on a `Dispatcher`, yielding a `Guarantee`
that represents the value ultimately returned by the closure.
dispatcher.dispatch {
md5(input)
}.done { md5 in
//
}
- Parameter body: The closure that yields the value of the Guarantee.
- Returns: A new `Guarantee` resolved by the result of the provided closure.
*/
func dispatch<T>(_ body: @escaping () -> T) -> Guarantee<T> {
let rg = Guarantee<T>(.pending)
dispatch {
rg.box.seal(body())
}
return rg
}
}
#if os(Linux)
import func CoreFoundation._CFIsMainThread

28
Sources/Info.plist Normal file
View File

@ -0,0 +1,28 @@
<?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>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
<key>CFBundleShortVersionString</key>
<string>$(CURRENT_PROJECT_VERSION)</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>1</string>
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
</array>
</dict>
</plist>

View File

@ -27,29 +27,4 @@ public enum LogEvent {
/// An error which occurred while resolving a promise was swallowed
case cauterized(Error)
/// Odd arguments to DispatchQueue-compatibility layer
case nilDispatchQueueWithFlags
/// DispatchWorkItem flags specified for non-DispatchQueue Dispatcher
case extraneousFlagsSpecified
public func asString() -> String {
var message: String
switch self {
case .waitOnMainThread:
message = " warning: `wait()` called on main thread!"
case .pendingPromiseDeallocated:
message = " warning: pending promise deallocated"
case .pendingGuaranteeDeallocated:
message = " warning: pending guarantee deallocated"
case .cauterized(let error):
message = "cauterized-error: \(error)"
case .nilDispatchQueueWithFlags:
message = " warning: nil DispatchQueue specified, but DispatchWorkItemFlags were also supplied (ignored)"
case .extraneousFlagsSpecified:
message = " warning: DispatchWorkItemFlags flags specified, but default Dispatcher is not a DispatchQueue (ignored)"
}
return "PromiseKit:\(message)"
}
}

View File

@ -0,0 +1,77 @@
#import <Foundation/NSMethodSignature.h>
struct PMKBlockLiteral {
void *isa; // initialized to &_NSConcreteStackBlock or &_NSConcreteGlobalBlock
int flags;
int reserved;
void (*invoke)(void *, ...);
struct block_descriptor {
unsigned long int reserved; // NULL
unsigned long int size; // sizeof(struct Block_literal_1)
// optional helper functions
void (*copy_helper)(void *dst, void *src); // IFF (1<<25)
void (*dispose_helper)(void *src); // IFF (1<<25)
// required ABI.2010.3.16
const char *signature; // IFF (1<<30)
} *descriptor;
// imported variables
};
typedef NS_OPTIONS(NSUInteger, PMKBlockDescriptionFlags) {
PMKBlockDescriptionFlagsHasCopyDispose = (1 << 25),
PMKBlockDescriptionFlagsHasCtor = (1 << 26), // helpers have C++ code
PMKBlockDescriptionFlagsIsGlobal = (1 << 28),
PMKBlockDescriptionFlagsHasStret = (1 << 29), // IFF BLOCK_HAS_SIGNATURE
PMKBlockDescriptionFlagsHasSignature = (1 << 30)
};
// It appears 10.7 doesn't support quotes in method signatures. Remove them
// via @rabovik's method. See https://github.com/OliverLetterer/SLObjectiveCRuntimeAdditions/pull/2
#if defined(__MAC_OS_X_VERSION_MIN_REQUIRED) && __MAC_OS_X_VERSION_MIN_REQUIRED < __MAC_10_8
NS_INLINE static const char * pmk_removeQuotesFromMethodSignature(const char *str){
char *result = malloc(strlen(str) + 1);
BOOL skip = NO;
char *to = result;
char c;
while ((c = *str++)) {
if ('"' == c) {
skip = !skip;
continue;
}
if (skip) continue;
*to++ = c;
}
*to = '\0';
return result;
}
#endif
static NSMethodSignature *NSMethodSignatureForBlock(id block) {
if (!block)
return nil;
struct PMKBlockLiteral *blockRef = (__bridge struct PMKBlockLiteral *)block;
PMKBlockDescriptionFlags flags = (PMKBlockDescriptionFlags)blockRef->flags;
if (flags & PMKBlockDescriptionFlagsHasSignature) {
void *signatureLocation = blockRef->descriptor;
signatureLocation += sizeof(unsigned long int);
signatureLocation += sizeof(unsigned long int);
if (flags & PMKBlockDescriptionFlagsHasCopyDispose) {
signatureLocation += sizeof(void(*)(void *dst, void *src));
signatureLocation += sizeof(void (*)(void *src));
}
const char *signature = (*(const char **)signatureLocation);
#if defined(__MAC_OS_X_VERSION_MIN_REQUIRED) && __MAC_OS_X_VERSION_MIN_REQUIRED < __MAC_10_8
signature = pmk_removeQuotesFromMethodSignature(signature);
NSMethodSignature *nsSignature = [NSMethodSignature signatureWithObjCTypes:signature];
free((void *)signature);
return nsSignature;
#endif
return [NSMethodSignature signatureWithObjCTypes:signature];
}
return 0;
}

View File

@ -0,0 +1,120 @@
#import "NSMethodSignatureForBlock.m"
#import <Foundation/NSDictionary.h>
#import <Foundation/NSException.h>
#import "AnyPromise+Private.h"
#import <Foundation/NSError.h>
#import <dispatch/once.h>
#import <string.h>
#ifndef PMKLog
#define PMKLog NSLog
#endif
@interface PMKArray : NSObject {
@public
id objs[3];
NSUInteger count;
} @end
@implementation PMKArray
- (id)objectAtIndexedSubscript:(NSUInteger)idx {
if (count <= idx) {
// this check is necessary due to lack of checks in `pmk_safely_call_block`
return nil;
}
return objs[idx];
}
@end
id __PMKArrayWithCount(NSUInteger count, ...) {
PMKArray *this = [PMKArray new];
this->count = count;
va_list args;
va_start(args, count);
for (NSUInteger x = 0; x < count; ++x)
this->objs[x] = va_arg(args, id);
va_end(args);
return this;
}
static inline id _PMKCallVariadicBlock(id frock, id result) {
NSCAssert(frock, @"");
NSMethodSignature *sig = NSMethodSignatureForBlock(frock);
const NSUInteger nargs = sig.numberOfArguments;
const char rtype = sig.methodReturnType[0];
#define call_block_with_rtype(type) ({^type{ \
switch (nargs) { \
case 1: \
return ((type(^)(void))frock)(); \
case 2: { \
const id arg = [result class] == [PMKArray class] ? result[0] : result; \
return ((type(^)(id))frock)(arg); \
} \
case 3: { \
type (^block)(id, id) = frock; \
return [result class] == [PMKArray class] \
? block(result[0], result[1]) \
: block(result, nil); \
} \
case 4: { \
type (^block)(id, id, id) = frock; \
return [result class] == [PMKArray class] \
? block(result[0], result[1], result[2]) \
: block(result, nil, nil); \
} \
default: \
@throw [NSException exceptionWithName:NSInvalidArgumentException reason:@"PromiseKit: The provided blocks argument count is unsupported." userInfo:nil]; \
}}();})
switch (rtype) {
case 'v':
call_block_with_rtype(void);
return nil;
case '@':
return call_block_with_rtype(id) ?: nil;
case '*': {
char *str = call_block_with_rtype(char *);
return str ? @(str) : nil;
}
case 'c': return @(call_block_with_rtype(char));
case 'i': return @(call_block_with_rtype(int));
case 's': return @(call_block_with_rtype(short));
case 'l': return @(call_block_with_rtype(long));
case 'q': return @(call_block_with_rtype(long long));
case 'C': return @(call_block_with_rtype(unsigned char));
case 'I': return @(call_block_with_rtype(unsigned int));
case 'S': return @(call_block_with_rtype(unsigned short));
case 'L': return @(call_block_with_rtype(unsigned long));
case 'Q': return @(call_block_with_rtype(unsigned long long));
case 'f': return @(call_block_with_rtype(float));
case 'd': return @(call_block_with_rtype(double));
case 'B': return @(call_block_with_rtype(_Bool));
case '^':
if (strcmp(sig.methodReturnType, "^v") == 0) {
call_block_with_rtype(void);
return nil;
}
// else fall through!
default:
@throw [NSException exceptionWithName:@"PromiseKit" reason:@"PromiseKit: Unsupported method signature." userInfo:nil];
}
}
static id PMKCallVariadicBlock(id frock, id result) {
@try {
return _PMKCallVariadicBlock(frock, result);
} @catch (id 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

@ -6,9 +6,9 @@ import Dispatch
- See: `Thenable`
*/
public final class Promise<T>: Thenable, CatchMixin {
let box: Box<Result<T, Error>>
let box: Box<Result<T>>
fileprivate init(box: SealedBox<Result<T, Error>>) {
fileprivate init(box: SealedBox<Result<T>>) {
self.box = box
}
@ -38,13 +38,13 @@ public final class Promise<T>: Thenable, CatchMixin {
return .value(bar)
}
*/
public class func value(_ value: T) -> Promise<T> {
return Promise(box: SealedBox(value: .success(value)))
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: .failure(error))
box = SealedBox(value: .rejected(error))
}
/// Initialize a new promise bound to the provided `Thenable`.
@ -64,26 +64,13 @@ public final class Promise<T>: Thenable, CatchMixin {
}
}
/// Initialize a new promise that can be resolved with the provided `Resolver`.
public init(cancellable: Cancellable, resolver body: (Resolver<T>) throws -> Void) {
box = EmptyBox()
let resolver = Resolver(box)
self.cancellable = cancellable
self.rejectIfCancelled = resolver.reject
do {
try body(resolver)
} catch {
resolver.reject(error)
}
}
/// - 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))
}
/// - See: `Thenable.pipe`
public func pipe(to: @escaping(Result<T, Error>) -> Void) {
public func pipe(to: @escaping(Result<T>) -> Void) {
switch box.inspect() {
case .pending:
box.inspect {
@ -100,7 +87,7 @@ public final class Promise<T>: Thenable, CatchMixin {
}
/// - See: `Thenable.result`
public var result: Result<T, Error>? {
public var result: Result<T>? {
switch box.inspect() {
case .pending:
return nil
@ -112,14 +99,6 @@ public final class Promise<T>: Thenable, CatchMixin {
init(_: PMKUnambiguousInitializer) {
box = EmptyBox()
}
var cancellable: Cancellable?
var rejectIfCancelled: ((Error) -> Void)?
public func setCancellable(_ cancellable: Cancellable?, reject: ((Error) -> Void)? = nil) {
self.cancellable = cancellable
rejectIfCancelled = reject
}
}
public extension Promise {
@ -130,7 +109,7 @@ public extension Promise {
func wait() throws -> T {
if Thread.isMainThread {
conf.logHandler(.waitOnMainThread)
conf.logHandler(LogEvent.waitOnMainThread)
}
var result = self.result
@ -142,20 +121,33 @@ public extension Promise {
group.wait()
}
return try result!.get()
switch result! {
case .rejected(let error):
throw error
case .fulfilled(let value):
return value
}
}
}
#if swift(>=3.1)
extension Promise where T == Void {
/// Initializes a new promise fulfilled with `Void`
public convenience init() {
self.init(box: SealedBox(value: .success(Void())))
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, yielding a `Promise`.
Asynchronously executes the provided closure on a dispatch queue.
DispatchQueue.global().async(.promise) {
try md5(input)
@ -163,54 +155,24 @@ public extension DispatchQueue {
//
}
- Parameters:
- _: Must be `.promise` to distinguish from standard `DispatchQueue.async`
- group: A `DispatchGroup`, as for standard `DispatchQueue.async`
- qos: A quality-of-service grade, as for standard `DispatchQueue.async`
- flags: Work item flags, as for standard `DispatchQueue.async`
- body: A closure that yields a value to resolve the promise.
- 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? = nil, flags: DispatchWorkItemFlags? = nil, execute body: @escaping () throws -> T) -> Promise<T> {
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)
asyncD(group: group, qos: qos, flags: flags) {
async(group: group, qos: qos, flags: flags) {
do {
promise.box.seal(.success(try body()))
promise.box.seal(.fulfilled(try body()))
} catch {
promise.box.seal(.failure(error))
promise.box.seal(.rejected(error))
}
}
return promise
}
}
public extension Dispatcher {
/**
Executes the provided closure on a `Dispatcher`, yielding a `Promise`
that represents the value ultimately returned by the closure.
dispatcher.dispatch {
try md5(input)
}.done { md5 in
//
}
- Parameter body: A closure that yields a value to resolve the promise.
- Returns: A new `Promise` resolved by the result of the provided closure.
*/
func dispatch<T>(_ body: @escaping () throws -> T) -> Promise<T> {
let promise = Promise<T>(.pending)
dispatch {
do {
promise.box.seal(.success(try body()))
} catch {
promise.box.seal(.failure(error))
}
}
return promise
}
}
/// used by our extensions to provide unambiguous functions with the same name as the original function
public enum PMKNamespacer {

7
Sources/PromiseKit.h Normal file
View File

@ -0,0 +1,7 @@
#import <PromiseKit/fwd.h>
#import <PromiseKit/AnyPromise.h>
#import <Foundation/NSObjCRuntime.h> // `FOUNDATION_EXPORT`
FOUNDATION_EXPORT double PromiseKitVersionNumber;
FOUNDATION_EXPORT const unsigned char PromiseKitVersionString[];

View File

@ -1,8 +1,8 @@
/// An object for resolving promises
public final class Resolver<T> {
let box: Box<Result<T, Error>>
let box: Box<Result<T>>
init(_ box: Box<Result<T, Error>>) {
init(_ box: Box<Result<T>>) {
self.box = box
}
@ -16,16 +16,16 @@ public final class Resolver<T> {
public extension Resolver {
/// Fulfills the promise with the provided value
func fulfill(_ value: T) {
box.seal(.success(value))
box.seal(.fulfilled(value))
}
/// Rejects the promise with the provided error
func reject(_ error: Error) {
box.seal(.failure(error))
box.seal(.rejected(error))
}
/// Resolves the promise with the provided result
func resolve(_ result: Result<T, Error>) {
func resolve(_ result: Result<T>) {
box.seal(result)
}
@ -55,6 +55,7 @@ public extension Resolver {
}
}
#if swift(>=3.1)
extension Resolver where T == Void {
/// Fulfills the promise unless error is non-nil
public func resolve(_ error: Error?) {
@ -64,10 +65,35 @@ extension Resolver where T == Void {
fulfill(())
}
}
#if false
// disabled https://github.com/mxcl/PromiseKit/issues/990
/// Fulfills the promise
/// - Note: underscore is present due to: https://github.com/mxcl/PromiseKit/issues/990
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

@ -6,20 +6,20 @@ public protocol Thenable: class {
associatedtype T
/// `pipe` is immediately executed when this `Thenable` is resolved
func pipe(to: @escaping(Result<T, Error>) -> Void)
func pipe(to: @escaping(Result<T>) -> Void)
/// The resolved result or nil if pending.
var result: Result<T, Error>? { get }
var result: Result<T>? { get }
}
public extension Thenable {
/**
The provided closure executes when this promise resolves.
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 dispatcher that executes the provided closure.
- Parameter body: The closure that executes when this promise fulfills. It must return a promise.
- 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 {
@ -30,35 +30,35 @@ public extension Thenable {
//
}
*/
func then<U: Thenable>(on: Dispatcher = conf.D.map, _ body: @escaping(T) throws -> U) -> Promise<U.T> {
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 .success(let value):
on.dispatch {
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(.failure(error))
rp.box.seal(.rejected(error))
}
}
case .failure(let error):
rp.box.seal(.failure(error))
case .rejected(let error):
rp.box.seal(.rejected(error))
}
}
return rp
}
/**
The provided closure is executed when this promise is resolved.
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 dispatcher that executes the provided closure.
- 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 resolved with the value returned from the provided closure. For example:
- 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)
@ -68,27 +68,51 @@ public extension Thenable {
//
}
*/
func map<U>(on: Dispatcher = conf.D.map, _ transform: @escaping(T) throws -> U) -> Promise<U> {
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 .success(let value):
on.dispatch {
case .fulfilled(let value):
on.async(flags: flags) {
do {
rp.box.seal(.success(try transform(value)))
rp.box.seal(.fulfilled(try transform(value)))
} catch {
rp.box.seal(.failure(error))
rp.box.seal(.rejected(error))
}
}
case .failure(let error):
rp.box.seal(.failure(error))
case .rejected(let error):
rp.box.seal(.rejected(error))
}
}
return rp
}
#if swift(>=4) && !swift(>=5.2)
/**
The provided closure is executed when this promise is resolved.
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.
@ -102,38 +126,70 @@ public extension Thenable {
// either `PMKError.compactMap` or a `JSONError`
}
*/
func compactMap<U>(on: Dispatcher = conf.D.map, _ transform: @escaping(T) throws -> U?) -> Promise<U> {
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 .success(let value):
on.dispatch {
case .fulfilled(let value):
on.async(flags: flags) {
do {
if let rv = try transform(value) {
rp.box.seal(.success(rv))
rp.box.seal(.fulfilled(rv))
} else {
throw PMKError.compactMap(value, U.self)
}
} catch {
rp.box.seal(.failure(error))
rp.box.seal(.rejected(error))
}
}
case .failure(let error):
rp.box.seal(.failure(error))
case .rejected(let error):
rp.box.seal(.rejected(error))
}
}
return rp
}
#if swift(>=4) && !swift(>=5.2)
/**
The provided closure is executed when this promise is resolved.
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 dispatcher that executes the provided closure.
- 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`.
- Returns: A new promise fulfilled as `Void` or rejected if the provided closure throws.
firstly {
URLSession.shared.dataTask(.promise, with: url)
@ -141,35 +197,35 @@ public extension Thenable {
print(response.data)
}
*/
func done(on: Dispatcher = conf.D.return, _ body: @escaping(T) throws -> Void) -> Promise<Void> {
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 .success(let value):
on.dispatch {
case .fulfilled(let value):
on.async(flags: flags) {
do {
try body(value)
rp.box.seal(.success(()))
rp.box.seal(.fulfilled(()))
} catch {
rp.box.seal(.failure(error))
rp.box.seal(.rejected(error))
}
}
case .failure(let error):
rp.box.seal(.failure(error))
case .rejected(let error):
rp.box.seal(.rejected(error))
}
}
return rp
}
/**
The provided closure is executed when this promise is resolved.
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 dispatcher that executes the provided closure.
- 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 that the handler is fed. For example:
- 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)
@ -181,8 +237,8 @@ public extension Thenable {
print(foo, " is Void")
}
*/
func get(on: Dispatcher = conf.D.return, _ body: @escaping (T) throws -> Void) -> Promise<T> {
return map(on: on) {
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
}
@ -193,16 +249,16 @@ public extension Thenable {
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 dispatcher that executes the provided closure.
- 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: Dispatcher = conf.D.map, _ body: @escaping(Result<T, Error>) -> Void) -> Promise<T> {
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.dispatch {
on.async(flags: flags) {
body(result)
seal.resolve(result)
}
@ -224,9 +280,9 @@ public extension Thenable {
switch result {
case .none:
return nil
case .some(.success):
case .some(.fulfilled):
return nil
case .some(.failure(let error)):
case .some(.rejected(let error)):
return error
}
}
@ -266,26 +322,14 @@ public extension Thenable {
switch result {
case .none:
return nil
case .some(.success(let value)):
case .some(.fulfilled(let value)):
return value
case .some(.failure):
case .some(.rejected):
return nil
}
}
}
public extension Thenable {
/**
Converts a Promise or Guarantee into a promise that can be cancelled.
- Parameter thenable: The Thenable (Promise or Guarantee) to be made cancellable.
- Returns: A CancellablePromise that is a cancellable variant of the given Promise or Guarantee.
*/
func cancellize(cancelContext: CancelContext? = nil) -> CancellablePromise<T> {
return CancellablePromise(self, cancelContext: cancelContext)
}
}
public extension Thenable where T: Sequence {
/**
`Promise<[T]>` => `T` -> `U` => `Promise<[U]>`
@ -298,10 +342,25 @@ public extension Thenable where T: Sequence {
// $0 => [2,4,6]
}
*/
func mapValues<U>(on: Dispatcher = conf.D.map, _ transform: @escaping(T.Iterator.Element) throws -> U) -> Promise<[U]> {
return map(on: on) { try $0.map(transform) }
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]>`
@ -313,8 +372,8 @@ public extension Thenable where T: Sequence {
// $0 => [1,1,2,2,3,3]
}
*/
func flatMapValues<U: Sequence>(on: Dispatcher = conf.D.map, _ transform: @escaping(T.Iterator.Element) throws -> U) -> Promise<[U.Iterator.Element]> {
return map(on: on){ (foo: T) in
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) }
}
}
@ -330,13 +389,37 @@ public extension Thenable where T: Sequence {
// $0 => [1,2,3]
}
*/
func compactMapValues<U>(on: Dispatcher = conf.D.map, _ transform: @escaping(T.Iterator.Element) throws -> U?) -> Promise<[U]> {
return map(on: on) { foo -> [U] in
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]>`
@ -348,8 +431,8 @@ public extension Thenable where T: Sequence {
// $0 => [2,4,6]
}
*/
func thenMap<U: Thenable>(on: Dispatcher = conf.D.map, _ transform: @escaping(T.Iterator.Element) throws -> U) -> Promise<[U.T]> {
return then(on: on) {
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))
}
}
@ -365,8 +448,8 @@ public extension Thenable where T: Sequence {
// $0 => [1,1,2,2,3,3]
}
*/
func thenFlatMap<U: Thenable>(on: Dispatcher = conf.D.map, _ transform: @escaping(T.Iterator.Element) throws -> U) -> Promise<[U.T.Iterator.Element]> where U.T: Sequence {
return then(on: on) {
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 }
@ -374,7 +457,7 @@ public extension Thenable where T: Sequence {
}
/**
`Promise<[T]>` => `T` -> Bool => `Promise<[U]>`
`Promise<[T]>` => `T` -> Bool => `Promise<[T]>`
firstly {
.value([1,2,3])
@ -384,11 +467,28 @@ public extension Thenable where T: Sequence {
// $0 => [2,3]
}
*/
func filterValues(on: Dispatcher = conf.D.map, _ isIncluded: @escaping (T.Iterator.Element) -> Bool) -> Promise<[T.Iterator.Element]> {
return map(on: on) {
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 {
@ -403,8 +503,8 @@ public extension Thenable where T: Collection {
}
}
func firstValue(on: Dispatcher = conf.D.map, where test: @escaping (T.Iterator.Element) -> Bool) -> Promise<T.Iterator.Element> {
return map(on: on) {
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
}
@ -427,7 +527,7 @@ public extension Thenable where T: Collection {
public extension Thenable where T: Sequence, T.Iterator.Element: Comparable {
/// - Returns: a promise fulfilled with the sorted values of this `Sequence`.
func sortedValues(on: Dispatcher = conf.D.map) -> Promise<[T.Iterator.Element]> {
return map(on: on){ $0.sorted() }
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,73 +0,0 @@
import Dispatch
public extension _PMKCatchWrappers {
/**
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 flags: `DispatchWorkItemFlags` to be applied when dispatching.
- Parameter policy: The default policy does not execute your handler for cancellation errors.
- Parameter body: The handler to execute if this promise is rejected.
- Returns: A promise finalizer.
- SeeAlso: [Cancellation](http://https://github.com/mxcl/PromiseKit/blob/master/Documents/CommonPatterns.md#cancellation/docs/)
*/
@discardableResult
func `catch`(on: DispatchQueue? = .pmkDefault, flags: DispatchWorkItemFlags? = nil, policy: CatchPolicy = conf.catchPolicy, _ body: @escaping(Error) -> Void) -> Finalizer {
let dispatcher = selectDispatcher(given: on, configured: conf.D.return, flags: flags)
return `catch`(on: dispatcher, policy: policy, body)
}
/**
The provided closure executes when this promise rejects with the specific error passed in. A final `catch` is still required at the end of the chain.
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 only: The specific error to be caught and handled (e.g., `PMKError.emptySequence`).
- Parameter on: The queue to which the provided closure dispatches.
- Parameter flags: `DispatchWorkItemFlags` to be applied when dispatching.
- Parameter body: The handler to execute if this promise is rejected with the provided error.
- Note: Since this method handles only specific errors, supplying a `CatchPolicy` is unsupported.
- SeeAlso: [Cancellation](http://promisekit.org/docs/)
*/
func `catch`<E: Swift.Error>(only: E, on: DispatchQueue? = .pmkDefault, flags: DispatchWorkItemFlags? = nil, _ body: @escaping(E) -> Void)
-> CascadingFinalizer where E: Equatable
{
let dispatcher = selectDispatcher(given: on, configured: conf.D.return, flags: flags)
return `catch`(only: only, on: dispatcher, body)
}
/**
The provided closure executes when this promise rejects with an error of the type passed in. A final `catch` is still required at the end of the chain.
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 only: The error type to be caught and handled (e.g., `PMKError`).
- Parameter on: The queue to which the provided closure dispatches.
- Parameter flags: `DispatchWorkItemFlags` to be applied when dispatching.
- Parameter policy: A `CatchPolicy` that further constrains the errors this handler will see. E.g., if
you are receiving `PMKError` errors, do you want to see even those that result from cancellation?
- Parameter body: The handler to execute if this promise is rejected with the provided error type.
- SeeAlso: [Cancellation](http://promisekit.org/docs/)
*/
func `catch`<E: Swift.Error>(only: E.Type, on: DispatchQueue? = .pmkDefault, flags: DispatchWorkItemFlags? = nil,
policy: CatchPolicy = conf.catchPolicy, _ body: @escaping(E) -> Void) -> CascadingFinalizer
{
let dispatcher = selectDispatcher(given: on, configured: conf.D.return, flags: flags)
return `catch`(only: only, on: dispatcher, policy: policy, body)
}
}

View File

@ -1,51 +0,0 @@
import Dispatch
public extension _PMKSharedWrappers {
/**
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 flags: `DispatchWorkItemFlags` to be applied when dispatching.
- Parameter body: The closure that executes when this promise resolves.
- Returns: A new promise, resolved with this promises resolution.
*/
func ensure(on: DispatchQueue? = .pmkDefault, flags: DispatchWorkItemFlags? = nil, _ body: @escaping () -> Void) -> BaseOfT {
let dispatcher = selectDispatcher(given: on, configured: conf.D.return, flags: flags)
return ensure(on: dispatcher, body)
}
/**
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()
}.catch {
//
}
- Parameter on: The queue to which the provided closure dispatches.
- Parameter flags: `DispatchWorkItemFlags` to be applied when dispatching.
- Parameter body: The closure that executes when this promise resolves.
- Returns: A new promise, resolved with this promises resolution.
*/
func ensureThen(on: DispatchQueue? = .pmkDefault, flags: DispatchWorkItemFlags? = nil, _ body: @escaping () -> VoidReturn) -> BaseOfT {
let dispatcher = selectDispatcher(given: on, configured: conf.D.return, flags: flags)
return ensureThen(on: dispatcher, body)
}
}

View File

@ -1,11 +0,0 @@
import Dispatch
public extension _PMKFinallyWrappers {
/// `finally` is the same as `ensure`, but it is not chainable
@discardableResult
func finally(on: DispatchQueue? = .pmkDefault, flags: DispatchWorkItemFlags? = nil, _ body: @escaping () -> Void) -> FinallyReturn {
let dispatcher = selectDispatcher(given: on, configured: conf.D.return, flags: flags)
return finally(on: dispatcher, body)
}
}

View File

@ -1,39 +0,0 @@
// Since Guarantees have no error path, closures in the API are nonthrowing, which
// makes them different from the shared Promise/CancellablePromise API.
import Dispatch
public extension Guarantee {
@discardableResult
func then<U>(on: DispatchQueue? = .pmkDefault, flags: DispatchWorkItemFlags? = nil, _ body: @escaping(T) -> Guarantee<U>) -> Guarantee<U> {
let dispatcher = selectDispatcher(given: on, configured: conf.D.map, flags: flags)
return then(on: dispatcher, body)
}
func map<U>(on: DispatchQueue? = .pmkDefault, flags: DispatchWorkItemFlags? = nil, _ body: @escaping(T) -> U) -> Guarantee<U> {
let dispatcher = selectDispatcher(given: on, configured: conf.D.map, flags: flags)
return map(on: dispatcher, body)
}
@discardableResult
func done(on: DispatchQueue? = .pmkDefault, flags: DispatchWorkItemFlags? = nil, _ body: @escaping(T) -> Void) -> Guarantee<Void> {
let dispatcher = selectDispatcher(given: on, configured: conf.D.return, flags: flags)
return done(on: dispatcher, body)
}
func get(on: DispatchQueue? = .pmkDefault, flags: DispatchWorkItemFlags? = nil, _ body: @escaping (T) -> Void) -> Guarantee<T> {
let dispatcher = selectDispatcher(given: on, configured: conf.D.return, flags: flags)
return get(on: dispatcher, body)
}
}
public extension Guarantee where T: Sequence {
func thenMap<U>(on: DispatchQueue? = .pmkDefault, flags: DispatchWorkItemFlags? = nil, _ transform: @escaping(T.Iterator.Element) -> Guarantee<U>) -> Guarantee<[U]> {
let dispatcher = selectDispatcher(given: on, configured: conf.D.map, flags: flags)
return thenMap(on: dispatcher, transform)
}
}

View File

@ -1,275 +0,0 @@
import Dispatch
public extension _PMKSharedWrappers {
/**
The provided closure executes when this promise rejects.
Unlike `catch`, `recover` continues the chain. It can return a replacement promise or rethrow.
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 flags: `DispatchWorkItemFlags` to be applied when dispatching.
- Parameter policy: The default policy does not execute your handler for cancellation errors.
- Parameter body: The handler to execute if this promise is rejected.
- SeeAlso: [Cancellation](https://github.com/mxcl/PromiseKit/blob/master/Documents/CommonPatterns.md#cancellation)
*/
func recover<U: Thenable>(on: DispatchQueue? = .pmkDefault, flags: DispatchWorkItemFlags? = nil, policy: CatchPolicy = conf.catchPolicy,
_ body: @escaping(Error) throws -> U) -> BaseOfT where U.T == T
{
let dispatcher = selectDispatcher(given: on, configured: conf.D.map, flags: flags)
return recover(on: dispatcher, policy: policy, body)
}
/**
The provided closure executes when this promise rejects with the specific error passed in.
Unlike `catch`, `recover` continues the chain. It can return a replacement promise or rethrow.
Use `recover` in circumstances where recovering the chain from certain errors is a possibility. For example:
firstly {
CLLocationManager.requestLocation()
}.recover(CLError.unknownLocation) {
return .value(CLLocation.chicago)
}
- Parameter only: The specific error to be recovered (e.g., `PMKError.emptySequence`)
- Parameter on: The queue to which the provided closure dispatches.
- Parameter flags: `DispatchWorkItemFlags` to be applied when dispatching.
- Parameter body: The handler to execute if this promise is rejected with the provided error.
- Note: Since this method recovers only specific errors, supplying a `CatchPolicy` is unsupported.
- SeeAlso: [Cancellation](http://promisekit.org/docs/)
*/
func recover<U: Thenable, E: Swift.Error>(only: E, on: DispatchQueue? = .pmkDefault, flags: DispatchWorkItemFlags? = nil,
_ body: @escaping(E) throws -> U) -> BaseOfT where U.T == T, E: Equatable
{
let dispatcher = selectDispatcher(given: on, configured: conf.D.map, flags: flags)
return recover(only: only, on: dispatcher, body)
}
/**
The provided closure executes when this promise rejects with an error of the type passed in.
Unlike `catch`, `recover` continues the chain. It can return a replacement promise or rethrow.
Use `recover` in circumstances where recovering the chain from certain errors is a possibility. For example:
firstly {
API.fetchData()
}.recover(FetchError.self) { error in
guard case .missingImage(let partialData) = error else { throw error }
//
return .value(dataWithDefaultImage)
}
- Parameter only: The error type to be recovered (e.g., `PMKError`).
- Parameter on: The queue to which the provided closure dispatches.
- Parameter flags: `DispatchWorkItemFlags` to be applied when dispatching.
- Parameter policy: The default policy does not execute your handler for cancellation errors.
- Parameter body: The handler to execute if this promise is rejected with the provided error type.
- SeeAlso: [Cancellation](http://promisekit.org/docs/)
*/
func recover<U: Thenable, E: Swift.Error>(only: E.Type, on: DispatchQueue? = .pmkDefault, flags: DispatchWorkItemFlags? = nil,
policy: CatchPolicy = conf.catchPolicy, _ body: @escaping(E) throws -> U) -> BaseOfT where U.T == T
{
let dispatcher = selectDispatcher(given: on, configured: conf.D.map, flags: flags)
return recover(only: only, on: dispatcher, policy: policy, body)
}
}
public extension _PMKSharedVoidWrappers {
/**
The provided closure executes when this promise rejects.
This variant of `recover` ensures that no error is thrown from the handler
and allows you to specify a catch policy.
- Parameter on: The queue to which the provided closure dispatches.
- Parameter flags: `DispatchWorkItemFlags` to be applied when dispatching.
- Parameter policy: The default policy does not execute your handler for cancellation errors.
- Parameter body: The handler to execute if this promise is rejected.
- SeeAlso: [Cancellation](https://github.com/mxcl/PromiseKit/blob/master/Documents/CommonPatterns.md#cancellation)
*/
func recover(on: DispatchQueue? = .pmkDefault, flags: DispatchWorkItemFlags? = nil, policy: CatchPolicy = conf.catchPolicy,
_ body: @escaping(Error) throws -> Void) -> BaseOfT
{
let dispatcher = selectDispatcher(given: on, configured: conf.D.map, flags: flags)
return recover(on: dispatcher, policy: policy, body)
}
/**
The provided closure executes when this promise rejects with the specific error passed in.
Unlike `catch`, `recover` continues the chain. It can return a replacement promise or rethrow.
Use `recover` in circumstances where recovering the chain from certain errors is a possibility. For example:
- Parameter only: The specific error to be recovered (e.g., `PMKError.emptySequence`)
- Parameter on: The queue to which the provided closure dispatches.
- Parameter flags: `DispatchWorkItemFlags` to be applied when dispatching.
- Parameter body: The handler to execute if this promise is rejected with the provided error.
- Note: Since this method recovers only specific errors, supplying a `CatchPolicy` is unsupported.
- SeeAlso: [Cancellation](http://promisekit.org/docs/)
*/
func recover<E: Swift.Error>(only: E, on: DispatchQueue? = .pmkDefault, flags: DispatchWorkItemFlags? = nil,
_ body: @escaping(E) throws -> Void) -> BaseOfT where E: Equatable
{
let dispatcher = selectDispatcher(given: on, configured: conf.D.map, flags: flags)
return recover(only: only, on: dispatcher, body)
}
/**
The provided closure executes when this promise rejects with an error of the type passed in.
Unlike `catch`, `recover` continues the chain. It can return a replacement promise or rethrow.
Use `recover` in circumstances where recovering the chain from certain errors is a possibility.
- Parameter only: The error type to be recovered (e.g., `PMKError`).
- Parameter on: The queue to which the provided closure dispatches.
- Parameter flags: `DispatchWorkItemFlags` to be applied when dispatching.
- Parameter policy: The default policy does not execute your handler for cancellation errors.
- Parameter body: The handler to execute if this promise is rejected with the provided error type.
- SeeAlso: [Cancellation](http://promisekit.org/docs/)
*/
func recover<E: Swift.Error>(only: E.Type, on: DispatchQueue? = .pmkDefault, flags: DispatchWorkItemFlags? = nil,
policy: CatchPolicy = conf.catchPolicy, _ body: @escaping(E) throws -> Void) -> BaseOfT
{
let dispatcher = selectDispatcher(given: on, configured: conf.D.map, flags: flags)
return recover(only: only, on: dispatcher, policy: policy, body)
}
}
public extension CatchMixin {
/**
The provided closure executes when this promise rejects.
This variant of `recover` requires the handler to return a Guarantee; your closure cannot `throw`.
It is logically impossible for this variant to accept a `catchPolicy`. All errors will be presented
to your closure for processing.
- Parameter on: The queue to which the provided closure dispatches.
- Parameter flags: `DispatchWorkItemFlags` to be applied when dispatching.
- Parameter body: The handler to execute if this promise is rejected.
- SeeAlso: [Cancellation](https://github.com/mxcl/PromiseKit/blob/master/Documents/CommonPatterns.md#cancellation)
*/
@discardableResult
func recover(on: DispatchQueue? = .pmkDefault, flags: DispatchWorkItemFlags? = nil, _ body: @escaping(Error) -> Guarantee<T>) -> Guarantee<T> {
let dispatcher = selectDispatcher(given: on, configured: conf.D.map, flags: flags)
return recover(on: dispatcher, body)
}
}
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 error types,
including cancellation.
- Parameter on: The queue to which the provided closure dispatches.
- Parameter flags: `DispatchWorkItemFlags` to be applied when dispatching.
- Parameter body: The handler to execute if this promise is rejected.
- SeeAlso: [Cancellation](https://github.com/mxcl/PromiseKit/blob/master/Documents/CommonPatterns.md#cancellation)
*/
@discardableResult
func recover(on: DispatchQueue? = .pmkDefault, flags: DispatchWorkItemFlags? = nil, _ body: @escaping(Error) -> Void) -> Guarantee<Void> {
let dispatcher = selectDispatcher(given: on, configured: conf.D.map, flags: flags)
return recover(on: dispatcher, body)
}
}
public extension CancellableCatchMixin {
/**
The provided closure executes when this cancellable promise rejects.
Unlike `catch`, `recover` continues the chain. It can return a replacement promise or rethrow.
Use `recover` in circumstances where recovering the chain from certain errors is a possibility. For example:
let context = firstly {
CLLocationManager.requestLocation()
}.recover { error in
guard error == CLError.unknownLocation else { throw error }
return .value(CLLocation.chicago)
}.cancelContext
//
context.cancel()
- Parameter on: The queue to which the provided closure dispatches.
- Parameter flags: `DispatchWorkItemFlags` to be applied when dispatching.
- Parameter policy: The default policy does not execute your handler for cancellation errors.
- 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<V: CancellableThenable>(on: DispatchQueue? = .pmkDefault, flags: DispatchWorkItemFlags? = nil,
policy: CatchPolicy = conf.catchPolicy, _ body: @escaping(Error) throws -> V) -> CancellablePromise<C.T> where V.U.T == C.T
{
let dispatcher = selectDispatcher(given: on, configured: conf.D.map, flags: flags)
return recover(on: dispatcher, policy: policy, body)
}
/**
The provided closure executes when this cancellable promise rejects with the specific error passed in.
Unlike `catch`, `recover` continues the chain. It can return a replacement promise or rethrow.
Use `recover` in circumstances where recovering the chain from certain errors is a possibility. For example:
firstly {
CLLocationManager.requestLocation()
}.recover(CLError.unknownLocation) {
return .value(CLLocation.chicago)
}
- Parameter only: The specific error to be recovered (e.g., `PMKError.emptySequence`)
- Parameter on: The queue to which the provided closure dispatches.
- Parameter flags: `DispatchWorkItemFlags` to be applied when dispatching.
- Parameter body: The handler to execute if this promise is rejected with the provided error.
- Note: Since this method recovers only specific errors, supplying a `CatchPolicy` is unsupported.
- SeeAlso: [Cancellation](https://github.com/mxcl/PromiseKit/blob/master/Documentation/CommonPatterns.md#cancellation)
*/
func recover<V: CancellableThenable, E: Swift.Error>(only: E, on: DispatchQueue? = .pmkDefault, flags: DispatchWorkItemFlags? = nil,
_ body: @escaping(E) throws -> V) -> CancellablePromise<C.T> where V.U.T == C.T, E: Equatable
{
let dispatcher = selectDispatcher(given: on, configured: conf.D.map, flags: flags)
return recover(only: only, on: dispatcher, body)
}
/**
The provided closure executes when this cancellable promise rejects with an error of the type passed in.
Unlike `catch`, `recover` continues the chain. It can return a replacement promise or rethrow.
Use `recover` in circumstances where recovering the chain from certain errors is a possibility. For example:
firstly {
API.fetchData()
}.recover(FetchError.self) { error in
guard case .missingImage(let partialData) = error else { throw error }
//
return .value(dataWithDefaultImage)
}
- Parameter only: The error type to be recovered.
- Parameter on: The queue to which the provided closure dispatches.
- Parameter flags: `DispatchWorkItemFlags` to be applied when dispatching.
- Parameter policy: The default policy does not execute your handler for cancellation errors.
- Parameter body: The handler to execute if this promise is rejected with the provided error type.
- SeeAlso: [Cancellation](https://github.com/mxcl/PromiseKit/blob/master/Documentation/CommonPatterns.md#cancellation)
*/
func recover<V: CancellableThenable, E: Swift.Error>(only: E.Type, on: DispatchQueue? = .pmkDefault, flags: DispatchWorkItemFlags? = nil,
policy: CatchPolicy = conf.catchPolicy, _ body: @escaping(E) throws -> V) -> CancellablePromise<C.T> where V.U.T == C.T
{
let dispatcher = selectDispatcher(given: on, configured: conf.D.map, flags: flags)
return recover(only: only, on: dispatcher, policy: policy, body)
}
}

View File

@ -1,261 +0,0 @@
import Dispatch
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? = .pmkDefault, flags: DispatchWorkItemFlags? = nil, _ transform: @escaping(T.Iterator.Element) throws -> U) -> Promise<[U]> {
let dispatcher = selectDispatcher(given: on, configured: conf.D.map, flags: flags)
return mapValues(on: dispatcher, transform)
}
/**
`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? = .pmkDefault, flags: DispatchWorkItemFlags? = nil, _ transform: @escaping(T.Iterator.Element) throws -> U) -> Promise<[U.Iterator.Element]> {
let dispatcher = selectDispatcher(given: on, configured: conf.D.map, flags: flags)
return flatMapValues(on: dispatcher, transform)
}
/**
`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? = .pmkDefault, flags: DispatchWorkItemFlags? = nil, _ transform: @escaping(T.Iterator.Element) throws -> U?) -> Promise<[U]> {
let dispatcher = selectDispatcher(given: on, configured: conf.D.map, flags: flags)
return compactMapValues(on: dispatcher, transform)
}
/**
`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? = .pmkDefault, flags: DispatchWorkItemFlags? = nil, _ transform: @escaping(T.Iterator.Element) throws -> U) -> Promise<[U.T]> {
let dispatcher = selectDispatcher(given: on, configured: conf.D.map, flags: flags)
return thenMap(on: dispatcher, 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? = .pmkDefault, flags: DispatchWorkItemFlags? = nil, _ transform: @escaping(T.Iterator.Element) throws -> U) -> Promise<[U.T.Iterator.Element]> where U.T: Sequence {
let dispatcher = selectDispatcher(given: on, configured: conf.D.map, flags: flags)
return thenFlatMap(on: dispatcher, transform)
}
/**
`Promise<[T]>` => `T` -> Bool => `Promise<[U]>`
firstly {
.value([1,2,3])
}.filterValues {
$0 > 1
}.done {
// $0 => [2,3]
}
*/
func filterValues(on: DispatchQueue? = .pmkDefault, flags: DispatchWorkItemFlags? = nil, _ isIncluded: @escaping (T.Iterator.Element) -> Bool) -> Promise<[T.Iterator.Element]> {
let dispatcher = selectDispatcher(given: on, configured: conf.D.map, flags: flags)
return filterValues(on: dispatcher, isIncluded)
}
}
public extension Thenable where T: Collection {
func firstValue(on: DispatchQueue? = .pmkDefault, flags: DispatchWorkItemFlags? = nil, where test: @escaping (T.Iterator.Element) -> Bool) -> Promise<T.Iterator.Element> {
let dispatcher = selectDispatcher(given: on, configured: conf.D.map, flags: flags)
return firstValue(on: dispatcher, where: test)
}
}
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? = .pmkDefault, flags: DispatchWorkItemFlags? = nil) -> Promise<[T.Iterator.Element]> {
let dispatcher = selectDispatcher(given: on, configured: conf.D.map, flags: flags)
return sortedValues(on: dispatcher)
}
}
public extension CancellableThenable where U.T: Sequence {
/**
`CancellablePromise<[U.T]>` => `U.T` -> `V` => `CancellablePromise<[V]>`
firstly {
cancellize(Promise.value([1,2,3]))
}.mapValues { integer in
integer * 2
}.done {
// $0 => [2,4,6]
}
*/
func mapValues<V>(on: DispatchQueue? = .pmkDefault, flags: DispatchWorkItemFlags? = nil, _ transform: @escaping(U.T.Iterator.Element) throws -> V) -> CancellablePromise<[V]> {
let dispatcher = selectDispatcher(given: on, configured: conf.D.map, flags: flags)
return mapValues(on: dispatcher, transform)
}
/**
`CancellablePromise<[U.T]>` => `U.T` -> `[V]` => `CancellablePromise<[V]>`
firstly {
cancellize(Promise.value([1,2,3]))
}.flatMapValues { integer in
[integer, integer]
}.done {
// $0 => [1,1,2,2,3,3]
}
*/
func flatMapValues<V: Sequence>(on: DispatchQueue? = .pmkDefault, flags: DispatchWorkItemFlags? = nil, _ transform: @escaping(U.T.Iterator.Element) throws -> V) -> CancellablePromise<[V.Iterator.Element]> {
let dispatcher = selectDispatcher(given: on, configured: conf.D.map, flags: flags)
return flatMapValues(on: dispatcher, transform)
}
/**
`CancellablePromise<[U.T]>` => `U.T` -> `V?` => `CancellablePromise<[V]>`
firstly {
cancellize(Promise.value(["1","2","a","3"]))
}.compactMapValues {
Int($0)
}.done {
// $0 => [1,2,3]
}
*/
func compactMapValues<V>(on: DispatchQueue? = .pmkDefault, flags: DispatchWorkItemFlags? = nil, _ transform: @escaping(U.T.Iterator.Element) throws -> V?) -> CancellablePromise<[V]> {
let dispatcher = selectDispatcher(given: on, configured: conf.D.map, flags: flags)
return compactMapValues(on: dispatcher, transform)
}
/**
`CancellablePromise<[U.T]>` => `U.T` -> `CancellablePromise<V>` => `CancellablePromise<[V]>`
firstly {
cancellize(Promise.value([1,2,3]))
}.thenMap { integer in
cancellize(Promise.value(integer * 2))
}.done {
// $0 => [2,4,6]
}
*/
func thenMap<V: CancellableThenable>(on: DispatchQueue? = .pmkDefault, flags: DispatchWorkItemFlags? = nil, _ transform: @escaping(U.T.Iterator.Element) throws -> V) -> CancellablePromise<[V.U.T]> {
let dispatcher = selectDispatcher(given: on, configured: conf.D.map, flags: flags)
return thenMap(on: dispatcher, transform)
}
/**
`CancellablePromise<[U.T]>` => `U.T` -> `Promise<V>` => `CancellablePromise<[V]>`
firstly {
Promise.value([1,2,3])
}.cancellize().thenMap { integer in
.value(integer * 2)
}.done {
// $0 => [2,4,6]
}
*/
func thenMap<V: Thenable>(on: DispatchQueue? = .pmkDefault, flags: DispatchWorkItemFlags? = nil, _ transform: @escaping(U.T.Iterator.Element) throws -> V) -> CancellablePromise<[V.T]> {
let dispatcher = selectDispatcher(given: on, configured: conf.D.map, flags: flags)
return thenMap(on: dispatcher, transform)
}
/**
`CancellablePromise<[T]>` => `T` -> `CancellablePromise<[U]>` => `CancellablePromise<[U]>`
firstly {
cancellize(Promise.value([1,2,3]))
}.thenFlatMap { integer in
cancellize(Promise.value([integer, integer]))
}.done {
// $0 => [1,1,2,2,3,3]
}
*/
func thenFlatMap<V: CancellableThenable>(on: DispatchQueue? = .pmkDefault, flags: DispatchWorkItemFlags? = nil, _ transform: @escaping(U.T.Iterator.Element) throws -> V) -> CancellablePromise<[V.U.T.Iterator.Element]> where V.U.T: Sequence {
let dispatcher = selectDispatcher(given: on, configured: conf.D.map, flags: flags)
return thenFlatMap(on: dispatcher, transform)
}
/**
`CancellablePromise<[T]>` => `T` -> `Promise<[U]>` => `CancellablePromise<[U]>`
firstly {
Promise.value([1,2,3])
}.cancellize().thenFlatMap { integer in
.value([integer, integer])
}.done {
// $0 => [1,1,2,2,3,3]
}
*/
func thenFlatMap<V: Thenable>(on: DispatchQueue? = .pmkDefault, flags: DispatchWorkItemFlags? = nil, _ transform: @escaping(U.T.Iterator.Element) throws -> V) -> CancellablePromise<[V.T.Iterator.Element]> where V.T: Sequence {
let dispatcher = selectDispatcher(given: on, configured: conf.D.map, flags: flags)
return thenFlatMap(on: dispatcher, transform)
}
/**
`CancellablePromise<[T]>` => `T` -> Bool => `CancellablePromise<[U]>`
firstly {
cancellize(Promise.value([1,2,3]))
}.filterValues {
$0 > 1
}.done {
// $0 => [2,3]
}
*/
func filterValues(on: DispatchQueue? = .pmkDefault, flags: DispatchWorkItemFlags? = nil, _ isIncluded: @escaping (U.T.Iterator.Element) -> Bool) -> CancellablePromise<[U.T.Iterator.Element]> {
let dispatcher = selectDispatcher(given: on, configured: conf.D.map, flags: flags)
return filterValues(on: dispatcher, isIncluded)
}
}
public extension CancellableThenable where U.T: Collection {
func firstValue(on: DispatchQueue? = .pmkDefault, flags: DispatchWorkItemFlags? = nil, where test: @escaping (U.T.Iterator.Element) -> Bool) -> CancellablePromise<U.T.Iterator.Element> {
let dispatcher = selectDispatcher(given: on, configured: conf.D.map, flags: flags)
return firstValue(on: dispatcher, where: test)
}
}
public extension CancellableThenable where U.T: Sequence, U.T.Iterator.Element: Comparable {
/// - Returns: a cancellable promise fulfilled with the sorted values of this `Sequence`.
func sortedValues(on: DispatchQueue? = .pmkDefault, flags: DispatchWorkItemFlags? = nil) -> CancellablePromise<[U.T.Iterator.Element]> {
let dispatcher = selectDispatcher(given: on, configured: conf.D.map, flags: flags)
return sortedValues(on: dispatcher)
}
}

View File

@ -1,240 +0,0 @@
import Dispatch
public extension _PMKSharedWrappers {
/**
The provided closure is executed when this promise is resolved.
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.
firstly {
URLSession.shared.dataTask(.promise, with: url)
}.done { response in
print(response.data)
}
- Parameter on: The queue to which the provided closure dispatches.
- Parameter flags: `DispatchWorkItemFlags` to be applied when dispatching.
- Parameter body: The closure that is executed when this Promise is fulfilled.
- Returns: A new promise fulfilled as `Void`.
*/
func done(on: DispatchQueue? = .pmkDefault, flags: DispatchWorkItemFlags? = nil, _ body: @escaping(T) throws -> Void) -> BaseOfVoid {
let dispatcher = selectDispatcher(given: on, configured: conf.D.return, flags: flags)
return done(on: dispatcher, body)
}
/**
The provided closure is executed when this promise is resolved.
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.
firstly {
.value(1)
}.get { foo in
print(foo, " is 1")
}.done { foo in
print(foo, " is 1")
}.done { foo in
print(foo, " is Void")
}
- Parameter on: The queue to which the provided closure dispatches.
- Parameter flags: `DispatchWorkItemFlags` to be applied when dispatching.
- Parameter body: The closure that is executed when this Promise is fulfilled.
- Returns: A new promise that is resolved with the value that the handler is fed.
*/
func get(on: DispatchQueue? = .pmkDefault, flags: DispatchWorkItemFlags? = nil, _ body: @escaping (T) throws -> Void) -> BaseOfT {
let dispatcher = selectDispatcher(given: on, configured: conf.D.return, flags: flags)
return get(on: dispatcher, body)
}
/**
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.
promise.tap{ print($0) }.then{ /**/ }
- Parameter on: The queue to which the provided closure dispatches.
- Parameter flags: `DispatchWorkItemFlags` to be applied when dispatching.
- 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.
*/
func tap(on: DispatchQueue? = .pmkDefault, flags: DispatchWorkItemFlags? = nil, _ body: @escaping(Result<T, Error>) -> Void) -> BaseOfT {
let dispatcher = selectDispatcher(given: on, configured: conf.D.map, flags: flags)
return tap(on: dispatcher, body)
}
}
public extension Thenable {
/**
The provided closure executes when this promise resolves.
This allows chaining promises. The promise returned by the provided closure is resolved before the promise returned by this closure resolves.
firstly {
URLSession.shared.dataTask(.promise, with: url1)
}.then { response in
transform(data: response.data)
}.done { transformation in
//
}
- Parameter on: The queue to which the provided closure dispatches.
- Parameter flags: `DispatchWorkItemFlags` to be applied when dispatching.
- Parameter body: The closure that executes when this promise fulfills. It must return a promise.
- Returns: A new promise that resolves when the promise returned from the provided closure resolves.
*/
func then<U: Thenable>(on: DispatchQueue? = .pmkDefault, flags: DispatchWorkItemFlags? = nil, _ body: @escaping(T) throws -> U) -> Promise<U.T> {
let dispatcher = selectDispatcher(given: on, configured: conf.D.map, flags: flags)
return then(on: dispatcher, body)
}
/**
The provided closure is executed when this promise is resolved.
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 flags: `DispatchWorkItemFlags` to be applied when dispatching.
- Parameter transform: The closure that is executed when this Promise is fulfilled. It must return a non-promise.
- Returns: A new promise that is resolved with the value returned from the provided closure. For example:
firstly {
URLSession.shared.dataTask(.promise, with: url1)
}.map { response in
response.data.length
}.done { length in
//
}
*/
func map<U>(on: DispatchQueue? = .pmkDefault, flags: DispatchWorkItemFlags? = nil, _ transform: @escaping(T) throws -> U) -> Promise<U> {
let dispatcher = selectDispatcher(given: on, configured: conf.D.map, flags: flags)
return map(on: dispatcher, transform)
}
/**
The provided closure is executed when this promise is resolved.
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? = .pmkDefault, flags: DispatchWorkItemFlags? = nil, _ transform: @escaping(T) throws -> U?) -> Promise<U> {
let dispatcher = selectDispatcher(given: on, configured: conf.D.map, flags: flags)
return compactMap(on: dispatcher, transform)
}
}
public extension CancellableThenable {
/**
The provided closure executes when this cancellable promise resolves.
This allows chaining promises. The cancellable promise returned by the provided closure is resolved before the cancellable promise returned by this closure resolves.
let context = firstly {
URLSession.shared.dataTask(.promise, with: url1)
}.cancellize().then { response in
transform(data: response.data) // returns a CancellablePromise
}.done { transformation in
//
}.cancelContext
//
context.cancel()
- Parameter on: The queue to which the provided closure dispatches.
- Parameter flags: `DispatchWorkItemFlags` to be applied when dispatching.
- Parameter body: The closure that executes when this cancellable promise fulfills. It must return a cancellable promise.
- Returns: A new cancellable promise that resolves when the promise returned from the provided closure resolves.
*/
func then<V: CancellableThenable>(on: DispatchQueue? = .pmkDefault, flags: DispatchWorkItemFlags? = nil, _ body: @escaping (U.T) throws -> V) -> CancellablePromise<V.U.T> {
let dispatcher = selectDispatcher(given: on, configured: conf.D.map, flags: flags)
return then(on: dispatcher, body)
}
/**
The provided closure executes when this cancellable promise resolves.
This allows chaining promises. The promise returned by the provided closure is resolved before the cancellable promise returned by this closure resolves.
let context = firstly {
URLSession.shared.dataTask(.promise, with: url1)
}.cancellize().then { response in
transform(data: response.data) // returns a Promise
}.done { transformation in
//
}.cancelContext
//
context.cancel()
- Parameter on: The dispatcher that executes the provided closure.
- Parameter flags: `DispatchWorkItemFlags` to be applied when dispatching.
- Parameter body: The closure that executes when this cancellable promise fulfills. It must return a promise (not a cancellable promise).
- Returns: A new cancellable promise that resolves when the promise returned from the provided closure resolves.
*/
func then<V: Thenable>(on: DispatchQueue? = .pmkDefault, flags: DispatchWorkItemFlags? = nil, _ body: @escaping (U.T) throws -> V) -> CancellablePromise<V.T> {
let dispatcher = selectDispatcher(given: on, configured: conf.D.map, flags: flags)
return then(on: dispatcher, body)
}
/**
The provided closure is executed when this cancellable promise is resolved.
This is like `then` but it requires the closure to return a non-promise and non-cancellable-promise.
let context = firstly {
URLSession.shared.dataTask(.promise, with: url1)
}.cancellize().map { response in
response.data.length
}.done { length in
//
}.cancelContext
//
context.cancel()
- Parameter on: The queue to which the provided closure dispatches.
- Parameter flags: `DispatchWorkItemFlags` to be applied when dispatching.
- Parameter transform: The closure that is executed when this CancellablePromise is fulfilled. It must return a non-promise and non-cancellable-promise.
- Returns: A new cancellable promise that is resolved with the value returned from the provided closure.
*/
func map<V>(on: DispatchQueue? = .pmkDefault, flags: DispatchWorkItemFlags? = nil, _ transform: @escaping (U.T) throws -> V) -> CancellablePromise<V> {
let dispatcher = selectDispatcher(given: on, configured: conf.D.map, flags: flags)
return map(on: dispatcher, transform)
}
/**
The provided closure is executed when this cancellable promise is resolved.
In your closure return an `Optional`, if you return `nil` the resulting cancellable promise is rejected
with `PMKError.compactMap`, otherwise the cancellable promise is fulfilled with the unwrapped value.
let context = firstly {
URLSession.shared.dataTask(.promise, with: url)
}.cancellize().compactMap {
try JSONSerialization.jsonObject(with: $0.data) as? [String: String]
}.done { dictionary in
//
}.catch {
// either `PMKError.compactMap` or a `JSONError`
}.cancelContext
//
context.cancel()
*/
func compactMap<V>(on: DispatchQueue? = .pmkDefault, flags: DispatchWorkItemFlags? = nil, _ transform: @escaping (U.T) throws -> V?) -> CancellablePromise<V> {
let dispatcher = selectDispatcher(given: on, configured: conf.D.map, flags: flags)
return compactMap(on: dispatcher, transform)
}
}

View File

@ -1,82 +0,0 @@
import Dispatch
// These are protocols that define DispatchQueue-based wrappers for functions that are found on multiple
// entities. By putting the wrappers in a separate mixin protocol, they can be added to the objects
// without duplication.
//
// Ideally, we would just add the mixin to protocols such as Thenable. However, Swift (as of v5)
// does not allow protocol extension that add conformance to other protocols. The underlying issue is the risk
// of overlapping conformances. See https://goo.gl/rViwWS. The workaround is to declare conformance on each
// underlying object separately.
//
// Associated types within protocols may not be generic, so there are many functions that can't be genericized
// in this way. For example, anything of the form `func foo<U>(_ body: () -> U) -> Promise<U>` is unrepresentable.
//
// These protocols have to be public to make their contents accessible to users, but the protocols themselves
// should never appear in Xcode or in the documentation.
public protocol _PMKSharedWrappers {
associatedtype T
associatedtype BaseOfT
associatedtype BaseOfVoid
associatedtype VoidReturn
func done(on: Dispatcher, _ body: @escaping(T) throws -> Void) -> BaseOfVoid
func get(on: Dispatcher, _ body: @escaping(T) throws -> Void) -> BaseOfT
func tap(on: Dispatcher, _ body: @escaping(Result<T, Error>) -> Void) -> BaseOfT
func recover<U: Thenable>(on: Dispatcher, policy: CatchPolicy, _ body: @escaping(Error) throws -> U) -> BaseOfT where U.T == T
func recover<U: Thenable, E: Swift.Error>(only: E, on: Dispatcher, _ body: @escaping(E) throws -> U) -> BaseOfT where U.T == T, E: Equatable
func recover<U: Thenable, E: Swift.Error>(only: E.Type, on: Dispatcher, policy: CatchPolicy, _ body: @escaping(E) throws -> U) -> BaseOfT where U.T == T
func ensure(on: Dispatcher, _ body: @escaping () -> Void) -> BaseOfT
func ensureThen(on: Dispatcher, _ body: @escaping () -> VoidReturn) -> BaseOfT
}
extension Promise: _PMKSharedWrappers {
public typealias T = T
public typealias BaseOfT = Promise<T>
}
extension CancellablePromise: _PMKSharedWrappers {
public typealias T = T
public typealias BaseOfT = CancellablePromise<T>
}
public protocol _PMKSharedVoidWrappers {
associatedtype BaseOfT
func recover(on: Dispatcher, policy: CatchPolicy, _ body: @escaping(Error) throws -> Void) -> BaseOfT
func recover<E: Swift.Error>(only: E, on: Dispatcher, _ body: @escaping(E) throws -> Void) -> BaseOfT where E: Equatable
func recover<E: Swift.Error>(only: E.Type, on: Dispatcher, policy: CatchPolicy, _ body: @escaping(E) throws -> Void) -> BaseOfT
}
extension Promise: _PMKSharedVoidWrappers where T == Void {}
extension CancellablePromise: _PMKSharedVoidWrappers where C.T == Void {}
public protocol _PMKCatchWrappers {
associatedtype Finalizer
associatedtype CascadingFinalizer
func `catch`(on: Dispatcher, policy: CatchPolicy, _ body: @escaping(Error) -> Void) -> Finalizer
func `catch`<E: Swift.Error>(only: E, on: Dispatcher, _ body: @escaping(E) -> Void) -> CascadingFinalizer where E: Equatable
func `catch`<E: Swift.Error>(only: E.Type, on: Dispatcher, policy: CatchPolicy, _ body: @escaping(E) -> Void) -> CascadingFinalizer
}
extension Promise: _PMKCatchWrappers {}
extension PMKCascadingFinalizer: _PMKCatchWrappers {}
extension CancellablePromise: _PMKCatchWrappers {}
extension CancellableCascadingFinalizer: _PMKCatchWrappers {}
public protocol _PMKFinallyWrappers {
associatedtype FinallyReturn
func finally(on: Dispatcher, _ body: @escaping () -> Void) -> FinallyReturn
}
extension PMKFinalizer: _PMKFinallyWrappers {}
extension CancellableFinalizer: _PMKFinallyWrappers {}

14
Sources/after.m Normal file
View File

@ -0,0 +1,14 @@
#import "AnyPromise.h"
@import Dispatch;
@import Foundation.NSDate;
@import Foundation.NSValue;
/// @return A promise that fulfills after the specified duration.
AnyPromise *PMKAfter(NSTimeInterval duration) {
return [AnyPromise promiseWithResolverBlock:^(PMKResolver resolve) {
dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(duration * NSEC_PER_SEC));
dispatch_after(time, dispatch_get_global_queue(0, 0), ^{
resolve(@(duration));
});
}];
}

View File

@ -1,25 +1,21 @@
import struct Foundation.TimeInterval
import Dispatch
/// Extend DispatchWorkItem to be cancellable
extension DispatchWorkItem: Cancellable { }
/**
after(seconds: 1.5).then {
//
}
- Returns: A guarantee that resolves after the specified duration.
- Note: cancelling this guarantee will cancel the underlying timer task
- SeeAlso: [Cancellation](http://promisekit.org/docs/)
*/
public func after(seconds: TimeInterval) -> Guarantee<Void> {
let (rg, seal) = Guarantee<Void>.pending()
let when = DispatchTime.now() + seconds
let task = DispatchWorkItem { seal(()) }
rg.setCancellable(task)
q.asyncAfter(deadline: when, execute: task)
#if swift(>=4.0)
q.asyncAfter(deadline: when) { seal(()) }
#else
q.asyncAfter(deadline: when, execute: seal)
#endif
return rg
}
@ -29,15 +25,15 @@ public func after(seconds: TimeInterval) -> Guarantee<Void> {
}
- Returns: A guarantee that resolves after the specified duration.
- Note: cancelling this guarantee will cancel the underlying timer task
- SeeAlso: [Cancellation](http://promisekit.org/docs/)
*/
public func after(_ interval: DispatchTimeInterval) -> Guarantee<Void> {
let (rg, seal) = Guarantee<Void>.pending()
let when = DispatchTime.now() + interval
let task = DispatchWorkItem { seal(()) }
rg.setCancellable(task)
q.asyncAfter(deadline: when, execute: task)
#if swift(>=4.0)
q.asyncAfter(deadline: when) { seal(()) }
#else
q.asyncAfter(deadline: when, execute: seal)
#endif
return rg
}

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