Compare commits
68 Commits
fix-NSErro
...
v7
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9f494dd1c3 | ||
|
|
9a08dcc512 | ||
|
|
216ba58be7 | ||
|
|
e4f52c22d6 | ||
|
|
a8f2e9b739 | ||
|
|
ca4ee531ef | ||
|
|
cd1a9b83ab | ||
|
|
c52bca7258 | ||
|
|
46517fee00 | ||
|
|
43394a7fe7 | ||
|
|
342c059a46 | ||
|
|
f634434ad0 | ||
|
|
b8c82085bc | ||
|
|
3e21581aba | ||
|
|
cc117234db | ||
|
|
bc28be9e91 | ||
|
|
a10e6ea8dc | ||
|
|
43662ff267 | ||
|
|
71ffdce045 | ||
|
|
a64f4c24c8 | ||
|
|
0ca0b8bc28 | ||
|
|
3cfc3a7893 | ||
|
|
ed7c6bcdf1 | ||
|
|
a8715931a9 | ||
|
|
7600e18972 | ||
|
|
4c09a115e5 | ||
|
|
f1885f3d4f | ||
|
|
c7b2bd1657 | ||
|
|
3729b5a23e | ||
|
|
4692135d97 | ||
|
|
78818516c5 | ||
|
|
4a8c2487d9 | ||
|
|
a09eb08b32 | ||
|
|
9e4b9be0d5 | ||
|
|
2078574b14 | ||
|
|
b286d7bd36 | ||
|
|
d91425dec3 | ||
|
|
dd83de89fc | ||
|
|
eddafa8e8e | ||
|
|
bd0a551392 | ||
|
|
f6242108a6 | ||
|
|
1495d68f77 | ||
|
|
087f066492 | ||
|
|
b06d7e59ed | ||
|
|
34e31140dc | ||
|
|
4ff28fad6c | ||
|
|
5e3fe21423 | ||
|
|
c68af9f021 | ||
|
|
ed6a108e56 | ||
|
|
2b90cb3d4e | ||
|
|
9ca62ff505 | ||
|
|
7038ecc4de | ||
|
|
ccccc0cf14 | ||
|
|
aa473f1cea | ||
|
|
d963bde01a | ||
|
|
442a6181df | ||
|
|
50f05b5ace | ||
|
|
ddb9677a2f | ||
|
|
ff06e0d1e7 | ||
|
|
99b4804128 | ||
|
|
6d2ea9cfe6 | ||
|
|
2a549fa7af | ||
|
|
87e84e6fe3 | ||
|
|
c202ed68cc | ||
|
|
a90ce6b5e8 | ||
|
|
7d61133ebb | ||
|
|
277944ff19 | ||
|
|
a484e750e4 |
2
.github/FUNDING.yml
vendored
2
.github/FUNDING.yml
vendored
@ -1,2 +0,0 @@
|
||||
tidelift: "cocoapods/PromiseKit"
|
||||
patreon: "mxcl"
|
||||
19
.github/LinuxMain.stencil
vendored
19
.github/LinuxMain.stencil
vendored
@ -1,19 +0,0 @@
|
||||
@testable import Core
|
||||
@testable import A_
|
||||
import XCTest
|
||||
|
||||
//TODO get this to run on CI and don’t have it committed
|
||||
//NOTE problem is Sourcery doesn’t 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
277
.github/PromiseKit.podspec
vendored
@ -1,277 +0,0 @@
|
||||
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
2
.github/codecov.yml
vendored
@ -1,7 +1,7 @@
|
||||
ignore:
|
||||
- "Tests"
|
||||
- "README.md"
|
||||
- "Documentation"
|
||||
- "Documents"
|
||||
- ".travis.yml"
|
||||
|
||||
codecov:
|
||||
|
||||
2
.github/jazzy.yml
vendored
2
.github/jazzy.yml
vendored
@ -13,6 +13,6 @@ output:
|
||||
../output
|
||||
# output directory is relative to config file… ugh
|
||||
readme:
|
||||
Documentation/README.md
|
||||
Documents/README.md
|
||||
theme:
|
||||
fullwidth
|
||||
|
||||
17
.github/release
vendored
17
.github/release
vendored
@ -1,17 +0,0 @@
|
||||
#!/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
12
.github/sourcery.yml
vendored
@ -1,12 +0,0 @@
|
||||
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
|
||||
131
.github/spelling-skip-words
vendored
Normal file
131
.github/spelling-skip-words
vendored
Normal file
@ -0,0 +1,131 @@
|
||||
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
10
.gitignore
vendored
@ -1,9 +1,7 @@
|
||||
*.xcodeproj/**/xcuserdata/
|
||||
*.xcscmblueprint
|
||||
/PromiseKit.xcodeproj
|
||||
/Carthage
|
||||
/.build
|
||||
.DS_Store
|
||||
DerivedData
|
||||
/PromiseKit.podspec
|
||||
/Extensions/Carthage
|
||||
/Tests/JS-A+/build
|
||||
/build
|
||||
/Tests/A+/JavaScript/build
|
||||
/.swiftpm
|
||||
|
||||
69
.gitmodules
vendored
69
.gitmodules
vendored
@ -1,69 +0,0 @@
|
||||
[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 +0,0 @@
|
||||
extra_ignore_directories: [ Tests ]
|
||||
225
.travis.yml
225
.travis.yml
@ -1,201 +1,102 @@
|
||||
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
|
||||
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: permutations
|
||||
- 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 -Xswiftc -target -Xswiftc x86_64-apple-macosx10.12
|
||||
install: swift test --generate-linuxmain
|
||||
script: git diff --exit-code
|
||||
|
||||
- &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
|
||||
- &swiftpm
|
||||
name: macOS SwiftPM 5.0
|
||||
stage: permutations
|
||||
script: swift build
|
||||
- <<: *swiftpm
|
||||
name: macOS SwiftPM 5.1
|
||||
osx_image: xcode11
|
||||
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
|
||||
|
||||
- &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
|
||||
osx_image: xcode11
|
||||
env: SWIFT=5.1
|
||||
|
||||
- &linux
|
||||
stage: swiftpm
|
||||
env: SWIFT_BUILD_VERSION=3 SWIFT_VERSION=4.0.3
|
||||
name: Linux / Swift 3.2
|
||||
name: Linux
|
||||
env: SWIFT_VERSION='5.0'
|
||||
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_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
|
||||
env: SWIFT_VERSION=5.1-DEVELOPMENT-SNAPSHOT-2019-07-03-a
|
||||
|
||||
- &test
|
||||
stage: test
|
||||
name: macOS / Xcode 10.2
|
||||
xcode_scheme: PromiseKit
|
||||
xcode_project: PromiseKit.xcodeproj
|
||||
xcode_destination: 'platform=macOS'
|
||||
- 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
|
||||
after_success: bash <(curl -s https://codecov.io/bash)
|
||||
|
||||
- <<: *test
|
||||
name: iOS / Xcode 10.2
|
||||
xcode_destination: 'platform=iOS Simulator,OS=12.2,name=iPhone SE'
|
||||
|
||||
- <<: *test
|
||||
name: tvOS / Xcode 10.2
|
||||
xcode_destination: 'platform=tvOS Simulator,OS=12.2,name=Apple TV'
|
||||
|
||||
- name: Promises/A+ (via WebKit JavaScript Bridge)
|
||||
before_install:
|
||||
set -exo pipefail
|
||||
install:
|
||||
bash -c "cd Tests/JS-A+; npm ci &>/dev/null && npm run --hide-modules build"
|
||||
script:
|
||||
xcodebuild -scheme PromiseKit -target PMKJSA+Tests -enableCodeCoverage NO -only-testing:PMKJSA+Tests test | xcpretty
|
||||
cache.directories:
|
||||
- Tests/JS-A+/build
|
||||
- Tests/JS-A+/node_modules
|
||||
|
||||
- &swiftpm
|
||||
stage: swiftpm
|
||||
name: 'macOS / swift-tools-version: 4.0 / Swift 4.1.2'
|
||||
osx_image: xcode9.4
|
||||
script: swift build
|
||||
- <<: *swiftpm
|
||||
osx_image: xcode10.1
|
||||
name: 'macOS / swift-tools-version: 4.2 / Swift 4.2.1'
|
||||
- <<: *swiftpm
|
||||
osx_image: xcode10.3
|
||||
name: 'macOS / swift-tools-version: 5.0 / Swift 5.0.0'
|
||||
- <<: *swiftpm
|
||||
osx_image: xcode11
|
||||
name: 'macOS / swift-tools-version: 5.0 / Swift 5.1.0'
|
||||
- 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
|
||||
|
||||
- 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
|
||||
- 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: Generate Documentation
|
||||
git.depth: false
|
||||
install: gem install jazzy
|
||||
before_script: swift package generate-xcodeproj
|
||||
script: |
|
||||
set -exo pipefail
|
||||
jazzy --config .github/jazzy.yml \
|
||||
@ -204,9 +105,9 @@ jobs:
|
||||
git remote update
|
||||
git fetch origin gh-pages:gh-pages --depth 1
|
||||
git checkout gh-pages
|
||||
rm -rf reference/v6
|
||||
mv output reference/v6
|
||||
git add reference/v6
|
||||
rm -rf reference/v7
|
||||
mv foo reference/v7
|
||||
git add reference/v7
|
||||
git config user.name "Travis"
|
||||
git config user.email "jazzy@travis-ci.com"
|
||||
git commit -m "Updated docs for v$TRAVIS_TAG"
|
||||
|
||||
@ -209,3 +209,45 @@ its work on a background thread.
|
||||
|
||||
Promises abstract asynchronicity, so exploit and support that model. Design your
|
||||
APIs so that consumers don’t 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.
|
||||
464
Documents/Cancel.md
Normal file
464
Documents/Cancel.md
Normal file
@ -0,0 +1,464 @@
|
||||
# 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><T>
|
||||
then<U>(<span style="color:gray;"><i>on: DispatchQueue?</i></span>, body: (T) -> BFTask<U>) -> <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)
|
||||
}
|
||||
}
|
||||
```
|
||||
@ -82,8 +82,10 @@ class MyRestAPI {
|
||||
}
|
||||
```
|
||||
|
||||
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.
|
||||
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.
|
||||
|
||||
PromiseKit is *entirely* thread safe.
|
||||
|
||||
@ -173,7 +175,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
|
||||
|
||||
var fade = Guarantee()
|
||||
let fade = Guarantee()
|
||||
for cell in tableView.visibleCells {
|
||||
fade = fade.then {
|
||||
UIView.animate(.promise, duration: 0.1) {
|
||||
@ -186,14 +188,12 @@ fade.done {
|
||||
}
|
||||
```
|
||||
|
||||
Or if you have an array of closures that return promises:
|
||||
Or if you have an array of promises:
|
||||
|
||||
```swift
|
||||
var foo = Promise()
|
||||
for nextPromise in arrayOfClosuresThatReturnPromises {
|
||||
foo = foo.then(nextPromise)
|
||||
// ^^ you rarely would want an array of promises instead, since then
|
||||
// they have all already started, you may as well use `when()`
|
||||
for nextPromise in arrayOfPromises {
|
||||
foo = foo.then { nextPromise }
|
||||
}
|
||||
foo.done {
|
||||
// finish
|
||||
@ -212,22 +212,42 @@ 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).then {
|
||||
race(when(fulfilled: fetches).asVoid(), timeout(seconds: 4)).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.
|
||||
|
||||
# Minimum Duration
|
||||
```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
|
||||
|
||||
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
|
||||
@ -245,61 +265,22 @@ 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
|
||||
|
||||
Promises don’t have a `cancel` function, but they do support cancellation through a
|
||||
special error type that conforms to the `CancellableError` protocol.
|
||||
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`.
|
||||
|
||||
```swift
|
||||
func foo() -> (Promise<Void>, cancel: () -> Void) {
|
||||
let task = Task(…)
|
||||
var cancelme = false
|
||||
Invoking `cancel` will both reject the promise with `PMKError.cancelled` and cancel any
|
||||
underlying asynchronous task(s).
|
||||
|
||||
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 don’t have a `cancel` function because you don’t 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.
|
||||
For full details see [Cancelling Promises](Cancel.md).
|
||||
|
||||
## Retry / Polling
|
||||
|
||||
@ -483,7 +464,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`
|
||||
@ -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 Apple’s SDKs rather than having to do all the work of writing the Promise implementations yourself? Then pick PromiseKit.
|
||||
* Do you want to be able to use Promises with Swift 3.x, Swift 4.x, ObjC, iOS, tvOS, watchOS, macOS, Android & Linux? Then pick PromiseKit.
|
||||
* Do you want to be able to use Promises with Swift, 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, 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,
|
||||
in RxSwift, 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,35 +256,25 @@ 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`?
|
||||
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:
|
||||
|
||||
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
|
||||
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)
|
||||
func foo() -> Promise<Any>
|
||||
return Promise { seal in
|
||||
Alamofire.request(rq).responseJSON { rsp in
|
||||
seal.resolve(rsp.value, rsp.error)
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
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.
|
||||
Who chooses when this promise starts? The answer is: Alamofire does, and in this
|
||||
case, it “starts” immediately when `foo()` is called.
|
||||
|
||||
## What is a good way to use Firebase with PromiseKit
|
||||
|
||||
@ -317,36 +307,56 @@ feature because it gives you guarantees about the flow of your chains.
|
||||
|
||||
## How do I change the default queues that handlers run on?
|
||||
|
||||
You can change the values of `PromiseKit.conf.Q`. There are two variables that
|
||||
change the default queues that the two kinds of handler run on. A typical
|
||||
pattern is to change all your `then`-type handlers to run on a background queue
|
||||
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
|
||||
and to have all your “finalizers” run on the main queue:
|
||||
|
||||
```
|
||||
PromiseKit.conf.Q.map = .global()
|
||||
PromiseKit.conf.Q.return = .main //NOTE this is the default
|
||||
PromiseKit.conf.Q.return = .main
|
||||
```
|
||||
|
||||
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.)
|
||||
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.)
|
||||
|
||||
```swift
|
||||
// in your test suite setup code
|
||||
PromiseKit.conf.Q.map = nil
|
||||
PromiseKit.conf.Q.return = nil
|
||||
PromiseKit.conf.Q = (map: nil, return: nil)
|
||||
```
|
||||
|
||||
## How do I use PromiseKit on the server side?
|
||||
|
||||
If your server framework requires that the main queue remain unused (e.g., Kitura),
|
||||
then you must use PromiseKit 6 and you must tell PromiseKit not to dispatch to the
|
||||
main queue by default. This is easy enough:
|
||||
then you must tell PromiseKit not to dispatch there by default. This is easy enough:
|
||||
|
||||
```swift
|
||||
PromiseKit.conf.Q = (map: DispatchQueue.global(), return: DispatchQueue.global())
|
||||
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")
|
||||
```
|
||||
|
||||
> Note, we recommend using your own queue rather than `.global()`, we've seen better performance this way.
|
||||
@ -382,12 +392,12 @@ Kitura.run()
|
||||
|
||||
## How do I control console output?
|
||||
|
||||
By default PromiseKit emits console messages when certain events occur. These events include:
|
||||
By default, PromiseKit emits warning messages on the console 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 }
|
||||
@ -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` is new to PromiseKit 5. We previously defined a variant of `then` that
|
||||
> *Note*: `done` was introduced in 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* `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
|
||||
> *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
|
||||
*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 have managed to avoid many of these common
|
||||
> By adding `done` to PromiseKit 5, we were able to blunt 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)
|
||||
|
||||
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).
|
||||
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.)
|
||||
|
||||
|
||||
[API Reference]: https://mxcl.dev/PromiseKit/reference/v6/Classes/Promise.html
|
||||
[API Reference]: https://mxcl.dev/PromiseKit/reference/v7/Classes/Promise.html
|
||||
@ -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")),
|
||||
@ -4,6 +4,7 @@
|
||||
* Handbook
|
||||
* [Getting Started](GettingStarted.md)
|
||||
* [Promises: Common Patterns](CommonPatterns.md)
|
||||
* [Cancelling Promises](Cancel.md)
|
||||
* [Frequently Asked Questions](FAQ.md)
|
||||
* Manual
|
||||
* [Installation Guide](Installation.md)
|
||||
@ -11,4 +12,4 @@
|
||||
* [Troubleshooting](Troubleshooting.md)
|
||||
* [Appendix](Appendix.md)
|
||||
* [Examples](Examples)
|
||||
* [API Reference](https://mxcl.dev/PromiseKit/reference/v6/Classes/Promise.html)
|
||||
* [API Reference](https://mxcl.dev/PromiseKit/reference/v7/Classes/Promise.html)
|
||||
@ -68,7 +68,7 @@ return firstly {
|
||||
}
|
||||
```
|
||||
|
||||
We have made great effort to reduce the need for explicit typing in PromiseKit 6,
|
||||
We have made great effort to reduce the need for explicit typing in PromiseKit,
|
||||
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,6 +163,131 @@ 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 doesn’t work
|
||||
|
||||
@ -175,17 +300,17 @@ All PromiseKit functions are documented and provide examples.
|
||||
|
||||
You have a `then`; you want a `done`.
|
||||
|
||||
## "Missing argument for parameter #1 in call"
|
||||
## "Missing argument for parameter #1 in call" "Unable to infer closure type in the current context"
|
||||
|
||||
This is part of Swift 4’s “tuplegate”.
|
||||
|
||||
You must specify your `Void` parameter:
|
||||
You must fulfill a `Promise<Void>` with an explicit `Void` parameter:
|
||||
|
||||
```swift
|
||||
seal.fulfill(())
|
||||
```
|
||||
|
||||
Yes: we hope they revert this change in Swift 5 too.
|
||||
This wart remains in Swift 5, too. It's probably not going to change.
|
||||
|
||||
## "Ambiguous reference to 'firstly(execute:)'"
|
||||
|
||||
@ -1 +0,0 @@
|
||||
Subproject commit 911b257783efc0aa705481d556c8ecdfcdc055a9
|
||||
@ -1 +0,0 @@
|
||||
Subproject commit 6c01f5719ff08bbbf81d42b7e20c5188cab4a81d
|
||||
@ -1 +0,0 @@
|
||||
Subproject commit c694fa0628a96951c6e2db113dc4eb6dd4e47ebc
|
||||
@ -1 +0,0 @@
|
||||
Subproject commit a41a97d3d3aa6efb575d0f42f3b8aeec5a756ead
|
||||
@ -1 +0,0 @@
|
||||
Subproject commit e4317d5279db7ecfc5bc565e1dd88fcabc02c352
|
||||
@ -1 +0,0 @@
|
||||
Subproject commit fbc9941219c9bbdfe6d664ca6b8d10b5e2f8fb70
|
||||
@ -1 +0,0 @@
|
||||
Subproject commit 9586f3c06c4934e776c2b96c128f332a168c2c91
|
||||
@ -1 +0,0 @@
|
||||
Subproject commit 8e0bc957996daad5c49f8809839f8699883ed364
|
||||
@ -1 +0,0 @@
|
||||
Subproject commit c4ea4dba6e0516986b3cb1d93c86f6991ce46861
|
||||
@ -1 +0,0 @@
|
||||
Subproject commit 1f90d67538047087428d82550976c2e43aba0961
|
||||
@ -1 +0,0 @@
|
||||
Subproject commit 1a276e598dac59489ed904887e0740fa75e571e0
|
||||
@ -1 +0,0 @@
|
||||
Subproject commit b185822cb1fcf5297347498b783b4e21b2e55282
|
||||
@ -1 +0,0 @@
|
||||
Subproject commit 668abe78a52a23bd276c8924dba91592ac0ca45a
|
||||
@ -1 +0,0 @@
|
||||
Subproject commit d1d4ebdd6ceac78d734e2ae27e8ab429906d6929
|
||||
@ -1 +0,0 @@
|
||||
Subproject commit 2a93ce737502e13a3eaedc444b9ecb5abb28ec79
|
||||
@ -1 +0,0 @@
|
||||
Subproject commit 5ec40a68f168255dcc339b9620e2dcf62079ae6b
|
||||
@ -1 +0,0 @@
|
||||
Subproject commit 48f801454b01c69a1553873cb1d95e90ae2ec4cc
|
||||
@ -1 +0,0 @@
|
||||
Subproject commit b22f187b6b3f82f102aa309b256bf24570a73d1f
|
||||
@ -1 +0,0 @@
|
||||
Subproject commit 378912a47a206183b931d2008e0f396e9fc390e8
|
||||
@ -1 +0,0 @@
|
||||
Subproject commit e26f6a55921ea671855093754686991b08ef97cc
|
||||
@ -1 +0,0 @@
|
||||
Subproject commit 6b009f906fc489346a73759b5668b263a320c51c
|
||||
@ -1 +0,0 @@
|
||||
Subproject commit baf104ba29ff94d9d41e90e8ec557a56b87fecd4
|
||||
@ -1 +0,0 @@
|
||||
Subproject commit b5b3fca958a7296f71313b6c172584bc91d6da8b
|
||||
@ -1,30 +1,24 @@
|
||||
// swift-tools-version:4.0
|
||||
// swift-tools-version:5.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"]),
|
||||
]
|
||||
|
||||
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 = [
|
||||
.v5 // grab PromiseKit-6.x if you want Swift 3.1‒4.2
|
||||
]
|
||||
pkg.swiftLanguageVersions = [3, 4]
|
||||
pkg.targets = [
|
||||
pmk,
|
||||
.testTarget(name: "A+", dependencies: ["PromiseKit"]),
|
||||
.testTarget(name: "CorePromise", dependencies: ["PromiseKit"], path: "Tests/CorePromise"),
|
||||
.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"),
|
||||
]
|
||||
|
||||
@ -1,30 +0,0 @@
|
||||
// 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"),
|
||||
]
|
||||
@ -1,33 +0,0 @@
|
||||
// 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"),
|
||||
]
|
||||
@ -1,27 +0,0 @@
|
||||
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
|
||||
@ -1,7 +0,0 @@
|
||||
<?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>
|
||||
@ -1,7 +0,0 @@
|
||||
<?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
@ -1,7 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Workspace
|
||||
version = "1.0">
|
||||
<FileRef
|
||||
location = "self:">
|
||||
</FileRef>
|
||||
</Workspace>
|
||||
@ -1,8 +0,0 @@
|
||||
<?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>
|
||||
@ -1,8 +0,0 @@
|
||||
<?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>
|
||||
@ -1,137 +0,0 @@
|
||||
<?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>
|
||||
86
README.md
86
README.md
@ -35,8 +35,15 @@ pod used in many of the most popular apps in the world.
|
||||
|
||||
# PromiseKit 7 Alpha
|
||||
|
||||
We are testing PromiseKit 7 alpha, it is Swift 5 only. It is tagged and thus
|
||||
importable in all package managers.
|
||||
PromiseKit 7 is prerelease, if you’re using it: beware!
|
||||
|
||||
PromiseKit 7 uses Swift 5’s `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.
|
||||
|
||||
# PromiseKit 6
|
||||
|
||||
@ -44,26 +51,22 @@ importable in all package managers.
|
||||
|
||||
# Quick Start
|
||||
|
||||
In your [Podfile]:
|
||||
In your `Package.swift`:
|
||||
|
||||
```ruby
|
||||
use_frameworks!
|
||||
|
||||
target "Change Me!" do
|
||||
pod "PromiseKit", "~> 6.8"
|
||||
end
|
||||
```swift
|
||||
package.dependencies.append(
|
||||
.package(url: "https://github.com/mxcl/PromiseKit", from: Version(7, 0, 0, prereleaseIdentifiers: [“alpha”, “1”]))
|
||||
)
|
||||
```
|
||||
|
||||
> The above gives an Xcode warning? See our [Installation Guide].
|
||||
PromiseKit 7 supports Swift 5.x; Xcode >= 10.2; iOS, macOS, tvOS, watchOS, Linux
|
||||
and Android; SwiftPM.
|
||||
|
||||
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)).
|
||||
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)).
|
||||
|
||||
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).
|
||||
For Carthage, SwiftPM, Accio, etc., or for instructions when using older Swifts or Xcodes, see our [Installation Guide].
|
||||
|
||||
# Professionally Supported PromiseKit is Now Available
|
||||
|
||||
@ -92,15 +95,16 @@ help me continue my work, I appreciate it 🙏🏻
|
||||
# Documentation
|
||||
|
||||
* Handbook
|
||||
* [Getting Started](Documentation/GettingStarted.md)
|
||||
* [Promises: Common Patterns](Documentation/CommonPatterns.md)
|
||||
* [Frequently Asked Questions](Documentation/FAQ.md)
|
||||
* [Getting Started](Documents/GettingStarted.md)
|
||||
* [Promises: Common Patterns](Documents/CommonPatterns.md)
|
||||
* [Cancelling Promises](Documents/Cancel.md)
|
||||
* [Frequently Asked Questions](Documents/FAQ.md)
|
||||
* Manual
|
||||
* [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)
|
||||
* [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)
|
||||
|
||||
# Extensions
|
||||
|
||||
@ -111,29 +115,19 @@ extensions are available by specifying additional subspecs in your `Podfile`,
|
||||
e.g.:
|
||||
|
||||
```ruby
|
||||
pod "PromiseKit/MapKit" # MKDirections().calculate().then { /*…*/ }
|
||||
pod "PromiseKit/CoreLocation" # CLLocationManager.requestLocation().then { /*…*/ }
|
||||
pod "PMKMapKit" # MKDirections().calculate().then { /*…*/ }
|
||||
pod "PMKCoreLocation" # CLLocationManager.requestLocation().then { /*…*/ }
|
||||
```
|
||||
|
||||
All our extensions are separate repositories at the [PromiseKit organization].
|
||||
|
||||
## I don't want the extensions!
|
||||
|
||||
Then don’t 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 'PromiseKit/Foundation' # https://github.com/PromiseKit/Foundation
|
||||
// pod 'PMKFoundation' # https://github.com/PromiseKit/PMKFoundation
|
||||
|
||||
firstly {
|
||||
URLSession.shared.dataTask(.promise, with: try makeUrlRequest()).validate()
|
||||
@ -159,7 +153,7 @@ func makeUrlRequest() throws -> URLRequest {
|
||||
And [Alamofire]:
|
||||
|
||||
```swift
|
||||
// pod 'PromiseKit/Alamofire' # https://github.com/PromiseKit/Alamofire-
|
||||
// pod 'PMKAlamofire' # https://github.com/PromiseKit/PMKAlamofire
|
||||
|
||||
firstly {
|
||||
Alamofire
|
||||
@ -185,13 +179,19 @@ became true, but nowadays it isn’t really necessary.
|
||||
|
||||
# Support
|
||||
|
||||
Please check our [Troubleshooting Guide](Documentation/Troubleshooting.md), and
|
||||
Please check our [Troubleshooting Guide](Documents/Troubleshooting.md), and
|
||||
if after that you still have a question, ask at our [Gitter chat channel] or on [our bug tracker].
|
||||
|
||||
## Security & Vulnerability Reporting or Disclosure
|
||||
# Contributing
|
||||
|
||||
https://tidelift.com/security
|
||||
## Xcode 10
|
||||
|
||||
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 @@ https://tidelift.com/security
|
||||
[our bug tracker]: https://github.com/mxcl/PromiseKit/issues/new
|
||||
[Podfile]: https://guides.cocoapods.org/syntax/podfile.html
|
||||
[PMK6]: http://mxcl.dev/PromiseKit/news/2018/02/PromiseKit-6.0-Released/
|
||||
[Installation Guide]: Documentation/Installation.md
|
||||
[Installation Guide]: Documents/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
|
||||
|
||||
@ -1,32 +0,0 @@
|
||||
@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
|
||||
@ -1,306 +0,0 @@
|
||||
#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.
|
||||
|
||||
Don’t 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 variant’s 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 Apple’s enums are, so if you find one that hasn’t 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
|
||||
@ -1,179 +0,0 @@
|
||||
#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
|
||||
@ -1,224 +0,0 @@
|
||||
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 don’t 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
|
||||
@ -82,20 +82,3 @@ 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
257
Sources/Cancellation/CancelContext.swift
Normal file
257
Sources/Cancellation/CancelContext.swift
Normal file
@ -0,0 +1,257 @@
|
||||
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
|
||||
}
|
||||
}
|
||||
12
Sources/Cancellation/Cancellable.swift
Normal file
12
Sources/Cancellation/Cancellable.swift
Normal file
@ -0,0 +1,12 @@
|
||||
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 }
|
||||
}
|
||||
628
Sources/Cancellation/CancellableCatchable.swift
Normal file
628
Sources/Cancellation/CancellableCatchable.swift
Normal file
@ -0,0 +1,628 @@
|
||||
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 don’t 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 promise’s 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 promise’s 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 don’t 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)
|
||||
}
|
||||
}
|
||||
141
Sources/Cancellation/CancellablePromise.swift
Normal file
141
Sources/Cancellation/CancellablePromise.swift
Normal file
@ -0,0 +1,141 @@
|
||||
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, so—you know—don’t 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)
|
||||
}
|
||||
}
|
||||
520
Sources/Cancellation/CancellableThenable.swift
Normal file
520
Sources/Cancellation/CancellableThenable.swift
Normal file
@ -0,0 +1,520 @@
|
||||
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 closure’s 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() }
|
||||
}
|
||||
}
|
||||
@ -14,51 +14,197 @@ 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 queue to which the provided closure dispatches.
|
||||
- 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.
|
||||
- Parameter body: 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)
|
||||
- SeeAlso: [Cancellation](https://github.com/mxcl/PromiseKit/blob/master/Documents/CommonPatterns.md#cancellation)
|
||||
*/
|
||||
@discardableResult
|
||||
func `catch`(on: DispatchQueue? = conf.Q.return, flags: DispatchWorkItemFlags? = nil, policy: CatchPolicy = conf.catchPolicy, _ body: @escaping(Error) -> Void) -> PMKFinalizer {
|
||||
func `catch`(on: Dispatcher = conf.D.return, policy: CatchPolicy = conf.catchPolicy, _ body: @escaping(Error) -> Void) -> PMKFinalizer {
|
||||
let finalizer = PMKFinalizer()
|
||||
pipe {
|
||||
switch $0 {
|
||||
case .rejected(let error):
|
||||
case .failure(let error):
|
||||
guard policy == .allErrors || !error.isCancelled else {
|
||||
fallthrough
|
||||
}
|
||||
on.async(flags: flags) {
|
||||
on.dispatch {
|
||||
body(error)
|
||||
finalizer.pending.resolve(())
|
||||
}
|
||||
case .fulfilled:
|
||||
case .success:
|
||||
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: DispatchQueue? = conf.Q.return, flags: DispatchWorkItemFlags? = nil, _ body: @escaping () -> Void) {
|
||||
pending.guarantee.done(on: on, flags: flags) {
|
||||
public func finally(on: Dispatcher = conf.D.return, _ body: @escaping () -> Void) {
|
||||
pending.guarantee.done(on: on) {
|
||||
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 don’t 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.
|
||||
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 {
|
||||
@ -68,52 +214,144 @@ public extension CatchMixin {
|
||||
return .value(CLLocation.chicago)
|
||||
}
|
||||
|
||||
- Parameter on: The queue to which the provided closure dispatches.
|
||||
- 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)
|
||||
- SeeAlso: [Cancellation](https://github.com/mxcl/PromiseKit/blob/master/Documents/CommonPatterns.md#cancellation)
|
||||
*/
|
||||
func recover<U: Thenable>(on: DispatchQueue? = conf.Q.map, flags: DispatchWorkItemFlags? = nil, policy: CatchPolicy = conf.catchPolicy, _ body: @escaping(Error) throws -> U) -> Promise<T> where U.T == T {
|
||||
func recover<U: Thenable>(on: Dispatcher = conf.D.map, policy: CatchPolicy = conf.catchPolicy, _ body: @escaping(Error) throws -> U) -> Promise<T> where U.T == T {
|
||||
let rp = Promise<U.T>(.pending)
|
||||
pipe {
|
||||
switch $0 {
|
||||
case .fulfilled(let value):
|
||||
rp.box.seal(.fulfilled(value))
|
||||
case .rejected(let error):
|
||||
case .success(let value):
|
||||
rp.box.seal(.success(value))
|
||||
case .failure(let error):
|
||||
if policy == .allErrors || !error.isCancelled {
|
||||
on.async(flags: flags) {
|
||||
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(.rejected(error))
|
||||
rp.box.seal(.failure(error))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
rp.box.seal(.rejected(error))
|
||||
rp.box.seal(.failure(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, 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.
|
||||
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.
|
||||
- Parameter body: The handler to execute if this promise is rejected.
|
||||
- SeeAlso: [Cancellation](https://github.com/mxcl/PromiseKit/blob/master/Documentation/CommonPatterns.md#cancellation)
|
||||
- SeeAlso: [Cancellation](https://github.com/mxcl/PromiseKit/blob/master/Documents/CommonPatterns.md#cancellation)
|
||||
*/
|
||||
@discardableResult
|
||||
func recover(on: DispatchQueue? = conf.Q.map, flags: DispatchWorkItemFlags? = nil, _ body: @escaping(Error) -> Guarantee<T>) -> Guarantee<T> {
|
||||
func recover(on: Dispatcher = conf.D.map, _ body: @escaping(Error) -> Guarantee<T>) -> Guarantee<T> {
|
||||
let rg = Guarantee<T>(.pending)
|
||||
pipe {
|
||||
switch $0 {
|
||||
case .fulfilled(let value):
|
||||
case .success(let value):
|
||||
rg.box.seal(value)
|
||||
case .rejected(let error):
|
||||
on.async(flags: flags) {
|
||||
case .failure(let error):
|
||||
on.dispatch {
|
||||
body(error).pipe(to: rg.box.seal)
|
||||
}
|
||||
}
|
||||
@ -134,14 +372,14 @@ public extension CatchMixin {
|
||||
//…
|
||||
}
|
||||
|
||||
- Parameter on: The queue to which the provided closure dispatches.
|
||||
- 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 promise’s resolution.
|
||||
*/
|
||||
func ensure(on: DispatchQueue? = conf.Q.return, flags: DispatchWorkItemFlags? = nil, _ body: @escaping () -> Void) -> Promise<T> {
|
||||
func ensure(on: Dispatcher = conf.D.return, _ body: @escaping () -> Void) -> Promise<T> {
|
||||
let rp = Promise<T>(.pending)
|
||||
pipe { result in
|
||||
on.async(flags: flags) {
|
||||
on.dispatch {
|
||||
body()
|
||||
rp.box.seal(result)
|
||||
}
|
||||
@ -163,14 +401,14 @@ public extension CatchMixin {
|
||||
//…
|
||||
}
|
||||
|
||||
- Parameter on: The queue to which the provided closure dispatches.
|
||||
- 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 promise’s resolution.
|
||||
*/
|
||||
func ensureThen(on: DispatchQueue? = conf.Q.return, flags: DispatchWorkItemFlags? = nil, _ body: @escaping () -> Guarantee<Void>) -> Promise<T> {
|
||||
func ensureThen(on: Dispatcher = conf.D.return, _ body: @escaping () -> Guarantee<Void>) -> Promise<T> {
|
||||
let rp = Promise<T>(.pending)
|
||||
pipe { result in
|
||||
on.async(flags: flags) {
|
||||
on.dispatch {
|
||||
body().done {
|
||||
rp.box.seal(result)
|
||||
}
|
||||
@ -180,7 +418,6 @@ public extension CatchMixin {
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
Consumes the Swift unused-result warning.
|
||||
- Note: You should `catch`, but in situations where you know you don’t need a `catch`, `cauterize` makes your intentions clear.
|
||||
@ -199,21 +436,23 @@ public extension CatchMixin where T == Void {
|
||||
/**
|
||||
The provided closure executes when this promise rejects.
|
||||
|
||||
This variant of `recover` is specialized for `Void` promises and de-errors your chain returning a `Guarantee`, thus you cannot `throw` and you must handle all errors including cancellation.
|
||||
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 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)
|
||||
- SeeAlso: [Cancellation](https://github.com/mxcl/PromiseKit/blob/master/Documents/CommonPatterns.md#cancellation)
|
||||
*/
|
||||
@discardableResult
|
||||
func recover(on: DispatchQueue? = conf.Q.map, flags: DispatchWorkItemFlags? = nil, _ body: @escaping(Error) -> Void) -> Guarantee<Void> {
|
||||
func recover(on: Dispatcher = conf.D.map, _ body: @escaping(Error) -> Void) -> Guarantee<Void> {
|
||||
let rg = Guarantee<Void>(.pending)
|
||||
pipe {
|
||||
switch $0 {
|
||||
case .fulfilled:
|
||||
case .success:
|
||||
rg.box.seal(())
|
||||
case .rejected(let error):
|
||||
on.async(flags: flags) {
|
||||
case .failure(let error):
|
||||
on.dispatch {
|
||||
body(error)
|
||||
rg.box.seal(())
|
||||
}
|
||||
@ -225,32 +464,103 @@ 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 specifying a catch policy.
|
||||
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 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)
|
||||
- SeeAlso: [Cancellation](https://github.com/mxcl/PromiseKit/blob/master/Documents/CommonPatterns.md#cancellation)
|
||||
*/
|
||||
func recover(on: DispatchQueue? = conf.Q.map, flags: DispatchWorkItemFlags? = nil, policy: CatchPolicy = conf.catchPolicy, _ body: @escaping(Error) throws -> Void) -> Promise<Void> {
|
||||
func recover(on: Dispatcher = conf.D.map, policy: CatchPolicy = conf.catchPolicy, _ body: @escaping(Error) throws -> Void) -> Promise<Void> {
|
||||
let rg = Promise<Void>(.pending)
|
||||
pipe {
|
||||
switch $0 {
|
||||
case .fulfilled:
|
||||
rg.box.seal(.fulfilled(()))
|
||||
case .rejected(let error):
|
||||
case .success:
|
||||
rg.box.seal(.success(()))
|
||||
case .failure(let error):
|
||||
if policy == .allErrors || !error.isCancelled {
|
||||
on.async(flags: flags) {
|
||||
on.dispatch {
|
||||
do {
|
||||
rg.box.seal(.fulfilled(try body(error)))
|
||||
rg.box.seal(.success(try body(error)))
|
||||
} catch {
|
||||
rg.box.seal(.rejected(error))
|
||||
rg.box.seal(.failure(error))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
rg.box.seal(.rejected(error))
|
||||
rg.box.seal(.failure(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
|
||||
}
|
||||
}
|
||||
|
||||
@ -8,8 +8,18 @@ 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 {
|
||||
/// The default queues that promises handlers dispatch to
|
||||
public var Q: (map: DispatchQueue?, return: DispatchQueue?) = (map: DispatchQueue.main, return: DispatchQueue.main)
|
||||
/// 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 catch-policy for all `catch` and `resolve`
|
||||
public var catchPolicy = CatchPolicy.allErrorsExceptCancellation
|
||||
@ -17,17 +27,8 @@ 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) -> 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)")
|
||||
}
|
||||
public var logHandler: (LogEvent) -> () = { event in
|
||||
print(event.asString())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -5,9 +5,9 @@ extension Promise: CustomStringConvertible {
|
||||
switch result {
|
||||
case nil:
|
||||
return "Promise(…\(T.self))"
|
||||
case .rejected(let error)?:
|
||||
case .failure(let error)?:
|
||||
return "Promise(\(error))"
|
||||
case .fulfilled(let value)?:
|
||||
case .success(let value)?:
|
||||
return "Promise(\(value))"
|
||||
}
|
||||
}
|
||||
@ -19,26 +19,10 @@ extension Promise: CustomDebugStringConvertible {
|
||||
switch box.inspect() {
|
||||
case .pending(let handlers):
|
||||
return "Promise<\(T.self)>.pending(handlers: \(handlers.bodies.count))"
|
||||
case .resolved(.rejected(let error)):
|
||||
return "Promise<\(T.self)>.rejected(\(type(of: error)).\(error))"
|
||||
case .resolved(.fulfilled(let value)):
|
||||
return "Promise<\(T.self)>.fulfilled(\(value))"
|
||||
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))"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#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
|
||||
|
||||
@ -1,93 +0,0 @@
|
||||
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)
|
||||
}
|
||||
}
|
||||
120
Sources/Dispatcher.swift
Normal file
120
Sources/Dispatcher.swift
Normal file
@ -0,0 +1,120 @@
|
||||
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
|
||||
}
|
||||
41
Sources/Dispatchers/ConcurrencyLimitedDispatcher.swift
Normal file
41
Sources/Dispatchers/ConcurrencyLimitedDispatcher.swift
Normal file
@ -0,0 +1,41 @@
|
||||
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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
31
Sources/Dispatchers/CoreDataDispatcher.swift
Normal file
31
Sources/Dispatchers/CoreDataDispatcher.swift
Normal file
@ -0,0 +1,31 @@
|
||||
#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
|
||||
|
||||
64
Sources/Dispatchers/Queue.swift
Normal file
64
Sources/Dispatchers/Queue.swift
Normal file
@ -0,0 +1,64 @@
|
||||
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
|
||||
}
|
||||
|
||||
}
|
||||
110
Sources/Dispatchers/RateLimitedDispatcher.swift
Normal file
110
Sources/Dispatchers/RateLimitedDispatcher.swift
Normal file
@ -0,0 +1,110 @@
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
57
Sources/Dispatchers/RateLimitedDispatcherBase.swift
Normal file
57
Sources/Dispatchers/RateLimitedDispatcherBase.swift
Normal file
@ -0,0 +1,57 @@
|
||||
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
|
||||
}
|
||||
|
||||
}
|
||||
106
Sources/Dispatchers/StrictRateLimitedDispatcher.swift
Normal file
106
Sources/Dispatchers/StrictRateLimitedDispatcher.swift
Normal file
@ -0,0 +1,106 @@
|
||||
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
|
||||
}
|
||||
|
||||
}
|
||||
@ -18,7 +18,10 @@ 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)
|
||||
@ -49,6 +52,8 @@ 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"
|
||||
}
|
||||
@ -76,6 +81,8 @@ 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 {
|
||||
@ -83,10 +90,9 @@ extension Error {
|
||||
} catch CocoaError.userCancelled {
|
||||
return true
|
||||
} catch {
|
||||
#if canImport(StoreKit)
|
||||
let domain = (error as AnyObject).value(forKey: "domain") as? String
|
||||
let code = (error as AnyObject).value(forKey: "code") as? Int
|
||||
return ("SKErrorDomain", 2) == (domain, code)
|
||||
#if os(macOS) || os(iOS) || os(tvOS)
|
||||
let pair = { ($0.domain, $0.code) }(error as NSError)
|
||||
return ("SKErrorDomain", 2) == pair
|
||||
#else
|
||||
return false
|
||||
#endif
|
||||
|
||||
@ -23,9 +23,15 @@ public final class Guarantee<T>: Thenable {
|
||||
body(box.seal)
|
||||
}
|
||||
|
||||
/// Returns a pending `Guarantee` that can be resolved with the provided closure’s 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>) -> Void) {
|
||||
pipe{ to(.fulfilled($0)) }
|
||||
public func pipe(to: @escaping(Result<T, Error>) -> Void) {
|
||||
pipe{ to(.success($0)) }
|
||||
}
|
||||
|
||||
func pipe(to: @escaping(T) -> Void) {
|
||||
@ -45,20 +51,23 @@ public final class Guarantee<T>: Thenable {
|
||||
}
|
||||
|
||||
/// - See: `Thenable.result`
|
||||
public var result: Result<T>? {
|
||||
public var result: Result<T, Error>? {
|
||||
switch box.inspect() {
|
||||
case .pending:
|
||||
return nil
|
||||
case .resolved(let value):
|
||||
return .fulfilled(value)
|
||||
return .success(value)
|
||||
}
|
||||
}
|
||||
|
||||
final private class Box<T>: EmptyBox<T> {
|
||||
var cancelled = false
|
||||
deinit {
|
||||
switch inspect() {
|
||||
case .pending:
|
||||
PromiseKit.conf.logHandler(.pendingGuaranteeDeallocated)
|
||||
if !cancelled {
|
||||
PromiseKit.conf.logHandler(.pendingGuaranteeDeallocated)
|
||||
}
|
||||
case .resolved:
|
||||
break
|
||||
}
|
||||
@ -73,14 +82,43 @@ 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: DispatchQueue? = conf.Q.return, flags: DispatchWorkItemFlags? = nil, _ body: @escaping(T) -> Void) -> Guarantee<Void> {
|
||||
func done(on: Dispatcher = conf.D.return, _ body: @escaping(T) -> Void) -> Guarantee<Void> {
|
||||
let rg = Guarantee<Void>(.pending)
|
||||
pipe { (value: T) in
|
||||
on.async(flags: flags) {
|
||||
on.dispatch {
|
||||
body(value)
|
||||
rg.box.seal(())
|
||||
}
|
||||
@ -88,40 +126,28 @@ public extension Guarantee {
|
||||
return rg
|
||||
}
|
||||
|
||||
func get(on: DispatchQueue? = conf.Q.return, flags: DispatchWorkItemFlags? = nil, _ body: @escaping (T) -> Void) -> Guarantee<T> {
|
||||
return map(on: on, flags: flags) {
|
||||
func get(on: Dispatcher = conf.D.return, _ body: @escaping (T) -> Void) -> Guarantee<T> {
|
||||
return map(on: on) {
|
||||
body($0)
|
||||
return $0
|
||||
}
|
||||
}
|
||||
|
||||
func map<U>(on: DispatchQueue? = conf.Q.map, flags: DispatchWorkItemFlags? = nil, _ body: @escaping(T) -> U) -> Guarantee<U> {
|
||||
func map<U>(on: Dispatcher = conf.D.map, _ body: @escaping(T) -> U) -> Guarantee<U> {
|
||||
let rg = Guarantee<U>(.pending)
|
||||
pipe { value in
|
||||
on.async(flags: flags) {
|
||||
on.dispatch {
|
||||
rg.box.seal(body(value))
|
||||
}
|
||||
}
|
||||
return rg
|
||||
}
|
||||
|
||||
#if swift(>=4) && !swift(>=5.2)
|
||||
func map<U>(on: DispatchQueue? = conf.Q.map, flags: DispatchWorkItemFlags? = nil, _ keyPath: KeyPath<T, U>) -> Guarantee<U> {
|
||||
@discardableResult
|
||||
func then<U>(on: Dispatcher = conf.D.map, _ body: @escaping(T) -> Guarantee<U>) -> Guarantee<U> {
|
||||
let rg = Guarantee<U>(.pending)
|
||||
pipe { value in
|
||||
on.async(flags: flags) {
|
||||
rg.box.seal(value[keyPath: keyPath])
|
||||
}
|
||||
}
|
||||
return rg
|
||||
}
|
||||
#endif
|
||||
|
||||
@discardableResult
|
||||
func then<U>(on: DispatchQueue? = conf.Q.map, flags: DispatchWorkItemFlags? = nil, _ body: @escaping(T) -> Guarantee<U>) -> Guarantee<U> {
|
||||
let rg = Guarantee<U>(.pending)
|
||||
pipe { value in
|
||||
on.async(flags: flags) {
|
||||
on.dispatch {
|
||||
body(value).pipe(to: rg.box.seal)
|
||||
}
|
||||
}
|
||||
@ -156,206 +182,48 @@ 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` -> `[U]` => `Guarantee<[U]>`
|
||||
`Guarantee<[T]>` => `T` -> `Guarantee<U>` => `Guarantee<[U]>`
|
||||
|
||||
Guarantee.value([1,2,3])
|
||||
.flatMapValues { integer in [integer, integer] }
|
||||
.done {
|
||||
// $0 => [1,1,2,2,3,3]
|
||||
}
|
||||
firstly {
|
||||
.value([1,2,3])
|
||||
}.thenMap {
|
||||
.value($0 * 2)
|
||||
}.done {
|
||||
// $0 => [2,4,6]
|
||||
}
|
||||
*/
|
||||
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) {
|
||||
func thenMap<U>(on: Dispatcher = conf.D.map, _ transform: @escaping(T.Iterator.Element) -> Guarantee<U>) -> Guarantee<[U]> {
|
||||
return then(on: on) {
|
||||
when(fulfilled: $0.map(transform))
|
||||
}.recover {
|
||||
// if happens then is bug inside PromiseKit
|
||||
fatalError(String(describing: $0))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
`Guarantee<[T]>` => `T` -> `Guarantee<[U]>` => `Guarantee<[U]>`
|
||||
|
||||
Guarantee.value([1,2,3])
|
||||
.thenFlatMap { integer in .value([integer, integer]) }
|
||||
.done {
|
||||
// $0 => [1,1,2,2,3,3]
|
||||
}
|
||||
*/
|
||||
func thenFlatMap<U: Thenable>(on: DispatchQueue? = conf.Q.map, flags: DispatchWorkItemFlags? = nil, _ transform: @escaping(T.Iterator.Element) -> U) -> Guarantee<[U.T.Iterator.Element]> where U.T: Sequence {
|
||||
return then(on: on, flags: flags) {
|
||||
when(fulfilled: $0.map(transform))
|
||||
}.map(on: nil) {
|
||||
$0.flatMap { $0 }
|
||||
}.recover {
|
||||
// if happens then is bug inside PromiseKit
|
||||
fatalError(String(describing: $0))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
`Guarantee<[T]>` => `T` -> Bool => `Guarantee<[T]>`
|
||||
|
||||
Guarantee.value([1,2,3])
|
||||
.filterValues { $0 > 1 }
|
||||
.done {
|
||||
// $0 => [2,3]
|
||||
}
|
||||
*/
|
||||
func filterValues(on: DispatchQueue? = conf.Q.map, flags: DispatchWorkItemFlags? = nil, _ isIncluded: @escaping(T.Iterator.Element) -> Bool) -> Guarantee<[T.Iterator.Element]> {
|
||||
return map(on: on, flags: flags) {
|
||||
$0.filter(isIncluded)
|
||||
}
|
||||
}
|
||||
|
||||
#if swift(>=4) && !swift(>=5.2)
|
||||
/**
|
||||
`Guarantee<[T]>` => `KeyPath<T, Bool>` => `Guarantee<[T]>`
|
||||
|
||||
Guarantee.value([Person(name: "Max"), Person(name: "Roman", age: 26, isStudent: false), Person(name: "John", age: 23, isStudent: true)])
|
||||
.filterValues(\.isStudent)
|
||||
.done {
|
||||
// $0 => [Person(name: "John", age: 23, isStudent: true)]
|
||||
}
|
||||
*/
|
||||
func filterValues(on: DispatchQueue? = conf.Q.map, flags: DispatchWorkItemFlags? = nil, _ keyPath: KeyPath<T.Iterator.Element, Bool>) -> Guarantee<[T.Iterator.Element]> {
|
||||
return map(on: on, flags: flags) {
|
||||
$0.filter { $0[keyPath: keyPath] }
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
/**
|
||||
`Guarantee<[T]>` => (`T`, `T`) -> Bool => `Guarantee<[T]>`
|
||||
|
||||
Guarantee.value([5,2,3,4,1])
|
||||
.sortedValues { $0 > $1 }
|
||||
.done {
|
||||
// $0 => [5,4,3,2,1]
|
||||
}
|
||||
*/
|
||||
func sortedValues(on: DispatchQueue? = conf.Q.map, flags: DispatchWorkItemFlags? = nil, _ areInIncreasingOrder: @escaping(T.Iterator.Element, T.Iterator.Element) -> Bool) -> Guarantee<[T.Iterator.Element]> {
|
||||
return map(on: on, flags: flags) {
|
||||
$0.sorted(by: areInIncreasingOrder)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public extension Guarantee where T: Sequence, T.Iterator.Element: Comparable {
|
||||
/**
|
||||
`Guarantee<[T]>` => `Guarantee<[T]>`
|
||||
|
||||
Guarantee.value([5,2,3,4,1])
|
||||
.sortedValues()
|
||||
.done {
|
||||
// $0 => [1,2,3,4,5]
|
||||
}
|
||||
*/
|
||||
func sortedValues(on: DispatchQueue? = conf.Q.map, flags: DispatchWorkItemFlags? = nil) -> Guarantee<[T.Iterator.Element]> {
|
||||
return map(on: on, flags: flags) { $0.sorted() }
|
||||
}
|
||||
}
|
||||
|
||||
#if swift(>=3.1)
|
||||
public extension Guarantee where T == Void {
|
||||
convenience init() {
|
||||
self.init(box: SealedBox(value: Void()))
|
||||
}
|
||||
|
||||
static var value: Guarantee<Void> {
|
||||
return .value(Void())
|
||||
#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(())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
public extension DispatchQueue {
|
||||
/**
|
||||
Asynchronously executes the provided closure on a dispatch queue.
|
||||
Asynchronously executes the provided closure on a dispatch queue, yielding a `Guarantee`.
|
||||
|
||||
DispatchQueue.global().async(.promise) {
|
||||
md5(input)
|
||||
@ -363,20 +231,45 @@ public extension DispatchQueue {
|
||||
//…
|
||||
}
|
||||
|
||||
- Parameter body: The closure that resolves this promise.
|
||||
- _: 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.
|
||||
- Returns: A new `Guarantee` resolved by the result of the provided closure.
|
||||
- Note: There is no Promise/Thenable version of this due to Swift compiler ambiguity issues.
|
||||
*/
|
||||
@available(macOS 10.10, iOS 2.0, tvOS 10.0, watchOS 2.0, *)
|
||||
final func async<T>(_: PMKNamespacer, group: DispatchGroup? = nil, qos: DispatchQoS = .default, flags: DispatchWorkItemFlags = [], execute body: @escaping () -> T) -> Guarantee<T> {
|
||||
final func async<T>(_: PMKNamespacer, group: DispatchGroup? = nil, qos: DispatchQoS? = nil, flags: DispatchWorkItemFlags? = nil, execute body: @escaping () -> T) -> Guarantee<T> {
|
||||
let rg = Guarantee<T>(.pending)
|
||||
async(group: group, qos: qos, flags: flags) {
|
||||
asyncD(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
|
||||
|
||||
@ -1,28 +0,0 @@
|
||||
<?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>
|
||||
@ -27,4 +27,29 @@ 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)"
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,77 +0,0 @@
|
||||
#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;
|
||||
}
|
||||
@ -1,120 +0,0 @@
|
||||
#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 block’s 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 don’t catch objc exceptions: they are meant to crash your app
|
||||
@throw thrown;
|
||||
}
|
||||
}
|
||||
@ -6,9 +6,9 @@ import Dispatch
|
||||
- See: `Thenable`
|
||||
*/
|
||||
public final class Promise<T>: Thenable, CatchMixin {
|
||||
let box: Box<Result<T>>
|
||||
let box: Box<Result<T, Error>>
|
||||
|
||||
fileprivate init(box: SealedBox<Result<T>>) {
|
||||
fileprivate init(box: SealedBox<Result<T, Error>>) {
|
||||
self.box = box
|
||||
}
|
||||
|
||||
@ -38,13 +38,13 @@ public final class Promise<T>: Thenable, CatchMixin {
|
||||
return .value(bar)
|
||||
}
|
||||
*/
|
||||
public static func value(_ value: T) -> Promise<T> {
|
||||
return Promise(box: SealedBox(value: .fulfilled(value)))
|
||||
public class func value(_ value: T) -> Promise<T> {
|
||||
return Promise(box: SealedBox(value: .success(value)))
|
||||
}
|
||||
|
||||
/// Initialize a new rejected promise.
|
||||
public init(error: Error) {
|
||||
box = SealedBox(value: .rejected(error))
|
||||
box = SealedBox(value: .failure(error))
|
||||
}
|
||||
|
||||
/// Initialize a new promise bound to the provided `Thenable`.
|
||||
@ -64,13 +64,26 @@ 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>) -> Void) {
|
||||
public func pipe(to: @escaping(Result<T, Error>) -> Void) {
|
||||
switch box.inspect() {
|
||||
case .pending:
|
||||
box.inspect {
|
||||
@ -87,7 +100,7 @@ public final class Promise<T>: Thenable, CatchMixin {
|
||||
}
|
||||
|
||||
/// - See: `Thenable.result`
|
||||
public var result: Result<T>? {
|
||||
public var result: Result<T, Error>? {
|
||||
switch box.inspect() {
|
||||
case .pending:
|
||||
return nil
|
||||
@ -99,6 +112,14 @@ 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 {
|
||||
@ -109,7 +130,7 @@ public extension Promise {
|
||||
func wait() throws -> T {
|
||||
|
||||
if Thread.isMainThread {
|
||||
conf.logHandler(LogEvent.waitOnMainThread)
|
||||
conf.logHandler(.waitOnMainThread)
|
||||
}
|
||||
|
||||
var result = self.result
|
||||
@ -121,33 +142,20 @@ public extension Promise {
|
||||
group.wait()
|
||||
}
|
||||
|
||||
switch result! {
|
||||
case .rejected(let error):
|
||||
throw error
|
||||
case .fulfilled(let value):
|
||||
return value
|
||||
}
|
||||
return try result!.get()
|
||||
}
|
||||
}
|
||||
|
||||
#if swift(>=3.1)
|
||||
extension Promise where T == Void {
|
||||
/// Initializes a new promise fulfilled with `Void`
|
||||
public convenience init() {
|
||||
self.init(box: SealedBox(value: .fulfilled(Void())))
|
||||
}
|
||||
|
||||
/// Returns a new promise fulfilled with `Void`
|
||||
public static var value: Promise<Void> {
|
||||
return .value(Void())
|
||||
self.init(box: SealedBox(value: .success(Void())))
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
public extension DispatchQueue {
|
||||
/**
|
||||
Asynchronously executes the provided closure on a dispatch queue.
|
||||
Asynchronously executes the provided closure on a dispatch queue, yielding a `Promise`.
|
||||
|
||||
DispatchQueue.global().async(.promise) {
|
||||
try md5(input)
|
||||
@ -155,24 +163,54 @@ public extension DispatchQueue {
|
||||
//…
|
||||
}
|
||||
|
||||
- Parameter body: The closure that resolves this promise.
|
||||
- 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.
|
||||
- Returns: A new `Promise` resolved by the result of the provided closure.
|
||||
- Note: There is no Promise/Thenable version of this due to Swift compiler ambiguity issues.
|
||||
*/
|
||||
@available(macOS 10.10, iOS 8.0, tvOS 9.0, watchOS 2.0, *)
|
||||
final func async<T>(_: PMKNamespacer, group: DispatchGroup? = nil, qos: DispatchQoS = .default, flags: DispatchWorkItemFlags = [], execute body: @escaping () throws -> T) -> Promise<T> {
|
||||
final func async<T>(_: PMKNamespacer, group: DispatchGroup? = nil, qos: DispatchQoS? = nil, flags: DispatchWorkItemFlags? = nil, execute body: @escaping () throws -> T) -> Promise<T> {
|
||||
let promise = Promise<T>(.pending)
|
||||
async(group: group, qos: qos, flags: flags) {
|
||||
asyncD(group: group, qos: qos, flags: flags) {
|
||||
do {
|
||||
promise.box.seal(.fulfilled(try body()))
|
||||
promise.box.seal(.success(try body()))
|
||||
} catch {
|
||||
promise.box.seal(.rejected(error))
|
||||
promise.box.seal(.failure(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 {
|
||||
|
||||
@ -1,7 +0,0 @@
|
||||
#import <PromiseKit/fwd.h>
|
||||
#import <PromiseKit/AnyPromise.h>
|
||||
|
||||
#import <Foundation/NSObjCRuntime.h> // `FOUNDATION_EXPORT`
|
||||
|
||||
FOUNDATION_EXPORT double PromiseKitVersionNumber;
|
||||
FOUNDATION_EXPORT const unsigned char PromiseKitVersionString[];
|
||||
@ -1,8 +1,8 @@
|
||||
/// An object for resolving promises
|
||||
public final class Resolver<T> {
|
||||
let box: Box<Result<T>>
|
||||
let box: Box<Result<T, Error>>
|
||||
|
||||
init(_ box: Box<Result<T>>) {
|
||||
init(_ box: Box<Result<T, Error>>) {
|
||||
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(.fulfilled(value))
|
||||
box.seal(.success(value))
|
||||
}
|
||||
|
||||
/// Rejects the promise with the provided error
|
||||
func reject(_ error: Error) {
|
||||
box.seal(.rejected(error))
|
||||
box.seal(.failure(error))
|
||||
}
|
||||
|
||||
/// Resolves the promise with the provided result
|
||||
func resolve(_ result: Result<T>) {
|
||||
func resolve(_ result: Result<T, Error>) {
|
||||
box.seal(result)
|
||||
}
|
||||
|
||||
@ -55,7 +55,6 @@ 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?) {
|
||||
@ -65,35 +64,10 @@ 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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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>) -> Void)
|
||||
func pipe(to: @escaping(Result<T, Error>) -> Void)
|
||||
|
||||
/// The resolved result or nil if pending.
|
||||
var result: Result<T>? { get }
|
||||
var result: Result<T, Error>? { get }
|
||||
}
|
||||
|
||||
public extension Thenable {
|
||||
/**
|
||||
The provided closure executes when this promise is fulfilled.
|
||||
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.
|
||||
|
||||
- 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.
|
||||
- Parameter on: The dispatcher that executes the provided closure.
|
||||
- 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. For example:
|
||||
|
||||
firstly {
|
||||
@ -30,35 +30,35 @@ public extension Thenable {
|
||||
//…
|
||||
}
|
||||
*/
|
||||
func then<U: Thenable>(on: DispatchQueue? = conf.Q.map, flags: DispatchWorkItemFlags? = nil, _ body: @escaping(T) throws -> U) -> Promise<U.T> {
|
||||
func then<U: Thenable>(on: Dispatcher = conf.D.map, _ body: @escaping(T) throws -> U) -> Promise<U.T> {
|
||||
let rp = Promise<U.T>(.pending)
|
||||
pipe {
|
||||
switch $0 {
|
||||
case .fulfilled(let value):
|
||||
on.async(flags: flags) {
|
||||
case .success(let value):
|
||||
on.dispatch {
|
||||
do {
|
||||
let rv = try body(value)
|
||||
guard rv !== rp else { throw PMKError.returnedSelf }
|
||||
rv.pipe(to: rp.box.seal)
|
||||
} catch {
|
||||
rp.box.seal(.rejected(error))
|
||||
rp.box.seal(.failure(error))
|
||||
}
|
||||
}
|
||||
case .rejected(let error):
|
||||
rp.box.seal(.rejected(error))
|
||||
case .failure(let error):
|
||||
rp.box.seal(.failure(error))
|
||||
}
|
||||
}
|
||||
return rp
|
||||
}
|
||||
|
||||
/**
|
||||
The provided closure is executed when this promise is fulfilled.
|
||||
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 on: The dispatcher that executes the provided closure.
|
||||
- Parameter transform: The closure that is executed when this Promise is fulfilled. It must return a non-promise.
|
||||
- Returns: A new promise that is fulfilled with the value returned from the provided closure or rejected if the provided closure throws. For example:
|
||||
- Returns: A new promise that is resolved with the value returned from the provided closure. For example:
|
||||
|
||||
firstly {
|
||||
URLSession.shared.dataTask(.promise, with: url1)
|
||||
@ -68,51 +68,27 @@ public extension Thenable {
|
||||
//…
|
||||
}
|
||||
*/
|
||||
func map<U>(on: DispatchQueue? = conf.Q.map, flags: DispatchWorkItemFlags? = nil, _ transform: @escaping(T) throws -> U) -> Promise<U> {
|
||||
func map<U>(on: Dispatcher = conf.D.map, _ transform: @escaping(T) throws -> U) -> Promise<U> {
|
||||
let rp = Promise<U>(.pending)
|
||||
pipe {
|
||||
switch $0 {
|
||||
case .fulfilled(let value):
|
||||
on.async(flags: flags) {
|
||||
case .success(let value):
|
||||
on.dispatch {
|
||||
do {
|
||||
rp.box.seal(.fulfilled(try transform(value)))
|
||||
rp.box.seal(.success(try transform(value)))
|
||||
} catch {
|
||||
rp.box.seal(.rejected(error))
|
||||
rp.box.seal(.failure(error))
|
||||
}
|
||||
}
|
||||
case .rejected(let error):
|
||||
rp.box.seal(.rejected(error))
|
||||
case .failure(let error):
|
||||
rp.box.seal(.failure(error))
|
||||
}
|
||||
}
|
||||
return rp
|
||||
}
|
||||
|
||||
#if swift(>=4) && !swift(>=5.2)
|
||||
/**
|
||||
Similar to func `map<U>(on: DispatchQueue? = conf.Q.map, flags: DispatchWorkItemFlags? = nil, _ transform: @escaping(T) throws -> U) -> Promise<U>`, but accepts a key path instead of a closure.
|
||||
|
||||
- Parameter on: The queue to which the provided key path for value dispatches.
|
||||
- Parameter keyPath: The key path to the value that is using when this Promise is fulfilled.
|
||||
- Returns: A new promise that is fulfilled with the value for the provided key path.
|
||||
*/
|
||||
func map<U>(on: DispatchQueue? = conf.Q.map, flags: DispatchWorkItemFlags? = nil, _ keyPath: KeyPath<T, U>) -> Promise<U> {
|
||||
let rp = Promise<U>(.pending)
|
||||
pipe {
|
||||
switch $0 {
|
||||
case .fulfilled(let value):
|
||||
on.async(flags: flags) {
|
||||
rp.box.seal(.fulfilled(value[keyPath: keyPath]))
|
||||
}
|
||||
case .rejected(let error):
|
||||
rp.box.seal(.rejected(error))
|
||||
}
|
||||
}
|
||||
return rp
|
||||
}
|
||||
#endif
|
||||
|
||||
/**
|
||||
The provided closure is executed when this promise is fulfilled.
|
||||
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.
|
||||
|
||||
@ -126,70 +102,38 @@ public extension Thenable {
|
||||
// either `PMKError.compactMap` or a `JSONError`
|
||||
}
|
||||
*/
|
||||
func compactMap<U>(on: DispatchQueue? = conf.Q.map, flags: DispatchWorkItemFlags? = nil, _ transform: @escaping(T) throws -> U?) -> Promise<U> {
|
||||
func compactMap<U>(on: Dispatcher = conf.D.map, _ transform: @escaping(T) throws -> U?) -> Promise<U> {
|
||||
let rp = Promise<U>(.pending)
|
||||
pipe {
|
||||
switch $0 {
|
||||
case .fulfilled(let value):
|
||||
on.async(flags: flags) {
|
||||
case .success(let value):
|
||||
on.dispatch {
|
||||
do {
|
||||
if let rv = try transform(value) {
|
||||
rp.box.seal(.fulfilled(rv))
|
||||
rp.box.seal(.success(rv))
|
||||
} else {
|
||||
throw PMKError.compactMap(value, U.self)
|
||||
}
|
||||
} catch {
|
||||
rp.box.seal(.rejected(error))
|
||||
rp.box.seal(.failure(error))
|
||||
}
|
||||
}
|
||||
case .rejected(let error):
|
||||
rp.box.seal(.rejected(error))
|
||||
case .failure(let error):
|
||||
rp.box.seal(.failure(error))
|
||||
}
|
||||
}
|
||||
return rp
|
||||
}
|
||||
|
||||
#if swift(>=4) && !swift(>=5.2)
|
||||
/**
|
||||
Similar to func `compactMap<U>(on: DispatchQueue? = conf.Q.map, flags: DispatchWorkItemFlags? = nil, _ transform: @escaping(T) throws -> U?) -> Promise<U>`, but accepts a key path instead of a closure.
|
||||
|
||||
- Parameter on: The queue to which the provided key path for value dispatches.
|
||||
- Parameter keyPath: The key path to the value that is using when this Promise is fulfilled. If the value for `keyPath` is `nil` the resulting promise is rejected with `PMKError.compactMap`.
|
||||
- Returns: A new promise that is fulfilled with the value for the provided key path.
|
||||
*/
|
||||
func compactMap<U>(on: DispatchQueue? = conf.Q.map, flags: DispatchWorkItemFlags? = nil, _ keyPath: KeyPath<T, U?>) -> Promise<U> {
|
||||
let rp = Promise<U>(.pending)
|
||||
pipe {
|
||||
switch $0 {
|
||||
case .fulfilled(let value):
|
||||
on.async(flags: flags) {
|
||||
do {
|
||||
if let rv = value[keyPath: keyPath] {
|
||||
rp.box.seal(.fulfilled(rv))
|
||||
} else {
|
||||
throw PMKError.compactMap(value, U.self)
|
||||
}
|
||||
} catch {
|
||||
rp.box.seal(.rejected(error))
|
||||
}
|
||||
}
|
||||
case .rejected(let error):
|
||||
rp.box.seal(.rejected(error))
|
||||
}
|
||||
}
|
||||
return rp
|
||||
}
|
||||
#endif
|
||||
|
||||
/**
|
||||
The provided closure is executed when this promise is fulfilled.
|
||||
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 closure’s qualification.
|
||||
|
||||
- Parameter on: The queue to which the provided closure dispatches.
|
||||
- Parameter on: The dispatcher that executes the provided closure.
|
||||
- Parameter body: The closure that is executed when this Promise is fulfilled.
|
||||
- Returns: A new promise fulfilled as `Void` or rejected if the provided closure throws.
|
||||
- Returns: A new promise fulfilled as `Void`.
|
||||
|
||||
firstly {
|
||||
URLSession.shared.dataTask(.promise, with: url)
|
||||
@ -197,35 +141,35 @@ public extension Thenable {
|
||||
print(response.data)
|
||||
}
|
||||
*/
|
||||
func done(on: DispatchQueue? = conf.Q.return, flags: DispatchWorkItemFlags? = nil, _ body: @escaping(T) throws -> Void) -> Promise<Void> {
|
||||
func done(on: Dispatcher = conf.D.return, _ body: @escaping(T) throws -> Void) -> Promise<Void> {
|
||||
let rp = Promise<Void>(.pending)
|
||||
pipe {
|
||||
switch $0 {
|
||||
case .fulfilled(let value):
|
||||
on.async(flags: flags) {
|
||||
case .success(let value):
|
||||
on.dispatch {
|
||||
do {
|
||||
try body(value)
|
||||
rp.box.seal(.fulfilled(()))
|
||||
rp.box.seal(.success(()))
|
||||
} catch {
|
||||
rp.box.seal(.rejected(error))
|
||||
rp.box.seal(.failure(error))
|
||||
}
|
||||
}
|
||||
case .rejected(let error):
|
||||
rp.box.seal(.rejected(error))
|
||||
case .failure(let error):
|
||||
rp.box.seal(.failure(error))
|
||||
}
|
||||
}
|
||||
return rp
|
||||
}
|
||||
|
||||
/**
|
||||
The provided closure is executed when this promise is fulfilled.
|
||||
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.
|
||||
|
||||
- Parameter on: The queue to which the provided closure dispatches.
|
||||
- Parameter on: The dispatcher that executes the provided closure.
|
||||
- Parameter body: The closure that is executed when this Promise is fulfilled.
|
||||
- Returns: A new promise that is fulfilled with the value that the handler is fed or rejected if the provided closure throws. For example:
|
||||
- Returns: A new promise that is resolved with the value that the handler is fed. For example:
|
||||
|
||||
firstly {
|
||||
.value(1)
|
||||
@ -237,8 +181,8 @@ public extension Thenable {
|
||||
print(foo, " is Void")
|
||||
}
|
||||
*/
|
||||
func get(on: DispatchQueue? = conf.Q.return, flags: DispatchWorkItemFlags? = nil, _ body: @escaping (T) throws -> Void) -> Promise<T> {
|
||||
return map(on: on, flags: flags) {
|
||||
func get(on: Dispatcher = conf.D.return, _ body: @escaping (T) throws -> Void) -> Promise<T> {
|
||||
return map(on: on) {
|
||||
try body($0)
|
||||
return $0
|
||||
}
|
||||
@ -249,16 +193,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 queue to which the provided closure dispatches.
|
||||
- Parameter on: The dispatcher that executes the provided closure.
|
||||
- Parameter body: The closure that is executed with Result of Promise.
|
||||
- Returns: A new promise that is resolved with the result that the handler is fed. For example:
|
||||
|
||||
promise.tap{ print($0) }.then{ /*…*/ }
|
||||
*/
|
||||
func tap(on: DispatchQueue? = conf.Q.map, flags: DispatchWorkItemFlags? = nil, _ body: @escaping(Result<T>) -> Void) -> Promise<T> {
|
||||
func tap(on: Dispatcher = conf.D.map, _ body: @escaping(Result<T, Error>) -> Void) -> Promise<T> {
|
||||
return Promise { seal in
|
||||
pipe { result in
|
||||
on.async(flags: flags) {
|
||||
on.dispatch {
|
||||
body(result)
|
||||
seal.resolve(result)
|
||||
}
|
||||
@ -280,9 +224,9 @@ public extension Thenable {
|
||||
switch result {
|
||||
case .none:
|
||||
return nil
|
||||
case .some(.fulfilled):
|
||||
case .some(.success):
|
||||
return nil
|
||||
case .some(.rejected(let error)):
|
||||
case .some(.failure(let error)):
|
||||
return error
|
||||
}
|
||||
}
|
||||
@ -322,14 +266,26 @@ public extension Thenable {
|
||||
switch result {
|
||||
case .none:
|
||||
return nil
|
||||
case .some(.fulfilled(let value)):
|
||||
case .some(.success(let value)):
|
||||
return value
|
||||
case .some(.rejected):
|
||||
case .some(.failure):
|
||||
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]>`
|
||||
@ -342,25 +298,10 @@ public extension Thenable where T: Sequence {
|
||||
// $0 => [2,4,6]
|
||||
}
|
||||
*/
|
||||
func mapValues<U>(on: DispatchQueue? = conf.Q.map, flags: DispatchWorkItemFlags? = nil, _ transform: @escaping(T.Iterator.Element) throws -> U) -> Promise<[U]> {
|
||||
return map(on: on, flags: flags){ try $0.map(transform) }
|
||||
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) }
|
||||
}
|
||||
|
||||
#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]>`
|
||||
|
||||
@ -372,8 +313,8 @@ public extension Thenable where T: Sequence {
|
||||
// $0 => [1,1,2,2,3,3]
|
||||
}
|
||||
*/
|
||||
func flatMapValues<U: Sequence>(on: DispatchQueue? = conf.Q.map, flags: DispatchWorkItemFlags? = nil, _ transform: @escaping(T.Iterator.Element) throws -> U) -> Promise<[U.Iterator.Element]> {
|
||||
return map(on: on, flags: flags){ (foo: T) in
|
||||
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
|
||||
try foo.flatMap{ try transform($0) }
|
||||
}
|
||||
}
|
||||
@ -389,37 +330,13 @@ public extension Thenable where T: Sequence {
|
||||
// $0 => [1,2,3]
|
||||
}
|
||||
*/
|
||||
func compactMapValues<U>(on: DispatchQueue? = conf.Q.map, flags: DispatchWorkItemFlags? = nil, _ transform: @escaping(T.Iterator.Element) throws -> U?) -> Promise<[U]> {
|
||||
return map(on: on, flags: flags) { foo -> [U] in
|
||||
#if !swift(>=3.3) || (swift(>=4) && !swift(>=4.1))
|
||||
return try foo.flatMap(transform)
|
||||
#else
|
||||
|
||||
func compactMapValues<U>(on: Dispatcher = conf.D.map, _ transform: @escaping(T.Iterator.Element) throws -> U?) -> Promise<[U]> {
|
||||
return map(on: on) { foo -> [U] in
|
||||
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]>`
|
||||
|
||||
@ -431,8 +348,8 @@ public extension Thenable where T: Sequence {
|
||||
// $0 => [2,4,6]
|
||||
}
|
||||
*/
|
||||
func thenMap<U: Thenable>(on: DispatchQueue? = conf.Q.map, flags: DispatchWorkItemFlags? = nil, _ transform: @escaping(T.Iterator.Element) throws -> U) -> Promise<[U.T]> {
|
||||
return then(on: on, flags: flags) {
|
||||
func thenMap<U: Thenable>(on: Dispatcher = conf.D.map, _ transform: @escaping(T.Iterator.Element) throws -> U) -> Promise<[U.T]> {
|
||||
return then(on: on) {
|
||||
when(fulfilled: try $0.map(transform))
|
||||
}
|
||||
}
|
||||
@ -448,8 +365,8 @@ public extension Thenable where T: Sequence {
|
||||
// $0 => [1,1,2,2,3,3]
|
||||
}
|
||||
*/
|
||||
func thenFlatMap<U: Thenable>(on: DispatchQueue? = conf.Q.map, flags: DispatchWorkItemFlags? = nil, _ transform: @escaping(T.Iterator.Element) throws -> U) -> Promise<[U.T.Iterator.Element]> where U.T: Sequence {
|
||||
return then(on: on, flags: flags) {
|
||||
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) {
|
||||
when(fulfilled: try $0.map(transform))
|
||||
}.map(on: nil) {
|
||||
$0.flatMap{ $0 }
|
||||
@ -457,7 +374,7 @@ public extension Thenable where T: Sequence {
|
||||
}
|
||||
|
||||
/**
|
||||
`Promise<[T]>` => `T` -> Bool => `Promise<[T]>`
|
||||
`Promise<[T]>` => `T` -> Bool => `Promise<[U]>`
|
||||
|
||||
firstly {
|
||||
.value([1,2,3])
|
||||
@ -467,28 +384,11 @@ public extension Thenable where T: Sequence {
|
||||
// $0 => [2,3]
|
||||
}
|
||||
*/
|
||||
func filterValues(on: DispatchQueue? = conf.Q.map, flags: DispatchWorkItemFlags? = nil, _ isIncluded: @escaping (T.Iterator.Element) -> Bool) -> Promise<[T.Iterator.Element]> {
|
||||
return map(on: on, flags: flags) {
|
||||
func filterValues(on: Dispatcher = conf.D.map, _ isIncluded: @escaping (T.Iterator.Element) -> Bool) -> Promise<[T.Iterator.Element]> {
|
||||
return map(on: on) {
|
||||
$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 {
|
||||
@ -503,8 +403,8 @@ public extension Thenable where T: Collection {
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
func firstValue(on: Dispatcher = conf.D.map, where test: @escaping (T.Iterator.Element) -> Bool) -> Promise<T.Iterator.Element> {
|
||||
return map(on: on) {
|
||||
for x in $0 where test(x) {
|
||||
return x
|
||||
}
|
||||
@ -527,7 +427,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: DispatchQueue? = conf.Q.map, flags: DispatchWorkItemFlags? = nil) -> Promise<[T.Iterator.Element]> {
|
||||
return map(on: on, flags: flags){ $0.sorted() }
|
||||
func sortedValues(on: Dispatcher = conf.D.map) -> Promise<[T.Iterator.Element]> {
|
||||
return map(on: on){ $0.sorted() }
|
||||
}
|
||||
}
|
||||
|
||||
73
Sources/Wrappers/CatchWrappers.swift
Normal file
73
Sources/Wrappers/CatchWrappers.swift
Normal file
@ -0,0 +1,73 @@
|
||||
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)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
51
Sources/Wrappers/EnsureWrappers.swift
Normal file
51
Sources/Wrappers/EnsureWrappers.swift
Normal file
@ -0,0 +1,51 @@
|
||||
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 promise’s 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 promise’s 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)
|
||||
}
|
||||
}
|
||||
11
Sources/Wrappers/FinallyWrappers.swift
Normal file
11
Sources/Wrappers/FinallyWrappers.swift
Normal file
@ -0,0 +1,11 @@
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
39
Sources/Wrappers/GuaranteeWrappers.swift
Normal file
39
Sources/Wrappers/GuaranteeWrappers.swift
Normal file
@ -0,0 +1,39 @@
|
||||
// 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)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
275
Sources/Wrappers/RecoverWrappers.swift
Normal file
275
Sources/Wrappers/RecoverWrappers.swift
Normal file
@ -0,0 +1,275 @@
|
||||
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)
|
||||
}
|
||||
}
|
||||
261
Sources/Wrappers/SequenceWrappers.swift
Normal file
261
Sources/Wrappers/SequenceWrappers.swift
Normal file
@ -0,0 +1,261 @@
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
240
Sources/Wrappers/ThenableWrappers.swift
Normal file
240
Sources/Wrappers/ThenableWrappers.swift
Normal file
@ -0,0 +1,240 @@
|
||||
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 closure’s 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)
|
||||
}
|
||||
}
|
||||
|
||||
82
Sources/Wrappers/WrapperProtocols.swift
Normal file
82
Sources/Wrappers/WrapperProtocols.swift
Normal file
@ -0,0 +1,82 @@
|
||||
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 {}
|
||||
@ -1,14 +0,0 @@
|
||||
#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));
|
||||
});
|
||||
}];
|
||||
}
|
||||
@ -1,21 +1,25 @@
|
||||
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
|
||||
#if swift(>=4.0)
|
||||
q.asyncAfter(deadline: when) { seal(()) }
|
||||
#else
|
||||
q.asyncAfter(deadline: when, execute: seal)
|
||||
#endif
|
||||
let task = DispatchWorkItem { seal(()) }
|
||||
rg.setCancellable(task)
|
||||
q.asyncAfter(deadline: when, execute: task)
|
||||
return rg
|
||||
}
|
||||
|
||||
@ -25,15 +29,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
|
||||
#if swift(>=4.0)
|
||||
q.asyncAfter(deadline: when) { seal(()) }
|
||||
#else
|
||||
q.asyncAfter(deadline: when, execute: seal)
|
||||
#endif
|
||||
let task = DispatchWorkItem { seal(()) }
|
||||
rg.setCancellable(task)
|
||||
q.asyncAfter(deadline: when, execute: task)
|
||||
return rg
|
||||
}
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user