Compare commits

...

No commits in common. "fix-NSError.isCancelled" and "gh-pages" have entirely different histories.

371 changed files with 67980 additions and 23126 deletions

3
.bundle/config Normal file
View File

@ -0,0 +1,3 @@
---
BUNDLE_PATH: gems
BUNDLE_DISABLE_SHARED_GEMS: true

2
.github/FUNDING.yml vendored
View File

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

View File

@ -1,11 +0,0 @@
[PLEASE READ THE TROUBLESHOOTING GUIDE](https://github.com/mxcl/PromiseKit/blob/master/Documentation/Troubleshooting.md).
---
You read the guide but it didnt help? OK, were here to help.
* Please specify the PromiseKit major version you are using
* [Please format your code in triple backticks and ensure readable indentation](https://help.github.com/articles/creating-and-highlighting-code-blocks/)
* Please specify how you installed PromiseKit, ie. Carthage, CocoaPods, SwiftPM or other. If other provide DETAILED information about how you are integrating PromiseKit.
If you ignore this template we will close your ticket and link to this template until you provide this necessary information. We cannot help you without it.

View File

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

View File

@ -1,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

29
.github/codecov.yml vendored
View File

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

18
.github/jazzy.yml vendored
View File

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

2
.github/ranger.yml vendored
View File

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

17
.github/release vendored
View File

@ -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
View File

@ -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

10
.gitignore vendored
View File

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

69
.gitmodules vendored
View File

@ -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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,131 +0,0 @@
# Image Cache with Promises
Here is an example of a simple image cache that uses promises to simplify the
state machine:
```swift
import Foundation
import PromiseKit
/**
* Small (10 images)
* Thread-safe
* Consolidates multiple requests to the same URLs
* Removes stale entries (FIXME well, strictly we may delete while fetching from cache, but this is unlikely and non-fatal)
* Completely _ignores_ server caching headers!
*/
private let q = DispatchQueue(label: "org.promisekit.cache.image", attributes: .concurrent)
private var active: [URL: Promise<Data>] = [:]
private var cleanup = Promise()
public func fetch(image url: URL) -> Promise<Data> {
var promise: Promise<Data>?
q.sync {
promise = active[url]
}
if let promise = promise {
return promise
}
q.sync {
promise = Promise(.start) {
let dst = try url.cacheDestination()
guard !FileManager.default.isReadableFile(atPath: dst.path) else {
return Promise(dst)
}
return Promise { seal in
URLSession.shared.downloadTask(with: url) { tmpurl, _, error in
do {
guard let tmpurl = tmpurl else { throw error ?? E.unexpectedError }
try FileManager.default.moveItem(at: tmpurl, to: dst)
seal.fulfill(dst)
} catch {
seal.reject(error)
}
}.resume()
}
}.then(on: .global(QoS: .userInitiated)) {
try Data(contentsOf: $0)
}
active[url] = promise
if cleanup.isFulfilled {
cleanup = promise!.asVoid().then(on: .global(QoS: .utility), execute: docleanup)
}
}
return promise!
}
public func cached(image url: URL) -> Data? {
guard let dst = try? url.cacheDestination() else {
return nil
}
return try? Data(contentsOf: dst)
}
public func cache(destination remoteUrl: URL) throws -> URL {
return try remoteUrl.cacheDestination()
}
private func cache() throws -> URL {
guard let dst = FileManager.default.docs?
.appendingPathComponent("Library")
.appendingPathComponent("Caches")
.appendingPathComponent("cache.img")
else {
throw E.unexpectedError
}
try FileManager.default.createDirectory(at: dst, withIntermediateDirectories: true, attributes: [:])
return dst
}
private extension URL {
func cacheDestination() throws -> URL {
var fn = String(hashValue)
let ext = pathExtension
// many of Apple's functions dont recognize file type
// unless we preserve the file extension
if !ext.isEmpty {
fn += ".\(ext)"
}
return try cache().appendingPathComponent(fn)
}
}
enum E: Error {
case unexpectedError
case noCreationTime
}
private func docleanup() throws {
var contents = try FileManager.default
.contentsOfDirectory(at: try cache(), includingPropertiesForKeys: [.creationDateKey])
.map { url -> (Date, URL) in
guard let date = try url.resourceValues(forKeys: [.creationDateKey]).creationDate else {
throw E.noCreationTime
}
return (date, url)
}.sorted(by: {
$0.0 > $1.0
})
while contents.count > 10 {
let rm = contents.popLast()!.1
try FileManager.default.removeItem(at: rm)
}
}
````

View File

@ -1,20 +0,0 @@
Promise(.pending) { seal in
URLSession.shared.dataTask(with: rq, completionHandler: { data, rsp, error in
if let data = data {
seal.fulfill(data)
} else if let error = error {
if case URLError.badServerResponse = error, let rsp = rsp as? HTTPURLResponse {
seal.reject(Error.badResponse(rsp.statusCode))
} else {
seal.reject(error)
}
} else {
seal.reject(PMKError.invalidCallingConvention)
}
})
}
enum Error: Swift.Error {
case badUrl
case badResponse(Int)
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,14 +0,0 @@
# Contents
* [README](../README.md)
* Handbook
* [Getting Started](GettingStarted.md)
* [Promises: Common Patterns](CommonPatterns.md)
* [Frequently Asked Questions](FAQ.md)
* Manual
* [Installation Guide](Installation.md)
* [Objective-C Guide](ObjectiveC.md)
* [Troubleshooting](Troubleshooting.md)
* [Appendix](Appendix.md)
* [Examples](Examples)
* [API Reference](https://mxcl.dev/PromiseKit/reference/v6/Classes/Promise.html)

View File

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

@ -1 +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

20
LICENSE
View File

@ -1,20 +0,0 @@
Copyright 2016-present, Max Howell; mxcl@me.com
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be included
in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@ -1,30 +0,0 @@
// swift-tools-version:4.0
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 = [3, 4]
pkg.targets = [
pmk,
.testTarget(name: "A+", dependencies: ["PromiseKit"]),
.testTarget(name: "CorePromise", dependencies: ["PromiseKit"], path: "Tests/CorePromise"),
]

View File

@ -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"),
]

View File

@ -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"),
]

View File

@ -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

View File

@ -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>

View File

@ -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

View File

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

View File

@ -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>

View File

@ -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>

View File

@ -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>

211
README.md
View File

@ -1,211 +0,0 @@
![PromiseKit](../gh-pages/public/img/logo-tight.png)
[![badge-pod][]][cocoapods] ![badge-languages][] ![badge-pms][] ![badge-platforms][] [![badge-travis][]][travis]
---
Promises simplify asynchronous programming, freeing you up to focus on the more
important things. They are easy to learn, easy to master and result in clearer,
more readable code. Your co-workers will thank you.
```swift
UIApplication.shared.isNetworkActivityIndicatorVisible = true
let fetchImage = URLSession.shared.dataTask(.promise, with: url).compactMap{ UIImage(data: $0.data) }
let fetchLocation = CLLocationManager.requestLocation().lastValue
firstly {
when(fulfilled: fetchImage, fetchLocation)
}.done { image, location in
self.imageView.image = image
self.label.text = "\(location)"
}.ensure {
UIApplication.shared.isNetworkActivityIndicatorVisible = false
}.catch { error in
self.show(UIAlertController(for: error), sender: self)
}
```
PromiseKit is a thoughtful and complete implementation of promises for any
platform that has a `swiftc`. It has *excellent* Objective-C bridging and
*delightful* specializations for iOS, macOS, tvOS and watchOS. It is a top-100
pod used in many of the most popular apps in the world.
[![codecov](https://codecov.io/gh/mxcl/PromiseKit/branch/master/graph/badge.svg)](https://codecov.io/gh/mxcl/PromiseKit)
# PromiseKit 7 Alpha
We are testing PromiseKit 7 alpha, it is Swift 5 only. It is tagged and thus
importable in all package managers.
# PromiseKit 6
[Release notes and migration guide][PMK6].
# Quick Start
In your [Podfile]:
```ruby
use_frameworks!
target "Change Me!" do
pod "PromiseKit", "~> 6.8"
end
```
> The above gives an Xcode warning? See our [Installation Guide].
PromiseKit 6, 5 and 4 support Xcode 8.3, 9.x and 10.0; Swift 3.1,
3.2, 3.3, 3.4, 4.0, 4.1, 4.2, 4.3 and 5.0 (development snapshots); iOS, macOS,
tvOS, watchOS, Linux and Android; CocoaPods, Carthage and SwiftPM;
([CI Matrix](https://travis-ci.org/mxcl/PromiseKit)).
For Carthage, SwiftPM, Accio, etc., or for instructions when using older Swifts or Xcodes, see our [Installation Guide]. We recommend
[Carthage](https://github.com/Carthage/Carthage) or
[Accio](https://github.com/JamitLabs/Accio).
# Professionally Supported PromiseKit is Now Available
TideLift gives software development teams a single source for purchasing
and maintaining their software, with professional grade assurances from
the experts who know it best, while seamlessly integrating with existing
tools.
[Get Professional Support for PromiseKit with TideLift](https://tidelift.com/subscription/pkg/cocoapods-promisekit?utm_source=cocoapods-promisekit&utm_medium=referral&utm_campaign=readme).
# PromiseKit is Thousands of Hours of Work
Hey there, Im Max Howell. Im a prolific producer of open source software and
probably you already use some of it (I created [`brew`]). I work full-time on
open source and its hard; currently *I earn less than minimum wage*. Please
help me continue my work, I appreciate it 🙏🏻
<a href="https://www.patreon.com/mxcl">
<img src="https://c5.patreon.com/external/logo/become_a_patron_button@2x.png" width="160">
</a>
[Other ways to say thanks](http://mxcl.dev/#donate).
[`brew`]: https://brew.sh
# Documentation
* Handbook
* [Getting Started](Documentation/GettingStarted.md)
* [Promises: Common Patterns](Documentation/CommonPatterns.md)
* [Frequently Asked Questions](Documentation/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)
# Extensions
Promises are only as useful as the asynchronous tasks they represent. Thus, we
have converted (almost) all of Apples APIs to promises. The default CocoaPod
provides Promises and the extensions for Foundation and UIKit. The other
extensions are available by specifying additional subspecs in your `Podfile`,
e.g.:
```ruby
pod "PromiseKit/MapKit" # MKDirections().calculate().then { /*…*/ }
pod "PromiseKit/CoreLocation" # CLLocationManager.requestLocation().then { /*…*/ }
```
All our extensions are separate repositories at the [PromiseKit organization].
## I don't want the extensions!
Then dont have them:
```ruby
pod "PromiseKit/CorePromise", "~> 6.8"
```
> *Note:* Carthage installations come with no extensions by default.
## Choose Your Networking Library
Promise chains commonly start with a network operation. Thus, we offer
extensions for `URLSession`:
```swift
// pod 'PromiseKit/Foundation' # https://github.com/PromiseKit/Foundation
firstly {
URLSession.shared.dataTask(.promise, with: try makeUrlRequest()).validate()
// ^^ we provide `.validate()` so that eg. 404s get converted to errors
}.map {
try JSONDecoder().decode(Foo.self, with: $0.data)
}.done { foo in
//…
}.catch { error in
//…
}
func makeUrlRequest() throws -> URLRequest {
var rq = URLRequest(url: url)
rq.httpMethod = "POST"
rq.addValue("application/json", forHTTPHeaderField: "Content-Type")
rq.addValue("application/json", forHTTPHeaderField: "Accept")
rq.httpBody = try JSONEncoder().encode(obj)
return rq
}
```
And [Alamofire]:
```swift
// pod 'PromiseKit/Alamofire' # https://github.com/PromiseKit/Alamofire-
firstly {
Alamofire
.request("http://example.com", method: .post, parameters: params)
.responseDecodable(Foo.self)
}.done { foo in
//…
}.catch { error in
//…
}
```
Nowadays, considering that:
* We almost always POST JSON
* We now have `JSONDecoder`
* PromiseKit now has `map` and other functional primitives
* PromiseKit (like Alamofire, but not raw-`URLSession`) also defaults to having
callbacks go to the main thread
We recommend vanilla `URLSession`. It uses fewer black boxes and sticks closer to the metal. Alamofire was essential until the three bullet points above
became true, but nowadays it isnt really necessary.
# Support
Please check our [Troubleshooting Guide](Documentation/Troubleshooting.md), and
if after that you still have a question, ask at our [Gitter chat channel] or on [our bug tracker].
## Security & Vulnerability Reporting or Disclosure
https://tidelift.com/security
[badge-pod]: https://img.shields.io/cocoapods/v/PromiseKit.svg?label=version
[badge-pms]: https://img.shields.io/badge/supports-CocoaPods%20%7C%20Carthage%20%7C%20Accio%20%7C%20SwiftPM-green.svg
[badge-languages]: https://img.shields.io/badge/languages-Swift%20%7C%20ObjC-orange.svg
[badge-platforms]: https://img.shields.io/badge/platforms-macOS%20%7C%20iOS%20%7C%20watchOS%20%7C%20tvOS%20%7C%20Linux-lightgrey.svg
[badge-mit]: https://img.shields.io/badge/license-MIT-blue.svg
[OMGHTTPURLRQ]: https://github.com/PromiseKit/OMGHTTPURLRQ
[Alamofire]: http://github.com/PromiseKit/Alamofire-
[PromiseKit organization]: https://github.com/PromiseKit
[Gitter chat channel]: https://gitter.im/mxcl/PromiseKit
[our bug tracker]: https://github.com/mxcl/PromiseKit/issues/new
[Podfile]: https://guides.cocoapods.org/syntax/podfile.html
[PMK6]: http://mxcl.dev/PromiseKit/news/2018/02/PromiseKit-6.0-Released/
[Installation Guide]: Documentation/Installation.md
[badge-travis]: https://travis-ci.org/mxcl/PromiseKit.svg?branch=master
[travis]: https://travis-ci.org/mxcl/PromiseKit
[cocoapods]: https://cocoapods.org/pods/PromiseKit

View File

@ -1,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

View File

@ -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.
Dont use this method if you already have promises! Instead, just return your promise.
Should you need to fulfill a promise but have no sensical value to use: your promise is a `void` promise: fulfill with `nil`.
The block you pass is executed immediately on the calling thread.
- Parameter block: The provided block is immediately executed, inside the block call `resolve` to resolve this promise and cause any attached handlers to execute. If you are wrapping a delegate-based system, we recommend instead to use: initWithResolver:
- Returns: A new promise.
- Warning: Resolving a promise with `nil` fulfills it.
- SeeAlso: https://github.com/mxcl/PromiseKit/blob/master/Documentation/GettingStarted.md#making-promises
- SeeAlso: https://github.com/mxcl/PromiseKit/blob/master/Documentation/CommonPatterns.md#wrapping-delegate-systems
*/
+ (instancetype __nonnull)promiseWithResolverBlock:(void (^ __nonnull)(__nonnull PMKResolver))resolveBlock NS_REFINED_FOR_SWIFT;
/// INTERNAL DO NOT USE
- (instancetype __nonnull)initWith__D:(__AnyPromise * __nonnull)d;
/**
Creates a resolved promise.
When developing your own promise systems, it is occasionally useful to be able to return an already resolved promise.
- Parameter value: The value with which to resolve this promise. Passing an `NSError` will cause the promise to be rejected, passing an AnyPromise will return a new AnyPromise bound to that promise, otherwise the promise will be fulfilled with the value passed.
- Returns: A resolved promise.
*/
+ (instancetype __nonnull)promiseWithValue:(__nullable id)value NS_REFINED_FOR_SWIFT;
/**
The value of the asynchronous task this promise represents.
A promise has `nil` value if the asynchronous task it represents has not finished. If the value is `nil` the promise is still `pending`.
- Warning: *Note* Our Swift variants value property returns nil if the promise is rejected where AnyPromise will return the error object. This fits with the pattern where AnyPromise is not strictly typed and is more dynamic, but you should be aware of the distinction.
- Note: If the AnyPromise was fulfilled with a `PMKManifold`, returns only the first fulfillment object.
- Returns: The value with which this promise was resolved or `nil` if this promise is pending.
*/
@property (nonatomic, readonly) __nullable id value NS_REFINED_FOR_SWIFT;
/// - Returns: if the promise is pending resolution.
@property (nonatomic, readonly) BOOL pending NS_REFINED_FOR_SWIFT;
/// - Returns: if the promise is resolved and fulfilled.
@property (nonatomic, readonly) BOOL fulfilled NS_REFINED_FOR_SWIFT;
/// - Returns: if the promise is resolved and rejected.
@property (nonatomic, readonly) BOOL rejected NS_REFINED_FOR_SWIFT;
/**
The provided block is executed when its receiver is fulfilled.
If you provide a block that takes a parameter, the value of the receiver will be passed as that parameter.
[NSURLSession GET:url].then(^(NSData *data){
// do something with data
});
@return A new promise that is resolved with the value returned from the provided block. For example:
[NSURLSession GET:url].then(^(NSData *data){
return data.length;
}).then(^(NSNumber *number){
//…
});
@warning *Important* The block passed to `then` may take zero, one, two or three arguments, and return an object or return nothing. This flexibility is why the method signature for then is `id`, which means you will not get completion for the block parameter, and must type it yourself. It is safe to type any block syntax here, so to start with try just: `^{}`.
@warning *Important* If an `NSError` or `NSString` is thrown inside your block, or you return an `NSError` object the next `Promise` will be rejected. See `catch` for documentation on error handling.
@warning *Important* `then` is always executed on the main queue.
@see thenOn
@see thenInBackground
*/
- (AnyPromise * __nonnull (^ __nonnull)(id __nonnull))then NS_REFINED_FOR_SWIFT;
/**
The provided block is executed on the default queue when the receiver is fulfilled.
This method is provided as a convenience for `thenOn`.
@see then
@see thenOn
*/
- (AnyPromise * __nonnull(^ __nonnull)(id __nonnull))thenInBackground NS_REFINED_FOR_SWIFT;
/**
The provided block is executed on the dispatch queue of your choice when the receiver is fulfilled.
@see then
@see thenInBackground
*/
- (AnyPromise * __nonnull(^ __nonnull)(dispatch_queue_t __nonnull, id __nonnull))thenOn NS_REFINED_FOR_SWIFT;
#ifndef __cplusplus
/**
The provided block is executed when the receiver is rejected.
Provide a block of form `^(NSError *){}` or simply `^{}`. The parameter has type `id` to give you the freedom to choose either.
The provided block always runs on the main queue.
@warning *Note* Cancellation errors are not caught.
@warning *Note* Since catch is a c++ keyword, this method is not available in Objective-C++ files. Instead use catchOn.
@see catchOn
@see catchInBackground
*/
- (AnyPromise * __nonnull(^ __nonnull)(id __nonnull))catch NS_REFINED_FOR_SWIFT;
#endif
/**
The provided block is executed when the receiver is rejected.
Provide a block of form `^(NSError *){}` or simply `^{}`. The parameter has type `id` to give you the freedom to choose either.
The provided block always runs on the global background queue.
@warning *Note* Cancellation errors are not caught.
@warning *Note* Since catch is a c++ keyword, this method is not available in Objective-C++ files. Instead use catchWithPolicy.
@see catch
@see catchOn
*/
- (AnyPromise * __nonnull(^ __nonnull)(id __nonnull))catchInBackground NS_REFINED_FOR_SWIFT;
/**
The provided block is executed when the receiver is rejected.
Provide a block of form `^(NSError *){}` or simply `^{}`. The parameter has type `id` to give you the freedom to choose either.
The provided block always runs on queue provided.
@warning *Note* Cancellation errors are not caught.
@see catch
@see catchInBackground
*/
- (AnyPromise * __nonnull(^ __nonnull)(dispatch_queue_t __nonnull, id __nonnull))catchOn NS_REFINED_FOR_SWIFT;
/**
The provided block is executed when the receiver is resolved.
The provided block always runs on the main queue.
@see ensureOn
*/
- (AnyPromise * __nonnull(^ __nonnull)(dispatch_block_t __nonnull))ensure NS_REFINED_FOR_SWIFT;
/**
The provided block is executed on the dispatch queue of your choice when the receiver is resolved.
@see ensure
*/
- (AnyPromise * __nonnull(^ __nonnull)(dispatch_queue_t __nonnull, dispatch_block_t __nonnull))ensureOn NS_REFINED_FOR_SWIFT;
/**
Wait until the promise is resolved.
@return Value if fulfilled or error if rejected.
*/
- (id __nullable)wait NS_REFINED_FOR_SWIFT;
/**
Create a new promise with an associated resolver.
Use this method when wrapping asynchronous code that does *not* use
promises so that this code can be used in promise chains. Generally,
prefer `promiseWithResolverBlock:` as the resulting code is more elegant.
PMKResolver resolve;
AnyPromise *promise = [[AnyPromise alloc] initWithResolver:&resolve];
// later
resolve(@"foo");
@param resolver A reference to a block pointer of PMKResolver type.
You can then call your resolver to resolve this promise.
@return A new promise.
@warning *Important* The resolver strongly retains the promise.
@see promiseWithResolverBlock:
*/
- (instancetype __nonnull)initWithResolver:(PMKResolver __strong __nonnull * __nonnull)resolver NS_REFINED_FOR_SWIFT;
@end
typedef void (^PMKAdapter)(id __nullable, NSError * __nullable) NS_REFINED_FOR_SWIFT;
typedef void (^PMKIntegerAdapter)(NSInteger, NSError * __nullable) NS_REFINED_FOR_SWIFT;
typedef void (^PMKBooleanAdapter)(BOOL, NSError * __nullable) NS_REFINED_FOR_SWIFT;
@interface AnyPromise (Adapters)
/**
Create a new promise by adapting an existing asynchronous system.
The pattern of a completion block that passes two parameters, the first
the result and the second an `NSError` object is so common that we
provide this convenience adapter to make wrapping such systems more
elegant.
return [PMKPromise promiseWithAdapterBlock:^(PMKAdapter adapter){
PFQuery *query = [PFQuery ];
[query findObjectsInBackgroundWithBlock:adapter];
}];
@warning *Important* If both parameters are nil, the promise fulfills,
if both are non-nil the promise rejects. This is per the convention.
@see https://github.com/mxcl/PromiseKit/blob/master/Documentation/GettingStarted.md#making-promises
*/
+ (instancetype __nonnull)promiseWithAdapterBlock:(void (^ __nonnull)(PMKAdapter __nonnull adapter))block NS_REFINED_FOR_SWIFT;
/**
Create a new promise by adapting an existing asynchronous system.
Adapts asynchronous systems that complete with `^(NSInteger, NSError *)`.
NSInteger will cast to enums provided the enum has been wrapped with
`NS_ENUM`. All of Apples enums are, so if you find one that hasnt you
may need to make a pull-request.
@see promiseWithAdapter
*/
+ (instancetype __nonnull)promiseWithIntegerAdapterBlock:(void (^ __nonnull)(PMKIntegerAdapter __nonnull adapter))block NS_REFINED_FOR_SWIFT;
/**
Create a new promise by adapting an existing asynchronous system.
Adapts asynchronous systems that complete with `^(BOOL, NSError *)`.
@see promiseWithAdapter
*/
+ (instancetype __nonnull)promiseWithBooleanAdapterBlock:(void (^ __nonnull)(PMKBooleanAdapter __nonnull adapter))block NS_REFINED_FOR_SWIFT;
@end
#ifdef __cplusplus
extern "C" {
#endif
/**
Whenever resolving a promise you may resolve with a tuple, eg.
returning from a `then` or `catch` handler or resolving a new promise.
Consumers of your Promise are not compelled to consume any arguments and
in fact will often only consume the first parameter. Thus ensure the
order of parameters is: from most-important to least-important.
Currently PromiseKit limits you to THREE parameters to the manifold.
*/
#define PMKManifold(...) __PMKManifold(__VA_ARGS__, 3, 2, 1)
#define __PMKManifold(_1, _2, _3, N, ...) __PMKArrayWithCount(N, _1, _2, _3)
extern id __nonnull __PMKArrayWithCount(NSUInteger, ...);
#ifdef __cplusplus
} // Extern C
#endif
@interface AnyPromise (Unavailable)
- (instancetype __nonnull)init __attribute__((unavailable("It is illegal to create an unresolvable promise.")));
+ (instancetype __nonnull)new __attribute__((unavailable("It is illegal to create an unresolvable promise.")));
- (AnyPromise * __nonnull(^ __nonnull)(dispatch_block_t __nonnull))always __attribute__((unavailable("See -ensure")));
- (AnyPromise * __nonnull(^ __nonnull)(dispatch_block_t __nonnull))alwaysOn __attribute__((unavailable("See -ensureOn")));
- (AnyPromise * __nonnull(^ __nonnull)(dispatch_block_t __nonnull))finally __attribute__((unavailable("See -ensure")));
- (AnyPromise * __nonnull(^ __nonnull)(dispatch_block_t __nonnull, dispatch_block_t __nonnull))finallyOn __attribute__((unavailable("See -ensureOn")));
@end
__attribute__((unavailable("See AnyPromise")))
@interface PMKPromise
@end

View File

@ -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

View File

@ -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 dont use it.
*/
@objc(__AnyPromise) public class __AnyPromise: NSObject {
fileprivate let box: Box<Any?>
@objc public init(resolver body: (@escaping (Any?) -> Void) -> Void) {
box = EmptyBox<Any?>()
super.init()
body {
if let p = $0 as? AnyPromise {
p.d.__pipe(self.box.seal)
} else {
self.box.seal($0)
}
}
}
@objc public func __thenOn(_ q: DispatchQueue, execute: @escaping (Any?) -> Any?) -> AnyPromise {
return AnyPromise(__D: __AnyPromise(resolver: { resolve in
self.__pipe { obj in
if !(obj is NSError) {
q.async {
resolve(execute(obj))
}
} else {
resolve(obj)
}
}
}))
}
@objc public func __catchOn(_ q: DispatchQueue, execute: @escaping (Any?) -> Any?) -> AnyPromise {
return AnyPromise(__D: __AnyPromise(resolver: { resolve in
self.__pipe { obj in
if obj is NSError {
q.async {
resolve(execute(obj))
}
} else {
resolve(obj)
}
}
}))
}
@objc public func __ensureOn(_ q: DispatchQueue, execute: @escaping () -> Void) -> AnyPromise {
return AnyPromise(__D: __AnyPromise(resolver: { resolve in
self.__pipe { obj in
q.async {
execute()
resolve(obj)
}
}
}))
}
@objc public func __wait() -> Any? {
if Thread.isMainThread {
conf.logHandler(.waitOnMainThread)
}
var result = __value
if result == nil {
let group = DispatchGroup()
group.enter()
self.__pipe { obj in
result = obj
group.leave()
}
group.wait()
}
return result
}
/// Internal, do not use! Some behaviors undefined.
@objc public func __pipe(_ to: @escaping (Any?) -> Void) {
let to = { (obj: Any?) -> Void in
if obj is NSError {
to(obj) // or we cannot determine if objects are errors in objc land
} else {
to(obj)
}
}
switch box.inspect() {
case .pending:
box.inspect {
switch $0 {
case .pending(let handlers):
handlers.append { obj in
to(obj)
}
case .resolved(let obj):
to(obj)
}
}
case .resolved(let obj):
to(obj)
}
}
@objc public var __value: Any? {
switch box.inspect() {
case .resolved(let obj):
return obj
default:
return nil
}
}
@objc public var __pending: Bool {
switch box.inspect() {
case .pending:
return true
case .resolved:
return false
}
}
}
extension AnyPromise: Thenable, CatchMixin {
/// - Returns: A new `AnyPromise` bound to a `Promise<Any>`.
public convenience init<U: Thenable>(_ bridge: U) {
self.init(__D: __AnyPromise(resolver: { resolve in
bridge.pipe {
switch $0 {
case .rejected(let error):
resolve(error as NSError)
case .fulfilled(let value):
resolve(value)
}
}
}))
}
public func pipe(to body: @escaping (Result<Any?>) -> Void) {
func fulfill() {
// calling through to the ObjC `value` property unwraps (any) PMKManifold
// and considering this is the Swift pipe; we want that.
body(.fulfilled(self.value(forKey: "value")))
}
switch box.inspect() {
case .pending:
box.inspect {
switch $0 {
case .pending(let handlers):
handlers.append {
if let error = $0 as? Error {
body(.rejected(error))
} else {
fulfill()
}
}
case .resolved(let error as Error):
body(.rejected(error))
case .resolved:
fulfill()
}
}
case .resolved(let error as Error):
body(.rejected(error))
case .resolved:
fulfill()
}
}
fileprivate var d: __AnyPromise {
return value(forKey: "__d") as! __AnyPromise
}
var box: Box<Any?> {
return d.box
}
public var result: Result<Any?>? {
guard let value = __value else {
return nil
}
if let error = value as? Error {
return .rejected(error)
} else {
return .fulfilled(value)
}
}
public typealias T = Any?
}
#if swift(>=3.1)
public extension Promise where T == Any? {
convenience init(_ anyPromise: AnyPromise) {
self.init {
anyPromise.pipe(to: $0.resolve)
}
}
}
#else
extension AnyPromise {
public func asPromise() -> Promise<Any?> {
return Promise(.pending, resolver: { resolve in
pipe { result in
switch result {
case .rejected(let error):
resolve.reject(error)
case .fulfilled(let obj):
resolve.fulfill(obj)
}
}
})
}
}
#endif

View File

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

View File

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

View File

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

View File

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

View File

@ -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)
}
}

View File

@ -1,104 +0,0 @@
import Foundation
public enum PMKError: Error {
/**
The completionHandler with form `(T?, Error?)` was called with `(nil, nil)`.
This is invalid as per Cocoa/Apple calling conventions.
*/
case invalidCallingConvention
/**
A handler returned its own promise. 99% of the time, this is likely a
programming error. It is also invalid per Promises/A+.
*/
case returnedSelf
/** `when()`, `race()` etc. were called with invalid parameters, eg. an empty array. */
case badInput
/// The operation was cancelled
case cancelled
/// `nil` was returned from `flatMap`
@available(*, deprecated, message: "See: `compactMap`")
case flatMap(Any, Any.Type)
/// `nil` was returned from `compactMap`
case compactMap(Any, Any.Type)
/**
The lastValue or firstValue of a sequence was requested but the sequence was empty.
Also used if all values of this collection failed the test passed to `firstValue(where:)`.
*/
case emptySequence
}
extension PMKError: CustomDebugStringConvertible {
public var debugDescription: String {
switch self {
case .flatMap(let obj, let type):
return "Could not `flatMap<\(type)>`: \(obj)"
case .compactMap(let obj, let type):
return "Could not `compactMap<\(type)>`: \(obj)"
case .invalidCallingConvention:
return "A closure was called with an invalid calling convention, probably (nil, nil)"
case .returnedSelf:
return "A promise handler returned itself"
case .badInput:
return "Bad input was provided to a PromiseKit function"
case .cancelled:
return "The asynchronous sequence was cancelled"
case .emptySequence:
return "The first or last element was requested for an empty sequence"
}
}
}
extension PMKError: LocalizedError {
public var errorDescription: String? {
return debugDescription
}
}
//////////////////////////////////////////////////////////// Cancellation
/// An error that may represent the cancelled condition
public protocol CancellableError: Error {
/// returns true if this Error represents a cancelled condition
var isCancelled: Bool { get }
}
extension Error {
public var isCancelled: Bool {
do {
throw self
} catch PMKError.cancelled {
return true
} catch let error as CancellableError {
return error.isCancelled
} catch URLError.cancelled {
return true
} catch CocoaError.userCancelled {
return true
} catch {
#if canImport(StoreKit)
let domain = (error as AnyObject).value(forKey: "domain") as? String
let code = (error as AnyObject).value(forKey: "code") as? Int
return ("SKErrorDomain", 2) == (domain, code)
#else
return false
#endif
}
}
}
/// Used by `catch` and `recover`
public enum CatchPolicy {
/// Indicates that `catch` or `recover` handle all error types including cancellable-errors.
case allErrors
/// Indicates that `catch` or `recover` handle all error except cancellable-errors.
case allErrorsExceptCancellation
}

View File

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

View File

@ -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>

View File

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

View File

@ -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;
}

View File

@ -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 blocks argument count is unsupported." userInfo:nil]; \
}}();})
switch (rtype) {
case 'v':
call_block_with_rtype(void);
return nil;
case '@':
return call_block_with_rtype(id) ?: nil;
case '*': {
char *str = call_block_with_rtype(char *);
return str ? @(str) : nil;
}
case 'c': return @(call_block_with_rtype(char));
case 'i': return @(call_block_with_rtype(int));
case 's': return @(call_block_with_rtype(short));
case 'l': return @(call_block_with_rtype(long));
case 'q': return @(call_block_with_rtype(long long));
case 'C': return @(call_block_with_rtype(unsigned char));
case 'I': return @(call_block_with_rtype(unsigned int));
case 'S': return @(call_block_with_rtype(unsigned short));
case 'L': return @(call_block_with_rtype(unsigned long));
case 'Q': return @(call_block_with_rtype(unsigned long long));
case 'f': return @(call_block_with_rtype(float));
case 'd': return @(call_block_with_rtype(double));
case 'B': return @(call_block_with_rtype(_Bool));
case '^':
if (strcmp(sig.methodReturnType, "^v") == 0) {
call_block_with_rtype(void);
return nil;
}
// else fall through!
default:
@throw [NSException exceptionWithName:@"PromiseKit" reason:@"PromiseKit: Unsupported method signature." userInfo:nil];
}
}
static id PMKCallVariadicBlock(id frock, id result) {
@try {
return _PMKCallVariadicBlock(frock, result);
} @catch (id thrown) {
if ([thrown isKindOfClass:[NSString class]])
return thrown;
if ([thrown isKindOfClass:[NSError class]])
return thrown;
// we dont catch objc exceptions: they are meant to crash your app
@throw thrown;
}
}

View File

@ -1,184 +0,0 @@
import class Foundation.Thread
import Dispatch
/**
A `Promise` is a functional abstraction around a failable asynchronous operation.
- See: `Thenable`
*/
public final class Promise<T>: Thenable, CatchMixin {
let box: Box<Result<T>>
fileprivate init(box: SealedBox<Result<T>>) {
self.box = box
}
/**
Initialize a new fulfilled promise.
We do not provide `init(value:)` because Swift is greedy
and would pick that initializer in cases where it should pick
one of the other more specific options leading to Promises with
`T` that is eg: `Error` or worse `(T->Void,Error->Void)` for
uses of our PMK < 4 pending initializer due to Swift trailing
closure syntax (nothing good comes without pain!).
Though often easy to detect, sometimes these issues would be
hidden by other type inference leading to some nasty bugs in
production.
In PMK5 we tried to work around this by making the pending
initializer take the form `Promise(.pending)` but this led to
bad migration errors for PMK4 users. Hence instead we quickly
released PMK6 and now only provide this initializer for making
sealed & fulfilled promises.
Usage is still (usually) good:
guard foo else {
return .value(bar)
}
*/
public static func value(_ value: T) -> Promise<T> {
return Promise(box: SealedBox(value: .fulfilled(value)))
}
/// Initialize a new rejected promise.
public init(error: Error) {
box = SealedBox(value: .rejected(error))
}
/// Initialize a new promise bound to the provided `Thenable`.
public init<U: Thenable>(_ bridge: U) where U.T == T {
box = EmptyBox()
bridge.pipe(to: box.seal)
}
/// Initialize a new promise that can be resolved with the provided `Resolver`.
public init(resolver body: (Resolver<T>) throws -> Void) {
box = EmptyBox()
let resolver = Resolver(box)
do {
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) {
switch box.inspect() {
case .pending:
box.inspect {
switch $0 {
case .pending(let handlers):
handlers.append(to)
case .resolved(let value):
to(value)
}
}
case .resolved(let value):
to(value)
}
}
/// - See: `Thenable.result`
public var result: Result<T>? {
switch box.inspect() {
case .pending:
return nil
case .resolved(let result):
return result
}
}
init(_: PMKUnambiguousInitializer) {
box = EmptyBox()
}
}
public extension Promise {
/**
Blocks this thread, soyou knowdont call this on a serial thread that
any part of your chain may use. Like the main thread for example.
*/
func wait() throws -> T {
if Thread.isMainThread {
conf.logHandler(LogEvent.waitOnMainThread)
}
var result = self.result
if result == nil {
let group = DispatchGroup()
group.enter()
pipe { result = $0; group.leave() }
group.wait()
}
switch result! {
case .rejected(let error):
throw error
case .fulfilled(let value):
return value
}
}
}
#if swift(>=3.1)
extension Promise where T == Void {
/// Initializes a new promise fulfilled with `Void`
public convenience init() {
self.init(box: SealedBox(value: .fulfilled(Void())))
}
/// Returns a new promise fulfilled with `Void`
public static var value: Promise<Void> {
return .value(Void())
}
}
#endif
public extension DispatchQueue {
/**
Asynchronously executes the provided closure on a dispatch queue.
DispatchQueue.global().async(.promise) {
try md5(input)
}.done { md5 in
//
}
- Parameter body: The closure that resolves this promise.
- Returns: A new `Promise` resolved by the result of the provided closure.
- Note: There is no Promise/Thenable version of this due to Swift compiler ambiguity issues.
*/
@available(macOS 10.10, iOS 8.0, tvOS 9.0, watchOS 2.0, *)
final func async<T>(_: PMKNamespacer, group: DispatchGroup? = nil, qos: DispatchQoS = .default, flags: DispatchWorkItemFlags = [], execute body: @escaping () throws -> T) -> Promise<T> {
let promise = Promise<T>(.pending)
async(group: group, qos: qos, flags: flags) {
do {
promise.box.seal(.fulfilled(try body()))
} catch {
promise.box.seal(.rejected(error))
}
}
return promise
}
}
/// used by our extensions to provide unambiguous functions with the same name as the original function
public enum PMKNamespacer {
case promise
}
enum PMKUnambiguousInitializer {
case pending
}

View File

@ -1,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[];

View File

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

View File

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

View File

@ -1,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));
});
}];
}

View File

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

View File

@ -1,10 +0,0 @@
#import "AnyPromise.h"
@import Dispatch;
AnyPromise *dispatch_promise_on(dispatch_queue_t queue, id block) {
return [AnyPromise promiseWithValue:nil].thenOn(queue, block);
}
AnyPromise *dispatch_promise(id block) {
return dispatch_promise_on(dispatch_get_global_queue(0, 0), block);
}

View File

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

View File

@ -1,165 +0,0 @@
#import <Foundation/NSDate.h>
#import <dispatch/dispatch.h>
@class AnyPromise;
extern NSString * __nonnull const PMKErrorDomain;
#define PMKFailingPromiseIndexKey @"PMKFailingPromiseIndexKey"
#define PMKJoinPromisesKey @"PMKJoinPromisesKey"
#define PMKUnexpectedError 1l
#define PMKInvalidUsageError 3l
#define PMKAccessDeniedError 4l
#define PMKOperationFailed 8l
#define PMKTaskError 9l
#define PMKJoinError 10l
#ifdef __cplusplus
extern "C" {
#endif
/**
@return A new promise that resolves after the specified duration.
@parameter duration The duration in seconds to wait before this promise is resolve.
For example:
PMKAfter(1).then(^{
//…
});
*/
extern AnyPromise * __nonnull PMKAfter(NSTimeInterval duration) NS_REFINED_FOR_SWIFT;
/**
`when` is a mechanism for waiting more than one asynchronous task and responding when they are all complete.
`PMKWhen` accepts varied input. If an array is passed then when those promises fulfill, whens promise fulfills with an array of fulfillment values. If a dictionary is passed then the same occurs, but whens promise fulfills with a dictionary of fulfillments keyed as per the input.
Interestingly, if a single promise is passed then when waits on that single promise, and if a single non-promise object is passed then when fulfills immediately with that object. If the array or dictionary that is passed contains objects that are not promises, then these objects are considered fulfilled promises. The reason we do this is to allow a pattern know as "abstracting away asynchronicity".
If *any* of the provided promises reject, the returned promise is immediately rejected with that promises rejection. The errors `userInfo` object is supplemented with `PMKFailingPromiseIndexKey`.
For example:
PMKWhen(@[promise1, promise2]).then(^(NSArray *results){
//…
});
@warning *Important* In the event of rejection the other promises will continue to resolve and as per any other promise will either fulfill or reject. This is the right pattern for `getter` style asynchronous tasks, but often for `setter` tasks (eg. storing data on a server), you most likely will need to wait on all tasks and then act based on which have succeeded and which have failed. In such situations use `PMKJoin`.
@param input The input upon which to wait before resolving this promise.
@return A promise that is resolved with either:
1. An array of values from the provided array of promises.
2. The value from the provided promise.
3. The provided non-promise object.
@see PMKJoin
*/
extern AnyPromise * __nonnull PMKWhen(id __nonnull input) NS_REFINED_FOR_SWIFT;
/**
Creates a new promise that resolves only when all provided promises have resolved.
Typically, you should use `PMKWhen`.
For example:
PMKJoin(@[promise1, promise2]).then(^(NSArray *resultingValues){
//…
}).catch(^(NSError *error){
assert(error.domain == PMKErrorDomain);
assert(error.code == PMKJoinError);
NSArray *promises = error.userInfo[PMKJoinPromisesKey];
for (AnyPromise *promise in promises) {
if (promise.rejected) {
//…
}
}
});
@param promises An array of promises.
@return A promise that thens three parameters:
1) An array of mixed values and errors from the resolved input.
2) An array of values from the promises that fulfilled.
3) An array of errors from the promises that rejected or nil if all promises fulfilled.
@see when
*/
AnyPromise *__nonnull PMKJoin(NSArray * __nonnull promises) NS_REFINED_FOR_SWIFT;
/**
Literally hangs this thread until the promise has resolved.
Do not use hang unless you are testing, playing or debugging.
If you use it in production code I will literally and honestly cry like a child.
@return The resolved value of the promise.
@warning T SAFE. IT IS NOT SAFE. IT IS NOT SAFE. IT IS NOT SAFE. IT IS NO
*/
extern id __nullable PMKHang(AnyPromise * __nonnull promise);
/**
Executes the provided block on a background queue.
dispatch_promise is a convenient way to start a promise chain where the
first step needs to run synchronously on a background queue.
dispatch_promise(^{
return md5(input);
}).then(^(NSString *md5){
NSLog(@"md5: %@", md5);
});
@param block The block to be executed in the background. Returning an `NSError` will reject the promise, everything else (including void) fulfills the promise.
@return A promise resolved with the return value of the provided block.
@see dispatch_async
*/
extern AnyPromise * __nonnull dispatch_promise(id __nonnull block) NS_SWIFT_UNAVAILABLE("Use our `DispatchQueue.async` override instead");
/**
Executes the provided block on the specified background queue.
dispatch_promise_on(myDispatchQueue, ^{
return md5(input);
}).then(^(NSString *md5){
NSLog(@"md5: %@", md5);
});
@param block The block to be executed in the background. Returning an `NSError` will reject the promise, everything else (including void) fulfills the promise.
@return A promise resolved with the return value of the provided block.
@see dispatch_promise
*/
extern AnyPromise * __nonnull dispatch_promise_on(dispatch_queue_t __nonnull queue, id __nonnull block) NS_SWIFT_UNAVAILABLE("Use our `DispatchQueue.async` override instead");
/**
Returns a new promise that resolves when the value of the first resolved promise in the provided array of promises.
*/
extern AnyPromise * __nonnull PMKRace(NSArray * __nonnull promises) NS_REFINED_FOR_SWIFT;
#ifdef __cplusplus
} // Extern C
#endif

View File

@ -1,29 +0,0 @@
#import "AnyPromise.h"
#import "AnyPromise+Private.h"
@import CoreFoundation.CFRunLoop;
/**
Suspends the active thread waiting on the provided promise.
@return The value of the provided promise once resolved.
*/
id PMKHang(AnyPromise *promise) {
if (promise.pending) {
static CFRunLoopSourceContext context;
CFRunLoopRef runLoop = CFRunLoopGetCurrent();
CFRunLoopSourceRef runLoopSource = CFRunLoopSourceCreate(NULL, 0, &context);
CFRunLoopAddSource(runLoop, runLoopSource, kCFRunLoopDefaultMode);
promise.ensure(^{
CFRunLoopStop(runLoop);
});
while (promise.pending) {
CFRunLoopRun();
}
CFRunLoopRemoveSource(runLoop, runLoopSource, kCFRunLoopDefaultMode);
CFRelease(runLoopSource);
}
return promise.value;
}

View File

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

View File

@ -1,54 +0,0 @@
@import Foundation.NSDictionary;
#import "AnyPromise+Private.h"
#import <libkern/OSAtomic.h>
@import Foundation.NSError;
@import Foundation.NSNull;
#import "PromiseKit.h"
#import <stdatomic.h>
/**
Waits on all provided promises.
`PMKWhen` rejects as soon as one of the provided promises rejects. `PMKJoin` waits on all provided promises, then rejects if any of those promises rejects, otherwise it fulfills with values from the provided promises.
- Returns: A new promise that resolves once all the provided promises resolve.
*/
AnyPromise *PMKJoin(NSArray *promises) {
if (promises == nil)
return [AnyPromise promiseWithValue:[NSError errorWithDomain:PMKErrorDomain code:PMKInvalidUsageError userInfo:@{NSLocalizedDescriptionKey: @"PMKJoin(nil)"}]];
if (promises.count == 0)
return [AnyPromise promiseWithValue:promises];
return [AnyPromise promiseWithResolverBlock:^(PMKResolver resolve) {
NSPointerArray *results = NSPointerArrayMake(promises.count);
__block atomic_int countdown = promises.count;
__block BOOL rejected = NO;
[promises enumerateObjectsUsingBlock:^(AnyPromise *promise, NSUInteger ii, BOOL *stop) {
[promise __pipe:^(id value) {
if (IsError(value)) {
rejected = YES;
}
//FIXME surely this isn't thread safe on multiple cores?
[results replacePointerAtIndex:ii withPointer:(__bridge void *)(value ?: [NSNull null])];
atomic_fetch_sub_explicit(&countdown, 1, memory_order_relaxed);
if (countdown == 0) {
if (!rejected) {
resolve(results.allObjects);
} else {
id userInfo = @{PMKJoinPromisesKey: promises};
id err = [NSError errorWithDomain:PMKErrorDomain code:PMKJoinError userInfo:userInfo];
resolve(err);
}
}
}];
(void) stop;
}];
}];
}

View File

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

View File

@ -1,57 +0,0 @@
@inline(__always)
private func _race<U: Thenable>(_ thenables: [U]) -> Promise<U.T> {
let rp = Promise<U.T>(.pending)
for thenable in thenables {
thenable.pipe(to: rp.box.seal)
}
return rp
}
/**
Waits for one promise to resolve
race(promise1, promise2, promise3).then { winner in
//
}
- Returns: The promise that resolves first
- Warning: If the first resolution is a rejection, the returned promise is rejected
*/
public func race<U: Thenable>(_ thenables: U...) -> Promise<U.T> {
return _race(thenables)
}
/**
Waits for one promise to resolve
race(promise1, promise2, promise3).then { winner in
//
}
- Returns: The promise that resolves first
- Warning: If the first resolution is a rejection, the returned promise is rejected
- Remark: If the provided array is empty the returned promise is rejected with PMKError.badInput
*/
public func race<U: Thenable>(_ thenables: [U]) -> Promise<U.T> {
guard !thenables.isEmpty else {
return Promise(error: PMKError.badInput)
}
return _race(thenables)
}
/**
Waits for one guarantee to resolve
race(promise1, promise2, promise3).then { winner in
//
}
- Returns: The guarantee that resolves first
*/
public func race<T>(_ guarantees: Guarantee<T>...) -> Guarantee<T> {
let rg = Guarantee<T>(.pending)
for guarantee in guarantees {
guarantee.pipe(to: rg.box.seal)
}
return rg
}

View File

@ -1,107 +0,0 @@
@import Foundation.NSDictionary;
#import "AnyPromise+Private.h"
@import Foundation.NSProgress;
#import <libkern/OSAtomic.h>
@import Foundation.NSError;
@import Foundation.NSNull;
#import "PromiseKit.h"
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
// ^^ OSAtomicDecrement32 is deprecated on watchOS
// NSProgress resources:
// * https://robots.thoughtbot.com/asynchronous-nsprogress
// * http://oleb.net/blog/2014/03/nsprogress/
// NSProgress! Beware!
// * https://github.com/AFNetworking/AFNetworking/issues/2261
/**
Wait for all promises in a set to resolve.
@note If *any* of the provided promises reject, the returned promise is immediately rejected with that error.
@warning In the event of rejection the other promises will continue to resolve and, as per any other promise, will either fulfill or reject. This is the right pattern for `getter` style asynchronous tasks, but often for `setter` tasks (eg. storing data on a server), you most likely will need to wait on all tasks and then act based on which have succeeded and which have failed, in such situations use `when(resolved:)`.
@param promises The promises upon which to wait before the returned promise resolves.
@note PMKWhen provides NSProgress.
@return A new promise that resolves when all the provided promises fulfill or one of the provided promises rejects.
*/
AnyPromise *PMKWhen(id promises) {
if (promises == nil)
return [AnyPromise promiseWithValue:[NSError errorWithDomain:PMKErrorDomain code:PMKInvalidUsageError userInfo:@{NSLocalizedDescriptionKey: @"PMKWhen(nil)"}]];
if ([promises isKindOfClass:[NSArray class]] || [promises isKindOfClass:[NSDictionary class]]) {
if ([promises count] == 0)
return [AnyPromise promiseWithValue:promises];
} else if ([promises isKindOfClass:[AnyPromise class]]) {
promises = @[promises];
} else {
return [AnyPromise promiseWithValue:promises];
}
#ifndef PMKDisableProgress
NSProgress *progress = [NSProgress progressWithTotalUnitCount:(int64_t)[promises count]];
progress.pausable = NO;
progress.cancellable = NO;
#else
struct PMKProgress {
int completedUnitCount;
int totalUnitCount;
double fractionCompleted;
};
__block struct PMKProgress progress;
#endif
__block int32_t countdown = (int32_t)[promises count];
BOOL const isdict = [promises isKindOfClass:[NSDictionary class]];
return [AnyPromise promiseWithResolverBlock:^(PMKResolver resolve) {
NSInteger index = 0;
for (__strong id key in promises) {
AnyPromise *promise = isdict ? promises[key] : key;
if (!isdict) key = @(index);
if (![promise isKindOfClass:[AnyPromise class]])
promise = [AnyPromise promiseWithValue:promise];
[promise __pipe:^(id value){
if (progress.fractionCompleted >= 1)
return;
if (IsError(value)) {
progress.completedUnitCount = progress.totalUnitCount;
NSMutableDictionary *userInfo = [NSMutableDictionary dictionaryWithDictionary:[(NSError *)value userInfo] ?: @{}];
userInfo[PMKFailingPromiseIndexKey] = key;
[userInfo setObject:value forKey:NSUnderlyingErrorKey];
id err = [[NSError alloc] initWithDomain:[value domain] code:[value code] userInfo:userInfo];
resolve(err);
}
else if (OSAtomicDecrement32(&countdown) == 0) {
progress.completedUnitCount = progress.totalUnitCount;
id results;
if (isdict) {
results = [NSMutableDictionary new];
for (id key in promises) {
id promise = promises[key];
results[key] = IsPromise(promise) ? ((AnyPromise *)promise).value : promise;
}
} else {
results = [NSMutableArray new];
for (AnyPromise *promise in promises) {
id value = IsPromise(promise) ? (promise.value ?: [NSNull null]) : promise;
[results addObject:value];
}
}
resolve(results);
} else {
progress.completedUnitCount++;
}
}];
}
}];
}
#pragma GCC diagnostic pop

View File

@ -1,258 +0,0 @@
import Foundation
import Dispatch
private func _when<U: Thenable>(_ thenables: [U]) -> Promise<Void> {
var countdown = thenables.count
guard countdown > 0 else {
return .value(Void())
}
let rp = Promise<Void>(.pending)
#if PMKDisableProgress || os(Linux)
var progress: (completedUnitCount: Int, totalUnitCount: Int) = (0, 0)
#else
let progress = Progress(totalUnitCount: Int64(thenables.count))
progress.isCancellable = false
progress.isPausable = false
#endif
let barrier = DispatchQueue(label: "org.promisekit.barrier.when", attributes: .concurrent)
for promise in thenables {
promise.pipe { result in
barrier.sync(flags: .barrier) {
switch result {
case .rejected(let error):
if rp.isPending {
progress.completedUnitCount = progress.totalUnitCount
rp.box.seal(.rejected(error))
}
case .fulfilled:
guard rp.isPending else { return }
progress.completedUnitCount += 1
countdown -= 1
if countdown == 0 {
rp.box.seal(.fulfilled(()))
}
}
}
}
}
return rp
}
/**
Wait for all promises in a set to fulfill.
For example:
when(fulfilled: promise1, promise2).then { results in
//
}.catch { error in
switch error {
case URLError.notConnectedToInternet:
//
case CLError.denied:
//
}
}
- Note: If *any* of the provided promises reject, the returned promise is immediately rejected with that error.
- Warning: In the event of rejection the other promises will continue to resolve and, as per any other promise, will either fulfill or reject. This is the right pattern for `getter` style asynchronous tasks, but often for `setter` tasks (eg. storing data on a server), you most likely will need to wait on all tasks and then act based on which have succeeded and which have failed, in such situations use `when(resolved:)`.
- Parameter promises: The promises upon which to wait before the returned promise resolves.
- Returns: A new promise that resolves when all the provided promises fulfill or one of the provided promises rejects.
- Note: `when` provides `NSProgress`.
- SeeAlso: `when(resolved:)`
*/
public func when<U: Thenable>(fulfilled thenables: [U]) -> Promise<[U.T]> {
return _when(thenables).map(on: nil) { thenables.map{ $0.value! } }
}
/// Wait for all promises in a set to fulfill.
public func when<U: Thenable>(fulfilled promises: U...) -> Promise<Void> where U.T == Void {
return _when(promises)
}
/// Wait for all promises in a set to fulfill.
public func when<U: Thenable>(fulfilled promises: [U]) -> Promise<Void> where U.T == Void {
return _when(promises)
}
/// Wait for all promises in a set to fulfill.
public func when<U: Thenable, V: Thenable>(fulfilled pu: U, _ pv: V) -> Promise<(U.T, V.T)> {
return _when([pu.asVoid(), pv.asVoid()]).map(on: nil) { (pu.value!, pv.value!) }
}
/// Wait for all promises in a set to fulfill.
public func when<U: Thenable, V: Thenable, W: Thenable>(fulfilled pu: U, _ pv: V, _ pw: W) -> Promise<(U.T, V.T, W.T)> {
return _when([pu.asVoid(), pv.asVoid(), pw.asVoid()]).map(on: nil) { (pu.value!, pv.value!, pw.value!) }
}
/// Wait for all promises in a set to fulfill.
public func when<U: Thenable, V: Thenable, W: Thenable, X: Thenable>(fulfilled pu: U, _ pv: V, _ pw: W, _ px: X) -> Promise<(U.T, V.T, W.T, X.T)> {
return _when([pu.asVoid(), pv.asVoid(), pw.asVoid(), px.asVoid()]).map(on: nil) { (pu.value!, pv.value!, pw.value!, px.value!) }
}
/// Wait for all promises in a set to fulfill.
public func when<U: Thenable, V: Thenable, W: Thenable, X: Thenable, Y: Thenable>(fulfilled pu: U, _ pv: V, _ pw: W, _ px: X, _ py: Y) -> Promise<(U.T, V.T, W.T, X.T, Y.T)> {
return _when([pu.asVoid(), pv.asVoid(), pw.asVoid(), px.asVoid(), py.asVoid()]).map(on: nil) { (pu.value!, pv.value!, pw.value!, px.value!, py.value!) }
}
/**
Generate promises at a limited rate and wait for all to fulfill.
For example:
func downloadFile(url: URL) -> Promise<Data> {
// ...
}
let urls: [URL] = /**/
let urlGenerator = urls.makeIterator()
let generator = AnyIterator<Promise<Data>> {
guard url = urlGenerator.next() else {
return nil
}
return downloadFile(url)
}
when(generator, concurrently: 3).done { datas in
// ...
}
No more than three downloads will occur simultaneously.
- Note: The generator is called *serially* on a *background* queue.
- Warning: Refer to the warnings on `when(fulfilled:)`
- Parameter promiseGenerator: Generator of promises.
- Returns: A new promise that resolves when all the provided promises fulfill or one of the provided promises rejects.
- SeeAlso: `when(resolved:)`
*/
public func when<It: IteratorProtocol>(fulfilled promiseIterator: It, concurrently: Int) -> Promise<[It.Element.T]> where It.Element: Thenable {
guard concurrently > 0 else {
return Promise(error: PMKError.badInput)
}
var generator = promiseIterator
let root = Promise<[It.Element.T]>.pending()
var pendingPromises = 0
var promises: [It.Element] = []
let barrier = DispatchQueue(label: "org.promisekit.barrier.when", attributes: [.concurrent])
func dequeue() {
guard root.promise.isPending else { return } // dont continue dequeueing if root has been rejected
var shouldDequeue = false
barrier.sync {
shouldDequeue = pendingPromises < concurrently
}
guard shouldDequeue else { return }
var promise: It.Element!
barrier.sync(flags: .barrier) {
guard let next = generator.next() else { return }
promise = next
pendingPromises += 1
promises.append(next)
}
func testDone() {
barrier.sync {
if pendingPromises == 0 {
#if !swift(>=3.3) || (swift(>=4) && !swift(>=4.1))
root.resolver.fulfill(promises.flatMap{ $0.value })
#else
root.resolver.fulfill(promises.compactMap{ $0.value })
#endif
}
}
}
guard promise != nil else {
return testDone()
}
promise.pipe { resolution in
barrier.sync(flags: .barrier) {
pendingPromises -= 1
}
switch resolution {
case .fulfilled:
dequeue()
testDone()
case .rejected(let error):
root.resolver.reject(error)
}
}
dequeue()
}
dequeue()
return root.promise
}
/**
Waits on all provided promises.
`when(fulfilled:)` rejects as soon as one of the provided promises rejects. `when(resolved:)` waits on all provided promises whatever their result, and then provides an array of `Result<T>` so you can individually inspect the results. As a consequence this function returns a `Guarantee`, ie. errors are lifted from the individual promises into the results array of the returned `Guarantee`.
when(resolved: promise1, promise2, promise3).then { results in
for result in results where case .fulfilled(let value) {
//
}
}.catch { error in
// invalid! Never rejects
}
- Returns: A new promise that resolves once all the provided promises resolve. The array is ordered the same as the input, ie. the result order is *not* resolution order.
- Note: we do not provide tuple variants for `when(resolved:)` but will accept a pull-request
- Remark: Doesn't take Thenable due to protocol `associatedtype` paradox
*/
public func when<T>(resolved promises: Promise<T>...) -> Guarantee<[Result<T>]> {
return when(resolved: promises)
}
/// - See: `when(resolved: Promise<T>...)`
public func when<T>(resolved promises: [Promise<T>]) -> Guarantee<[Result<T>]> {
guard !promises.isEmpty else {
return .value([])
}
var countdown = promises.count
let barrier = DispatchQueue(label: "org.promisekit.barrier.join", attributes: .concurrent)
let rg = Guarantee<[Result<T>]>(.pending)
for promise in promises {
promise.pipe { result in
barrier.sync(flags: .barrier) {
countdown -= 1
}
barrier.sync {
if countdown == 0 {
rg.box.seal(promises.map{ $0.result! })
}
}
}
}
return rg
}
/// Waits on all provided Guarantees.
public func when(_ guarantees: Guarantee<Void>...) -> Guarantee<Void> {
return when(guarantees: guarantees)
}
// Waits on all provided Guarantees.
public func when(guarantees: [Guarantee<Void>]) -> Guarantee<Void> {
return when(fulfilled: guarantees).recover{ _ in }.asVoid()
}

View File

@ -1,185 +0,0 @@
import PromiseKit
import Dispatch
import XCTest
enum Error: Swift.Error {
case dummy // we reject with this when we don't intend to test against it
case sentinel(UInt32)
}
private let timeout: TimeInterval = 10
extension XCTestCase {
func describe(_ description: String, file: StaticString = #file, line: UInt = #line, body: () throws -> Void) {
PromiseKit.conf.Q.map = .main
do {
try body()
} catch {
XCTFail(description, file: file, line: line)
}
}
func specify(_ description: String, file: StaticString = #file, line: UInt = #line, body: ((promise: Promise<Void>, fulfill: () -> Void, reject: (Error) -> Void), XCTestExpectation) throws -> Void) {
let expectation = self.expectation(description: description)
let (pending, seal) = Promise<Void>.pending()
do {
try body((pending, seal.fulfill_, seal.reject), expectation)
waitForExpectations(timeout: timeout) { err in
if let _ = err {
XCTFail("wait failed: \(description)", file: file, line: line)
}
}
} catch {
XCTFail(description, file: file, line: line)
}
}
func testFulfilled(file: StaticString = #file, line: UInt = #line, body: @escaping (Promise<UInt32>, XCTestExpectation, UInt32) -> Void) {
testFulfilled(withExpectationCount: 1, file: file, line: line) {
body($0, $1.first!, $2)
}
}
func testRejected(file: StaticString = #file, line: UInt = #line, body: @escaping (Promise<UInt32>, XCTestExpectation, UInt32) -> Void) {
testRejected(withExpectationCount: 1, file: file, line: line) {
body($0, $1.first!, $2)
}
}
func testFulfilled(withExpectationCount: Int, file: StaticString = #file, line: UInt = #line, body: @escaping (Promise<UInt32>, [XCTestExpectation], UInt32) -> Void) {
let specify = mkspecify(withExpectationCount, file: file, line: line, body: body)
specify("already-fulfilled") { value in
return (.value(value), {})
}
specify("immediately-fulfilled") { value in
let (promise, seal) = Promise<UInt32>.pending()
return (promise, {
seal.fulfill(value)
})
}
specify("eventually-fulfilled") { value in
let (promise, seal) = Promise<UInt32>.pending()
return (promise, {
after(ticks: 5) {
seal.fulfill(value)
}
})
}
}
func testRejected(withExpectationCount: Int, file: StaticString = #file, line: UInt = #line, body: @escaping (Promise<UInt32>, [XCTestExpectation], UInt32) -> Void) {
let specify = mkspecify(withExpectationCount, file: file, line: line, body: body)
specify("already-rejected") { sentinel in
return (Promise(error: Error.sentinel(sentinel)), {})
}
specify("immediately-rejected") { sentinel in
let (promise, seal) = Promise<UInt32>.pending()
return (promise, {
seal.reject(Error.sentinel(sentinel))
})
}
specify("eventually-rejected") { sentinel in
let (promise, seal) = Promise<UInt32>.pending()
return (promise, {
after(ticks: 50) {
seal.reject(Error.sentinel(sentinel))
}
})
}
}
/////////////////////////////////////////////////////////////////////////
private func mkspecify(_ numberOfExpectations: Int, file: StaticString, line: UInt, body: @escaping (Promise<UInt32>, [XCTestExpectation], UInt32) -> Void) -> (String, _ feed: (UInt32) -> (Promise<UInt32>, () -> Void)) -> Void {
return { desc, feed in
let value = arc4random()
let (promise, executeAfter) = feed(value)
let expectations = (1...numberOfExpectations).map {
self.expectation(description: "\(desc) (\($0))")
}
body(promise, expectations, value)
executeAfter()
self.waitForExpectations(timeout: timeout) { err in
if let _ = err {
XCTFail("timed out: \(desc)", file: file, line: line)
}
}
}
}
func mkex() -> XCTestExpectation {
return expectation(description: "")
}
}
func after(ticks: Int, execute body: @escaping () -> Void) {
precondition(ticks > 0)
var ticks = ticks
func f() {
DispatchQueue.main.async {
ticks -= 1
if ticks == 0 {
body()
} else {
f()
}
}
}
f()
}
extension Promise {
func test(onFulfilled: @escaping () -> Void, onRejected: @escaping () -> Void) {
tap { result in
switch result {
case .fulfilled:
onFulfilled()
case .rejected:
onRejected()
}
}.silenceWarning()
}
}
prefix func ++(a: inout Int) -> Int {
a += 1
return a
}
extension Promise {
func silenceWarning() {}
}
#if os(Linux)
import func Glibc.random
func arc4random() -> UInt32 {
return UInt32(random())
}
extension XCTestExpectation {
func fulfill() {
fulfill(#file, line: #line)
}
}
extension XCTestCase {
func wait(for: [XCTestExpectation], timeout: TimeInterval, file: StaticString = #file, line: UInt = #line) {
#if !(swift(>=4.0) && !swift(>=4.1))
let line = Int(line)
#endif
waitForExpectations(timeout: timeout, file: file, line: line)
}
}
#endif

View File

@ -1,26 +0,0 @@
import PromiseKit
import XCTest
class Test212: XCTestCase {
func test() {
describe("2.1.2.1: When fulfilled, a promise: must not transition to any other state.") {
testFulfilled { promise, expectation, _ in
promise.test(onFulfilled: expectation.fulfill, onRejected: { XCTFail() })
}
specify("trying to fulfill then immediately reject") { d, expectation in
d.promise.test(onFulfilled: expectation.fulfill, onRejected: { XCTFail() })
d.fulfill()
d.reject(Error.dummy)
}
specify("trying to fulfill then reject, delayed") { d, expectation in
d.promise.test(onFulfilled: expectation.fulfill, onRejected: { XCTFail() })
after(ticks: 1) {
d.fulfill()
d.reject(Error.dummy)
}
}
}
}
}

View File

@ -1,34 +0,0 @@
import PromiseKit
import XCTest
class Test213: XCTestCase {
func test() {
describe("2.1.3.1: When rejected, a promise: must not transition to any other state.") {
testRejected { promise, expectation, _ in
promise.test(onFulfilled: { XCTFail() }, onRejected: expectation.fulfill)
}
specify("trying to reject then immediately fulfill") { d, expectation in
d.promise.test(onFulfilled: { XCTFail() }, onRejected: expectation.fulfill)
d.reject(Error.dummy)
d.fulfill()
}
specify("trying to reject then fulfill, delayed") { d, expectation in
d.promise.test(onFulfilled: { XCTFail() }, onRejected: expectation.fulfill)
after(ticks: 1) {
d.reject(Error.dummy)
d.fulfill()
}
}
specify("trying to reject immediately then fulfill delayed") { d, expectation in
d.promise.test(onFulfilled: { XCTFail() }, onRejected: expectation.fulfill)
d.reject(Error.dummy)
after(ticks: 1) {
d.fulfill()
}
}
}
}
}

View File

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

View File

@ -1,93 +0,0 @@
import PromiseKit
import XCTest
class Test223: XCTestCase {
func test() {
describe("2.2.3: If `onRejected` is a function,") {
describe("2.2.3.1: it must be called after `promise` is rejected, with `promise`s rejection reason as its first argument.") {
testRejected { promise, expectation, sentinel in
promise.catch { error in
if case Error.sentinel(let value) = error {
XCTAssertEqual(value, sentinel)
} else {
XCTFail()
}
expectation.fulfill()
}
}
}
describe("2.2.3.2: it must not be called before `promise` is rejected") {
specify("rejected after a delay") { d, expectation in
var called = false
d.promise.catch { _ in
called = true
expectation.fulfill()
}
after(ticks: 1) {
XCTAssertFalse(called)
d.reject(Error.dummy)
}
}
specify("never rejected") { d, expectation in
d.promise.catch { _ in XCTFail() }
after(ticks: 1, execute: expectation.fulfill)
}
}
describe("2.2.3.3: it must not be called more than once.") {
specify("already-rejected") { d, expectation in
var timesCalled = 0
Promise<Int>(error: Error.dummy).catch { _ in
XCTAssertEqual(++timesCalled, 1)
}
after(ticks: 2) {
XCTAssertEqual(timesCalled, 1)
expectation.fulfill()
}
}
specify("trying to reject a pending promise more than once, immediately") { d, expectation in
d.promise.catch{_ in expectation.fulfill() }
d.reject(Error.dummy)
d.reject(Error.dummy)
}
specify("trying to reject a pending promise more than once, delayed") { d, expectation in
d.promise.catch{_ in expectation.fulfill() }
after(ticks: 1) {
d.reject(Error.dummy)
d.reject(Error.dummy)
}
}
specify("trying to reject a pending promise more than once, immediately then delayed") { d, expectation in
d.promise.catch{_ in expectation.fulfill() }
d.reject(Error.dummy)
after(ticks: 1) {
d.reject(Error.dummy)
}
}
specify("when multiple `then` calls are made, spaced apart in time") { d, expectation in
let mk = { self.expectation(description: "") }
let ex = (expectation, mk(), mk(), mk())
do {
d.promise.catch{ _ in ex.0.fulfill() }
}
after(ticks: 1) {
d.promise.catch{ _ in ex.1.fulfill() }
}
after(ticks: 2) {
d.promise.catch{ _ in ex.2.fulfill() }
}
after(ticks: 3) {
d.reject(Error.dummy)
ex.3.fulfill()
}
}
specify("when `then` is interleaved with rejection") { d, expectation in
let ex = (expectation, self.expectation(description: ""))
d.promise.catch{ _ in ex.0.fulfill() }
d.reject(Error.dummy)
d.promise.catch{ _ in ex.1.fulfill() }
}
}
}
}
}

View File

@ -1,146 +0,0 @@
import PromiseKit
import XCTest
class Test224: XCTestCase {
func test() {
describe("2.2.4: `onFulfilled` or `onRejected` must not be called until the execution context stack contains only platform code.") {
describe("`then` returns before the promise becomes fulfilled or rejected") {
testFulfilled { promise, expectation, dummy in
var thenHasReturned = false
promise.done { _ in
XCTAssert(thenHasReturned)
expectation.fulfill()
}.silenceWarning()
thenHasReturned = true
}
testRejected { promise, expectation, memo in
var catchHasReturned = false
promise.catch { _->() in
XCTAssert(catchHasReturned)
expectation.fulfill()
}
catchHasReturned = true
}
}
describe("Clean-stack execution ordering tests (fulfillment case)") {
specify("when `onFulfilled` is added immediately before the promise is fulfilled") { d, expectation in
var onFulfilledCalled = false
d.promise.done {
onFulfilledCalled = true
expectation.fulfill()
}.silenceWarning()
d.fulfill()
XCTAssertFalse(onFulfilledCalled)
}
specify("when `onFulfilled` is added immediately after the promise is fulfilled") { d, expectation in
var onFulfilledCalled = false
d.fulfill()
d.promise.done {
onFulfilledCalled = true
expectation.fulfill()
}.silenceWarning()
XCTAssertFalse(onFulfilledCalled)
}
specify("when one `onFulfilled` is added inside another `onFulfilled`") { _, expectation in
var firstOnFulfilledFinished = false
let promise = Promise()
promise.done {
promise.done {
XCTAssertTrue(firstOnFulfilledFinished)
expectation.fulfill()
}.silenceWarning()
firstOnFulfilledFinished = true
}.silenceWarning()
}
specify("when `onFulfilled` is added inside an `onRejected`") { _, expectation in
let promise1 = Promise<Void>(error: Error.dummy)
let promise2 = Promise()
var firstOnRejectedFinished = false
promise1.catch { _ in
promise2.done {
XCTAssertTrue(firstOnRejectedFinished)
expectation.fulfill()
}.silenceWarning()
firstOnRejectedFinished = true
}
}
specify("when the promise is fulfilled asynchronously") { d, expectation in
var firstStackFinished = false
after(ticks: 1) {
d.fulfill()
firstStackFinished = true
}
d.promise.done {
XCTAssertTrue(firstStackFinished)
expectation.fulfill()
}.silenceWarning()
}
}
describe("Clean-stack execution ordering tests (rejection case)") {
specify("when `onRejected` is added immediately before the promise is rejected") { d, expectation in
var onRejectedCalled = false
d.promise.catch { _ in
onRejectedCalled = true
expectation.fulfill()
}
d.reject(Error.dummy)
XCTAssertFalse(onRejectedCalled)
}
specify("when `onRejected` is added immediately after the promise is rejected") { d, expectation in
var onRejectedCalled = false
d.reject(Error.dummy)
d.promise.catch { _ in
onRejectedCalled = true
expectation.fulfill()
}
XCTAssertFalse(onRejectedCalled)
}
specify("when `onRejected` is added inside an `onFulfilled`") { d, expectation in
let promise1 = Promise()
let promise2 = Promise<Void>(error: Error.dummy)
var firstOnFulfilledFinished = false
promise1.done { _ in
promise2.catch { _ in
XCTAssertTrue(firstOnFulfilledFinished)
expectation.fulfill()
}
firstOnFulfilledFinished = true
}.silenceWarning()
}
specify("when one `onRejected` is added inside another `onRejected`") { d, expectation in
let promise = Promise<Void>(error: Error.dummy)
var firstOnRejectedFinished = false;
promise.catch { _ in
promise.catch { _ in
XCTAssertTrue(firstOnRejectedFinished)
expectation.fulfill()
}
firstOnRejectedFinished = true
}
}
specify("when the promise is rejected asynchronously") { d, expectation in
var firstStackFinished = false
after(ticks: 1) {
d.reject(Error.dummy)
firstStackFinished = true
}
d.promise.catch { _ in
XCTAssertTrue(firstStackFinished)
expectation.fulfill()
}
}
}
}
}
}

View File

@ -1,275 +0,0 @@
import PromiseKit
import XCTest
class Test226: XCTestCase {
func test() {
describe("2.2.6: `then` may be called multiple times on the same promise.") {
describe("2.2.6.1: If/when `promise` is fulfilled, all respective `onFulfilled` callbacks must execute in the order of their originating calls to `then`.") {
describe("multiple boring fulfillment handlers") {
testFulfilled(withExpectationCount: 4) { promise, exes, sentinel -> Void in
var orderValidator = 0
promise.done {
XCTAssertEqual($0, sentinel)
XCTAssertEqual(++orderValidator, 1)
exes[0].fulfill()
}.silenceWarning()
promise.catch { _ in XCTFail() }
promise.done {
XCTAssertEqual($0, sentinel)
XCTAssertEqual(++orderValidator, 2)
exes[1].fulfill()
}.silenceWarning()
promise.catch { _ in XCTFail() }
promise.done {
XCTAssertEqual($0, sentinel)
XCTAssertEqual(++orderValidator, 3)
exes[2].fulfill()
}.silenceWarning()
promise.catch { _ in XCTFail() }
promise.done {
XCTAssertEqual($0, sentinel)
XCTAssertEqual(++orderValidator, 4)
exes[3].fulfill()
}.silenceWarning()
}
}
describe("multiple fulfillment handlers, one of which throws") {
testFulfilled(withExpectationCount: 4) { promise, exes, sentinel in
var orderValidator = 0
promise.done {
XCTAssertEqual($0, sentinel)
XCTAssertEqual(++orderValidator, 1)
exes[0].fulfill()
}.silenceWarning()
promise.catch { _ in XCTFail() }
promise.done {
XCTAssertEqual($0, sentinel)
XCTAssertEqual(++orderValidator, 2)
exes[1].fulfill()
}.silenceWarning()
promise.catch { _ in XCTFail() }
promise.done {
XCTAssertEqual($0, sentinel)
XCTAssertEqual(++orderValidator, 3)
exes[2].fulfill()
throw Error.dummy
}.silenceWarning()
promise.catch { value in XCTFail() }
promise.done {
XCTAssertEqual($0, sentinel)
XCTAssertEqual(++orderValidator, 4)
exes[3].fulfill()
}.silenceWarning()
}
}
describe("results in multiple branching chains with their own fulfillment values") {
testFulfilled(withExpectationCount: 3) { promise, exes, memo in
let sentinel1 = 671
let sentinel2: UInt32 = 672
let sentinel3 = 673
promise.map { _ in
return sentinel1
}.done { value in
XCTAssertEqual(sentinel1, value)
exes[0].fulfill()
}.silenceWarning()
promise.done { _ in
throw Error.sentinel(sentinel2)
}.catch { err in
switch err {
case Error.sentinel(let err) where err == sentinel2:
break
default:
XCTFail()
}
exes[1].fulfill()
}
promise.map { _ in
sentinel3
}.done {
XCTAssertEqual($0, sentinel3)
exes[2].fulfill()
}.silenceWarning()
}
}
describe("`onFulfilled` handlers are called in the original order") {
testFulfilled(withExpectationCount: 3) { promise, exes, memo in
var orderValidator = 0
promise.done { _ in
XCTAssertEqual(++orderValidator, 1)
exes[0].fulfill()
}.silenceWarning()
promise.done { _ in
XCTAssertEqual(++orderValidator, 2)
exes[1].fulfill()
}.silenceWarning()
promise.done { _ in
XCTAssertEqual(++orderValidator, 3)
exes[2].fulfill()
}.silenceWarning()
}
}
describe("even when one handler is added inside another handler") {
testFulfilled(withExpectationCount: 3) { promise, exes, memo in
var x = 0
promise.done { _ in
XCTAssertEqual(x, 0)
x += 1
exes[0].fulfill()
promise.done { _ in
XCTAssertEqual(x, 2)
x += 1
exes[1].fulfill()
}.silenceWarning()
}.silenceWarning()
promise.done { _ in
XCTAssertEqual(x, 1)
x += 1
exes[2].fulfill()
}.silenceWarning()
}
}
}
describe("2.2.6.2: If/when `promise` is rejected, all respective `onRejected` callbacks must execute in the order of their originating calls to `then`.") {
describe("multiple boring rejection handlers") {
testRejected(withExpectationCount: 4) { promise, exes, sentinel in
var ticket = 0
promise.catch { err in
guard case Error.sentinel(let x) = err, x == sentinel else { return XCTFail() }
XCTAssertEqual(++ticket, 1)
exes[0].fulfill()
}
promise.done { _ in XCTFail() }.silenceWarning()
promise.catch { err in
guard case Error.sentinel(let x) = err, x == sentinel else { return XCTFail() }
XCTAssertEqual(++ticket, 2)
exes[1].fulfill()
}
promise.done { _ in XCTFail() }.silenceWarning()
promise.catch { err in
guard case Error.sentinel(let x) = err, x == sentinel else { return XCTFail() }
XCTAssertEqual(++ticket, 3)
exes[2].fulfill()
}
promise.done { _ in XCTFail() }.silenceWarning()
promise.catch { err in
guard case Error.sentinel(let x) = err, x == sentinel else { return XCTFail() }
XCTAssertEqual(++ticket, 4)
exes[3].fulfill()
}
}
}
describe("multiple rejection handlers, one of which throws") {
testRejected(withExpectationCount: 4) { promise, exes, sentinel in
var orderValidator = 0
promise.catch { err in
guard case Error.sentinel(let x) = err, x == sentinel else { return XCTFail() }
XCTAssertEqual(++orderValidator, 1)
exes[0].fulfill()
}
promise.done { _ in XCTFail() }.silenceWarning()
promise.catch { err in
guard case Error.sentinel(let x) = err, x == sentinel else { return XCTFail() }
XCTAssertEqual(++orderValidator, 2)
exes[1].fulfill()
}
promise.done { _ in XCTFail() }.silenceWarning()
promise.recover { err -> Promise<UInt32> in
if case Error.sentinel(let x) = err {
XCTAssertEqual(x, sentinel)
} else {
XCTFail()
}
XCTAssertEqual(++orderValidator, 3)
exes[2].fulfill()
throw Error.dummy
}.silenceWarning()
promise.done { _ in XCTFail() }.silenceWarning()
promise.catch { err in
guard case Error.sentinel(let x) = err, x == sentinel else { return XCTFail() }
XCTAssertEqual(++orderValidator, 4)
exes[3].fulfill()
}
}
}
describe("results in multiple branching chains with their own fulfillment values") {
testRejected(withExpectationCount: 3) { promise, exes, memo in
let sentinel1 = arc4random()
let sentinel2 = arc4random()
let sentinel3 = arc4random()
promise.recover { _ in
return .value(sentinel1)
}.done { value in
XCTAssertEqual(sentinel1, value)
exes[0].fulfill()
}
promise.recover { _ -> Promise<UInt32> in
throw Error.sentinel(sentinel2)
}.catch { err in
if case Error.sentinel(let x) = err, x == sentinel2 {
exes[1].fulfill()
}
}
promise.recover { _ in
.value(sentinel3)
}.done { value in
XCTAssertEqual(value, sentinel3)
exes[2].fulfill()
}
}
}
describe("`onRejected` handlers are called in the original order") {
testRejected(withExpectationCount: 3) { promise, exes, memo in
var x = 0
promise.catch { _ in
XCTAssertEqual(x, 0)
x += 1
exes[0].fulfill()
}
promise.catch { _ in
XCTAssertEqual(x, 1)
x += 1
exes[1].fulfill()
}
promise.catch { _ in
XCTAssertEqual(x, 2)
x += 1
exes[2].fulfill()
}
}
}
describe("even when one handler is added inside another handler") {
testRejected(withExpectationCount: 3) { promise, exes, memo in
var x = 0
promise.catch { _ in
XCTAssertEqual(x, 0)
x += 1
exes[0].fulfill()
promise.catch { _ in
XCTAssertEqual(x, 2)
x += 1
exes[1].fulfill()
}
}
promise.catch { _ in
XCTAssertEqual(x, 1)
x += 1
exes[2].fulfill()
}
}
}
}
}
}
}

View File

@ -1,33 +0,0 @@
import PromiseKit
import XCTest
class Test227: XCTestCase {
func test() {
describe("2.2.7: `then` must return a promise: `promise2 = promise1.then(onFulfilled, onRejected)") {
describe("2.2.7.2: If either `onFulfilled` or `onRejected` throws an exception `e`, `promise2` must be rejected with `e` as the reason.") {
testFulfilled { promise1, expectation, _ in
let sentinel = arc4random()
let promise2 = promise1.done { _ in throw Error.sentinel(sentinel) }
promise2.catch {
if case Error.sentinel(let x) = $0, x == sentinel {
expectation.fulfill()
}
}
}
testRejected { promise1, expectation, _ in
let sentinel = arc4random()
let promise2 = promise1.recover { _ -> Promise<UInt32> in throw Error.sentinel(sentinel) }
promise2.catch { error in
if case Error.sentinel(let x) = error, x == sentinel {
expectation.fulfill()
}
}
}
}
}
}
}

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