Compare commits

..

11 Commits

Author SHA1 Message Date
Max Howell
47f40ef75f
Update docs, fix broken links
Removes Chinese translations because they were far behind.

[ci skip]
2019-02-28 23:37:10 -05:00
Max Howell
dd77da78cb Test against Xcode 10.1 2018-12-27 09:55:29 +00:00
Max Howell
1001d23725 Test everything we claim to support 2018-06-04 19:30:20 -04:00
Max Howell
a92c40ca73 Update docs for Xcode 10 2018-06-04 19:25:02 -04:00
Max Howell
7fb12976da Fix Swift 4.2 warnings 2018-06-04 19:23:16 -04:00
Max Howell
99771a775e Update xcodeproj to Xcode 10 2018-06-04 19:23:02 -04:00
Max Howell
c70677a12b Update StoreKit extension; Tag 4.5.2 2018-01-29 11:06:23 -05:00
Max Howell
de638abca5 Update extensions; Tag 4.5.1 2018-01-05 18:17:35 -05:00
Max Howell
52697b25d4 Provide localizedDescription errors 2017-12-04 10:59:37 -05:00
Max Howell
6bab5e0c7f Add @discardableResult; Closes #595; Tag 4.5.0
PromiseKit 5 much improves this area with the concept of an unavailable promise (`Guarantee`).

Thus since we offer a superior library now, we will close this ticket.
2017-11-05 17:52:08 -05:00
Max Howell
d7a978d628 Update CoreLocation 2017-11-05 17:16:49 -05:00
159 changed files with 4720 additions and 16392 deletions

2
.github/FUNDING.yml vendored
View File

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

View File

@ -1,11 +1,6 @@
[PLEASE READ THE TROUBLESHOOTING GUIDE](https://github.com/mxcl/PromiseKit/blob/master/Documentation/Troubleshooting.md).
* PromiseKit version: **major version is enough unless you have a build issue**
* Xcode version: **only required for build issues**
* CocoaPods version: **only required for build issues**
* Carthage version: **only required for build issues**
---
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.
> Please format your code in triple backticks and delete this line before submitting your ticket. Failure to remove this line may result in mockery.

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

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

5
.gitignore vendored
View File

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

6
.gitmodules vendored
View File

@ -61,9 +61,3 @@
[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 +1,248 @@
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:
matrix:
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
- os: osx
language: objective-c
osx_image: xcode10.1
env: SWFT=4.2 PLAT=macOS
- os: osx
language: objective-c
osx_image: xcode10.1
env: SWFT=4.2 PLAT=iOS
- os: osx
language: objective-c
osx_image: xcode10.1
env: SWFT=4.2 PLAT=tvOS
- os: osx
language: objective-c
osx_image: xcode10.1
env: SWFT=4.2 PLAT=watchOS
- os: osx
language: objective-c
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
env: SWFT=4.1 PLAT=macOS
- os: osx
language: objective-c
osx_image: xcode9.3
env: SWFT=4.1 PLAT=iOS
- os: osx
language: objective-c
osx_image: xcode9.3
env: SWFT=4.1 PLAT=tvOS
- os: osx
language: objective-c
osx_image: xcode9.3
env: SWFT=4.1 PLAT=watchOS
- os: osx
language: objective-c
osx_image: xcode9.3
env: SWFT=3.2 PLAT=macOS
- os: osx
language: objective-c
osx_image: xcode9.3
env: SWFT=3.2 PLAT=iOS
- os: osx
language: objective-c
osx_image: xcode9.3
env: SWFT=3.2 PLAT=tvOS
- os: osx
language: objective-c
osx_image: xcode9.3
env: SWFT=3.2 PLAT=watchOS
- &pod
stage: lint
- os: osx
language: objective-c
osx_image: xcode9.2
env: SWFT=4.0 PLAT=macOS
- os: osx
language: objective-c
osx_image: xcode9.2
env: SWFT=4.0 PLAT=iOS
- os: osx
language: objective-c
osx_image: xcode9.2
env: SWFT=4.0 PLAT=tvOS
- os: osx
language: objective-c
osx_image: xcode9.2
env: SWFT=4.0 PLAT=watchOS
- os: osx
language: objective-c
osx_image: xcode9.2
env: SWFT=3.2 PLAT=macOS
- os: osx
language: objective-c
osx_image: xcode9.2
env: SWFT=3.2 PLAT=iOS
- os: osx
language: objective-c
osx_image: xcode9.2
env: SWFT=3.2 PLAT=tvOS
- os: osx
language: objective-c
osx_image: xcode9.2
env: SWFT=3.2 PLAT=watchOS
- os: osx
language: objective-c
osx_image: xcode9.1
env: SWFT=4.0 PLAT=macOS
- os: osx
language: objective-c
osx_image: xcode9.1
env: SWFT=4.0 PLAT=iOS
- os: osx
language: objective-c
osx_image: xcode9.1
env: SWFT=4.0 PLAT=tvOS
- os: osx
language: objective-c
osx_image: xcode9.1
env: SWFT=4.0 PLAT=watchOS
- os: osx
language: objective-c
osx_image: xcode9.1
env: SWFT=3.2 PLAT=macOS
- os: osx
language: objective-c
osx_image: xcode9.1
env: SWFT=3.2 PLAT=iOS
- os: osx
language: objective-c
osx_image: xcode9.1
env: SWFT=3.2 PLAT=tvOS
- os: osx
language: objective-c
osx_image: xcode9.1
env: SWFT=3.2 PLAT=watchOS
- os: osx
language: objective-c
osx_image: xcode9.0
env: SWFT=4.0 PLAT=macOS
- os: osx
language: objective-c
osx_image: xcode9.0
env: SWFT=4.0 PLAT=iOS
- os: osx
language: objective-c
osx_image: xcode9.0
env: SWFT=4.0 PLAT=tvOS
- os: osx
language: objective-c
osx_image: xcode9.0
env: SWFT=4.0 PLAT=watchOS
- os: osx
language: objective-c
osx_image: xcode9.0
env: SWFT=3.2 PLAT=macOS
- os: osx
language: objective-c
osx_image: xcode9.0
env: SWFT=3.2 PLAT=iOS
- os: osx
language: objective-c
osx_image: xcode9.0
env: SWFT=3.2 PLAT=tvOS
- os: osx
language: objective-c
osx_image: xcode9.0
env: SWFT=3.2 PLAT=watchOS
- os: osx
language: objective-c
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
env: SWFT=3.1 PLAT=macOS
- os: osx
language: objective-c
osx_image: xcode8.3
env: SWFT=3.1 PLAT=iOS
- os: osx
language: objective-c
osx_image: xcode8.3
env: SWFT=3.1 PLAT=tvOS
- os: osx
language: objective-c
osx_image: xcode8.3
env: SWFT=3.1 PLAT=watchOS
- &linux
stage: swiftpm
env: SWIFT_BUILD_VERSION=3 SWIFT_VERSION=4.0.3
name: Linux / Swift 3.2
os: linux
- os: osx
language: objective-c
osx_image: xcode8.2
env: SWFT=3.0 PLAT=macOS
- os: osx
language: objective-c
osx_image: xcode8.2
env: SWFT=3.0 PLAT=iOS
- os: osx
language: objective-c
osx_image: xcode8.2
env: SWFT=3.0 PLAT=tvOS
- os: osx
language: objective-c
osx_image: xcode8.2
env: SWFT=3.0 PLAT=watchOS
- os: osx
language: objective-c
osx_image: xcode8.1
env: SWFT=3.0 PLAT=macOS
- os: osx
language: objective-c
osx_image: xcode8.1
env: SWFT=3.0 PLAT=iOS
- os: osx
language: objective-c
osx_image: xcode8.1
env: SWFT=3.0 PLAT=tvOS
- os: osx
language: objective-c
osx_image: xcode8.1
env: SWFT=3.0 PLAT=watchOS
- os: linux
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
sudo: required
services: docker
env: DOCKER_IMAGE=swift:3.1
#TODO please help us test Linux with Swift 3.0, 3.2 and 4.0
- &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
before_install:
- if [ "$TRAVIS_OS_NAME" == "linux" ]; then
docker pull $DOCKER_IMAGE;
fi;
case $PLAT in
iOS)
NAME="iPhone SE";;
tvOS)
NAME="Apple TV 1080p";;
watchOS)
NAME="Apple Watch - 38mm";;
esac;
if [ -n "$NAME" ]; then
export UUID=$(instruments -s | ruby -e "ARGF.each_line{ |ln| ln =~ /$NAME .* \[(.*)\]/; if \$1; puts(\$1); exit; end }");
fi
script:
- set -o pipefail;
case $PLAT in
macOS)
xcodebuild -scheme PromiseKit -quiet build SWIFT_VERSION=$SWFT -enableCodeCoverage YES | xcpretty;
xcodebuild -scheme PromiseKit -quiet test;;
iOS|tvOS)
open -b com.apple.iphonesimulator --args -CurrentDeviceUDID "$UUID";
xcodebuild -scheme PromiseKit -quiet -destination "id=$UUID" build SWIFT_VERSION=$SWFT -enableCodeCoverage YES | xcpretty;
xcodebuild -scheme PromiseKit -quiet -destination "id=$UUID" test;;
watchOS)
xcodebuild -scheme PromiseKit -quiet -destination "id=$UUID" -quiet clean build SWIFT_VERSION=$SWFT | xcpretty;;
*)
docker-compose run PromiseKit;;
esac
after_success:
- if [ "$TRAVIS_OS_NAME" == "osx" ]; then
bash <(curl -s https://codecov.io/bash);
fi

View File

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

View File

@ -1,13 +1,13 @@
# Common Patterns
One feature of promises that makes them 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.
One feature of promises that makes them so useful is that thet are composable;
enabling complex, yet safe asynchronous patterns that would otherwise be quite
intimidating with traditional methods.
## Chaining
The most common pattern is chaining:
The most common pattern with promises is chaining:
```swift
firstly {
@ -18,50 +18,44 @@ firstly {
set($0)
return animate()
}.ensure {
// something that should happen whatever the outcome
cleanup()
}.catch {
handle(error: $0)
}
```
If you return a promise in a `then`, the next `then` *waits* on that promise
If you return a promise in a `then` the next `then` *waits* on that promise
before continuing. This is the essence of promises.
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
Composing promises is easy, and they thus encourage you to develop great apps
without fear for the typical spaghetti (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:
Promises are composable, return them instead of providing 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)
return URLSession.shared.dataTask(url).asDictionary().then { dict in
return User(dict: dict)
}
}
func avatar() -> Promise<UIImage> {
return user().then { user in
URLSession.shared.dataTask(.promise, with: user.imageUrl)
}.compactMap {
UIImage(data: $0.data)
URLSession.shared.dataTask(user.imageUrl)
}.then {
UIImage(data: $0)
}
}
}
```
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!
This way, your asynchronous systems can easily be engaged in chains all over
your apps.
## Background Work
@ -70,39 +64,31 @@ your app without violating architectural boundaries.
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) {
return user().then(on: bgq) { user in
URLSession.shared.dataTask(user.imageUrl)
}.then(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.
All PromiseKit handlers take an `on` parameter allowing you to choose the queue
the handler executes upon. 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:
If an error occurs mid chain, simply throw:
```swift
firstly {
foo()
}.then { baz in
bar(baz)
foo().then { baz in
return bar(baz)
}.then { result in
guard !result.isBad else { throw MyError.myIssue }
if result.isBad { throw MyError.myIssue }
//…
return doOtherThing()
}
@ -110,28 +96,22 @@ firstly {
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:
Thus if you call a throwing function, you don't have to wrap it in a `do`:
```swift
foo().then { baz in
bar(baz)
return bar(baz)
}.then { result in
try doOtherThing()
return try doOtherThing()
}.catch { error in
// if doOtherThing() throws, we end up here
}
```
> *Tip*: Swift lets you define an inline `enum Error` inside the function you
are working on. This isnt *great* coding practice, but it's better than
avoiding throwing an error because you couldn't be bothered to define a good global
`Error` `enum`.
## Abstracting Away Asychronicity
## Abstracting Away Asynchronicity
```swift
```switch
var fetch = API.fetch()
override func viewDidAppear() {
@ -146,9 +126,9 @@ func buttonPressed() {
}
}
func refresh() -> Promise {
func refresh() {
// ensure only one fetch operation happens at a time
if fetch.isResolved {
startSpinner()
fetch = API.fetch().ensure {
@ -159,11 +139,11 @@ func refresh() -> Promise {
}
```
With promises, you dont need to worry about *when* your asynchronous operation
finishes. Just act like it already has.
With promises you dont need to worry about *when* your asynchronous operation
finishes: 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.
> Above we can see that you can call `then` as many times on a promise as you
> like, they will all be executed in the order they were added.
## Chaining Sequences
@ -173,40 +153,23 @@ When you have a series of tasks to perform on an array of data:
```swift
// fade all visible table cells one by one in a “cascading” effect
var fade = Guarantee()
let fade = Promise()
for cell in tableView.visibleCells {
fade = fade.then {
UIView.animate(.promise, duration: 0.1) {
UIView.promise(animateWithDuration:0.1) {
cell.alpha = 0
}
}
}
fade.done {
// finish
fade.then {
//finish
}
```
Or if you have an array of closures that return promises:
Note *usually* you want `when()` since `when` executes all the promises in
parallel and thus is much faster to complete. Use the above pattern in
situations where tasks *must* be done sequentially; animation is a good example.
```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
@ -219,120 +182,96 @@ race(when(fulfilled: fetches).asVoid(), timeout).then {
}
```
`race` continues as soon as one of the promises it is watching finishes.
`race` continues as soon as one of the promises it watches 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()`.
> Common pitfalls: ensure the promises you pass to `race` are the same type.
> The easiest way to ensure this is using `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.
> Please note if any promise you pass rejects, then `race` will be rejected.
## Cancellation
Promises dont have a `cancel` function, but they do support cancellation through a
Promises dont have a `cancel` function, but they do support cancellation via 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)
let promise = Promise<Void> { fulfill, reject in
let task = Task(…)
let cancel = {
cancelme = true
task.cancel()
reject(NSError.cancelledError)
}
task.completion = { value in
guard !cancelme else { reject(NSError.cancelledError) }
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.
> Promises dont have a cancel function because you dont want code outside of
> your control to be able to cancel your operations *unless* you explicitly want
> that. In cases where you want it, then it varies how it should work depending
> on how the underlying task supports cancellation. Thus we have provided
> primitives but not concrete API.
Cancelled chains do not call `catch` handlers by default. However you can
Cancelled chains do not call a `catch` handler by default. However you can
intercept cancellation if you like:
```swift
foo.then {
//…
}.catch(policy: .allErrors) {
}.catch(policy: .allErrorsIncludingCancellation) {
// 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
**Important**, canceling the chain is *not* the same as canceling the underlying
asynchronous task. Promises are a wrapper around asynchronicity but they have no
control over the underlying tasks. If you need to cancel the underlying task you
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> {
func attempt<T>(interdelay: DispatchTimeInterval = .seconds(2), maxRepeat: Int = 3, 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)
guard attempts < maxRepeat else { throw error }
return after(interval: interdelay).then {
return attempt()
}
}
}
return attempt()
}
attempt(maximumRetryCount: 3) {
flakeyTask(parameters: foo)
}.then {
attempt{ flakeyTask() }.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
Probably you should supplement the above so that you only re-attempt 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
Be careful with Promises and delegate systems as they are not always suited.
Promises complete *once* where most delegate systems call their callbacks many
times. This is why, for example, there is no PromiseKit extension for a
`UIButton`.
@ -347,7 +286,7 @@ extension CLLocationManager {
}
class PMKCLLocationManagerProxy: NSObject, CLLocationManagerDelegate {
private let (promise, seal) = Promise<[CLLocation]>.pending()
private let (promise, fulfill, reject) = Promise<[CLLocation]>.pending()
private var retainCycle: PMKCLLocationManagerProxy?
private let manager = CLLocationManager()
@ -355,19 +294,19 @@ class PMKCLLocationManagerProxy: NSObject, CLLocationManagerDelegate {
super.init()
retainCycle = self
manager.delegate = self // does not retain hence the `retainCycle` property
promise.ensure {
// ensure we break the retain cycle
self.retainCycle = nil
}
}
@objc fileprivate func locationManager(_: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
seal.fulfill(locations)
fulfill(locations)
}
@objc func locationManager(_: CLLocationManager, didFailWithError error: Error) {
seal.reject(error)
reject(error)
}
}
@ -380,49 +319,49 @@ CLLocationManager.promise().then { locations in
}
```
> Please note: we provide this promise with our CoreLocation extensions at
> Please note, we provide this promise with our CoreLocation extensions at
> https://github.com/PromiseKit/CoreLocation
## Recovery
Sometimes you dont want an error to cascade. Instead, you want to supply a default result:
Sometimes you dont want an error to cascade, instead you have a default value:
```swift
CLLocationManager.requestLocation().recover { error -> Promise<CLLocation> in
```
CLLocationManager.promise().recover { error -> CLLocation in
guard error == MyError.airplaneMode else {
throw error
}
return .value(CLLocation.savannah)
}.done { location in
return CLLocation.savannah
}.then { location in
//…
}
```
Be careful not to ignore all errors, though! Recover only those errors that make sense to recover.
Be careful not to ignore all errors; recover only those errors that make sense.
## Promises for Modal View Controllers
## Promises for modal view-controllers
```swift
class ViewController: UIViewController {
private let (promise, seal) = Guarantee<…>.pending() // use Promise if your flow can fail
private let (promise, seal) = Promise<…>.pending()
func show(in: UIViewController) -> Promise<…> {
in.show(self, sender: in)
return promise
}
func done() {
dismiss(animated: true)
seal.fulfill(…)
seal(…)
}
}
// use:
ViewController().show(in: self).done {
ViewController().show(in: self).then {
//…
}.catch { error in
//…
@ -430,13 +369,13 @@ ViewController().show(in: self).done {
```
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
presentee to control the presentation and the presentee to be dismiss itself
explicitly.
Nothing seems to beat storyboard segues for decoupling an app's controllers.
Nothing seemingly can beat Storyboard segues for decoupling an app's router.
## Saving Previous Results
## Saving previous results
Lets say you have:
@ -444,51 +383,49 @@ Lets say you have:
```swift
login().then { username in
fetch(avatar: username)
}.done { image in
}.then { image in
//…
}
```
What if you want access to both `username` and `image` in your `done`?
In the second `then` how can you access `username` as well as `image`?
The most obvious way is to use nesting:
The most obvious way is with nesting:
```swift
login().then { username in
fetch(avatar: username).done { image in
// we have access to both `image` and `username`
fetch(avatar: username).then { image in
// we have image and username
}
}.done {
}.then {
// the chain still continues as you'd expect
}
```
However, such nesting reduces the clarity of the chain. Instead, we could use Swift
tuples:
However you could instead use Swift tuples:
```swift
login().then { username in
fetch(avatar: username).map { ($0, username) }
fetch(avatar: username).then { ($0, username) }
}.then { image, username in
//…
}
```
The code above simply maps `Promise<String>` into `Promise<(UIImage, String)>`.
The above is a quick transforming `then` that simply maps the `Promise<String>`
into `Promise<(UIImage, String)>`.
## Waiting on Multiple Promises, Whatever Their Result
## Waiting on multiple promises whatever their result
Use `when(resolved:)`:
```swift
when(resolved: a, b).done { (results: [Result<T>]) in
when(resolved: a, b).then { (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.
Generally you don't want this, people ask for it a lot, but usually they
actually just want to use `recover` on one of the promises. Usually you don't
want to ignore errors. Errors happen, they should be handled.

View File

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

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,87 +1,31 @@
# 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
Generally no, provided the promise completes then all handlers are released thus
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.
This does mean that if your chain contains side-effects that you would typically
not want to happen after, say, a view controller is popped then you should still
use `weak self` (and check for `self == nil`) to prevent any such side-effects.
*However*, 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.
[This stackoverflow question](https://stackoverflow.com/questions/39281214/should-i-use-weak-self-in-promisekit-blocks)
has some good discussion on the topic.
## 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.
`catch` deliberately terminates the chain, you should place low in your promise
heirarchy: at as root a point as possible. Typically this would be your view
controllers where your `catch` can then display a message to the user.
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 means you should be writing one catch for many `then`s and be returning
promises without there being `catch` handlers.
This is obviously a guideline; do what is necessary.
This is obviously a guideline, do what is necessary.
## How do branched chains work?
Suppose you have a promise:
If you have a promise:
```
let promise = foo()
@ -99,12 +43,12 @@ promise.then {
}
```
You now have a branched chain. When `promise` resolves, both chains receive its
value. However, the two chains are entirely separate and Swift will prompt you
to ensure that both have `catch` handlers.
You now have a branched chain. When `promise` resolves both chains receive its
value. However the two chains are entirely separate and Swift will prompt you
to ensure 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.
Probably however you can ignore the catch for one, but be careful in these
situations as Swift cannot help you ensure your chains are error handled.
```
promise.then {
@ -136,23 +80,22 @@ when(fulfilled: p1, p2).catch { error in
}
```
> It's worth noting that you can add multiple `catch` handlers to a promise, too.
> And indeed, both will be called if the chain is rejected.
> It's worth noting that you can add multiple `catch` handlers to a promise too,
> and indeed, both will be called if the chain is rejected.
## Is PromiseKit “heavy”?
No. PromiseKit 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)
No, PromiseKit is hardly any sources in fact, it is “light-weight”. Any
“weight” relative to other promise implementations is 6 years of bug fixes or
important things like [Zalgo prevention](http://blog.izs.me/post/59142742143/designing-apis-for-asynchrony)
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.
Because promises always execute via `dispatch` the backtraces you get have less
information than is often required to trace the path of execution.
One solution is to turn off dispatch during debugging:
One solution is (during debugging) to turn off the dispatch:
```swift
// Swift
@ -162,22 +105,19 @@ DispatchQueue.default = zalgo
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).
Dont leave this is on, we always dispatch to avoid you accidentally writing
a common bug pattern: http://blog.izs.me/post/59142742143/designing-apis-for-asynchrony
## 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.
Some promise libraries provide `all`, we provide `when`, it is the same. `when`
was chosen as it is the more common choice which we also think reads better.
## 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.
@ -185,68 +125,24 @@ 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.
By default PromiseKit handlers run on the `main` thread, which is serial, so
typically you 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.
`Promise<T>` is generic and and thus cannot be represented by Objective-C.
## Does PromiseKit conform to Promises/A+?
Yes. We have tests that prove this.
Yes, we have tests that prove this.
## How do PromiseKit and RxSwift/ReactiveSwift differ?
## How do PromiseKit and RxSwift differ?
PromiseKit is a lot simpler.
https://github.com/mxcl/PromiseKit/issues/484
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.
## Why cant I return from a catch like I can in Javascript?
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:
Swift demands functions with one purpose, thus we have two error handlers:
* `catch`: ends the chain and handles errors
* `recover`: attempts to recover from errors in a chain
@ -256,42 +152,35 @@ You want `recover`.
## When do promises “start”?
Often people are confused about when Promises “start”. Is it immediately? Is it
later? Is it when you call `then`?
later? Is it when you call then?
The answer is: promises do not choose when the underlying task they represent
starts. That is up to that task. For example here is the code for a simple
promise that wraps Alamofire:
The answer is: The promise **body** executes during initialization of the promise, on the current thread.
As an example, `"Executing the promise body"` will be printed to the console right after the promise is created,
without having to call `then` on the promise.
```swift
let testPromise = Promise<Bool> {
print("Executing the promise body.")
return $0.fulfill(true)
}
```
But what about asynchronous tasks that you create in your promise's body? They behave the same way as they would
without using PromiseKit. Here's a simple example:
```swift
let testPromise = Promise<Bool> { seal in
print("Executing the promise body.")
DispatchQueue.main.asyncAfter(deadline: .now() + 3.0) {
print("Executing asyncAfter.")
return seal.fulfill(true)
func foo() -> Promise<Any>
return Promise { fulfill, reject in
Alamofire.request(rq).responseJSON { rsp in
if let error = rsp.error {
reject(error)
} else {
fulfill(rsp.value)
}
}
}
}
```
The message `"Executing the promise body."` is being logged right away, but the message `"Executing asyncAfter."`
is only logged three seconds later. In this case `DispatchQueue` is responsible for deciding when to execute
the task you pass to it, PromiseKit has nothing to do with it.
Who chooses when this promise starts? The answer is: Alamofire does and in this
case, it “starts” immediately when `foo()` is called.
## What is a good way to use Firebase with PromiseKit
There is no good way to use Firebase with PromiseKit. See the next question for
a more detailed rationale.
There is no good way to use Firebase with PromiseKit. See the next question for rationale.
The best option is to embed your chain in your Firebase handler:
The best option is to embed your chain in your firebase handler:
```
foo.observe(.value) { snapshot in
@ -307,91 +196,9 @@ foo.observe(.value) { snapshot in
}
```
## 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 }
```
Then were afraid that you cannot use PromiseKit for that event. Promises only resolve `once`, this is the fundamental nature of promises and is considered a feature since it gives you guarantees about the flow of your chains.
## My question was not answered

View File

@ -1,4 +1,4 @@
# `then` and `done`
# `then`
Here is a typical promise chain:
@ -7,18 +7,18 @@ firstly {
login()
}.then { creds in
fetch(avatar: creds.user)
}.done { image in
}.then { image in
self.imageView = image
}
```
If this code used completion handlers, it would look like this:
If this code used completion handlers it would look like this:
```swift
login { creds, error in
if let creds = creds {
fetch(avatar: creds.user) { image, error in
if let image = image {
if let image = image else {
self.imageView = image
}
}
@ -26,77 +26,63 @@ login { creds, error in
}
```
`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.
`then` *is* just another way to do completion handlers, but it is also quite a bit more. At this
initial stage of our understanding it merely helps readability. The promise chain above is easy
to read, one asynchronous operation leads into the other, read line by line. It's as close to
procedural code as we can easily get with the current state of Swift.
Lets compare the signatures of the two login methods:
```swift
func login() -> Promise<Creds>
// Compared with:
// compared with:
func login(completion: (Creds?, Error?) -> Void)
// ^^ ugh. Optionals. Double optionals.
```
The distinction is that with promises, your functions 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.
The distinction is that with promises your functions returns *promises*. So for each handler in our
chain we return a promise. By doing this we can call `then`. Each `then` waits on its promise, so the
chains resolve procedurally, one at a time.
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.
A Promise represents the future value of an asynchronous task. It has a type
that represents the type of object it wraps. In the above example `login` is a
function that returns a `Promise` that *will* represent an instance of `Creds`.
---
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.
You may notice that unlike the completion pattern, the promise chain appear to
ignore errors, this is not the case, instead it is the opposite: the promise
chain makes error handling more accessible and harder to ignore.
# `catch`
With promises, errors cascade along the promise chain, ensuring that your apps are
robust and your code is clear:
With promises, errors cascade ensuring your apps are robust and the code,
clearer:
```swift
firstly {
login()
}.then { creds in
fetch(avatar: creds.user)
}.done { image in
}.then { 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.
> In fact, Swift emits a warning if you forget to `catch` a chain. But we'll
> talk about that more 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.)
Promises each are objects that represent individual asychnronous tasks. If those
tasks fail their promises become *rejected*. Chains that contain rejected
promises skip all subsequent `then`s, instead the next `catch` is executed
(strictly, *any* subsequent `catch` handlers).
For fun, lets compare this pattern with its completion handler equivalent:
For fun lets compare this pattern with a completion handler equivalent:
```swift
func handle(error: Error) {
@ -112,11 +98,11 @@ login { creds, error in
}
```
The use of `guard` and a consolidated error handler help, but the promise chains
Use of `guard` and a consolidated error handler help, but the promise chains
readability speaks for itself.
# `ensure`
# `always`
We have learned to compose asynchronicity. Next lets extend our primitives:
@ -126,19 +112,19 @@ firstly {
return login()
}.then {
fetch(avatar: $0.user)
}.done {
}.then {
self.imageView = $0
}.ensure {
}.always {
UIApplication.shared.isNetworkActivityIndicatorVisible = false
}.catch {
//…
}
```
No matter the outcome of your chain—-failure or success—-your `ensure`
handler is always called.
Whatever the outcome in your chain—failure or success—your `always`
handler is called.
Lets compare this pattern with its completion handler equivalent:
For fun lets compare this pattern with a completion handler equivalent:
```swift
UIApplication.shared.isNetworkActivityIndicatorVisible = true
@ -158,34 +144,14 @@ login { creds, error in
}
```
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)
}
```
Here it would be trivial for somebody to amend this code and not unset the activity indicator leading to a bug. With
promises this is almost impossible, the Swift compiler will resist you supplementing the chain without promises, you
almost wont need to review the pull-requests.
# `when`
With completion handlers, reacting to multiple asynchronous operations is either
With completion handlers reacting to multiple asycnhronous operations is either
slow or hard. Slow means doing it serially:
```swift
@ -212,7 +178,7 @@ operation2 {
result2 = $0
group.leave()
}
group.notify(queue: .main) {
group.completion = {
finish(result1, result2)
}
```
@ -222,33 +188,32 @@ Promises are easier:
```swift
firstly {
when(fulfilled: operation1(), operation2())
}.done { result1, result2 in
}.then { result1, result2 in
//…
}
```
`when` takes promises, waits for them to resolve and returns a promise containing the results.
`when` takes promises, waits for them to resolve and returns a promise with the results.
As with any promise chain, if any of the component promises fail, the chain calls the next `catch`.
And of course, if any of them fail the chain calls the next `catch`, like *any* promise chain.
# 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:
When we made PromiseKit we understood that we wanted to *only* use promises, thus, wherever possible, we offer
extensions on top of Apples APIs that add promises. For example:
```swift
firstly {
CLLocationManager.promise()
}.then { location in
CLGeocoder.reverseGeocode(location)
}.done { placemarks in
}.then { placemarks in
self.placemark.text = "\(placemarks.first)"
}
```
To use these extensions, you need to specify subspecs:
To use these you need to specify subspecs:
```ruby
pod "PromiseKit"
@ -256,21 +221,18 @@ 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-).
All our extensions are available at the [PromiseKit organization](https://github.com/PromiseKit) and you should go there
to see what is available and to read the sources so that you can read the documentation. We have copiously documented every
file and every function.
# 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.
You can get a long way with our extensions, but sometimes you have to start chains of your own. Maybe you have a third party
API that doesnt provide promises, or maybe you wrote your own asynchronous system. Either way, we provide the starting point,
and if you were to look at the code of our extensions, you would see it is the same method below as we use ourselves.
Lets say we have the following method:
Lets say we have a method:
```swift
func fetch(completion: (String?, Error?) -> Void)
@ -280,172 +242,40 @@ How do we convert this to a promise? Well, it's easy:
```swift
func fetch() -> Promise<String> {
return Promise { fetch(completion: $0.resolve) }
return PromiseKit.wrap(fetch) }
}
```
You may find the expanded version more readable:
For more complicated situations use the root-resolver:
```swift
func fetch() -> Promise<String> {
return Promise { seal in
fetch { result, error in
seal.resolve(result, error)
return Promise { fulfill, reject in
foo { result, error in
if let result = result {
fulfill(result)
} else if let error = error {
reject(error)
} else {
reject(PMKError.invalidCallingConvention)
// ^^ we provide this error so that all paths are handled, even
// this path which technically should never happen (but might!)
}
}
}
}
```
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 {
//…
}
```
Note with the above example `PromiseKit.wrap(foo)` *would* have worked.
Only use the root-resolver when wrap doesnt work *or* you need to handle
non-typical scenarios.
# 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:
Above we kept using `firstly`, but what is it? Well, it is just [syntactic sugar](https://en.wikipedia.org/wiki/Syntactic_sugar),
you dont need it, but it helps make your chains more readable. Instead of:
```swift
firstly {
@ -463,31 +293,24 @@ 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.
Here is a key understanding: `login()` returns `Promise` and all `Promise` have a `then` function.
Thus, indeed, `firstly` returns `Promise` and, indeed, `then` returns `Promise`, but dont worry too much about these details. To start
with learn the *patterns*, then, when you are ready to advance, learn the underlying architecture.
## `when` Variants
## `when` variants
`when` is one of PromiseKits more useful functions, and so we offer several variants.
`when` is one of PromiseKits more useful functions, and thus 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.
* The default `when` and the one you should typically use is `when(fulfilled:)` this variant waits on all its promises, but if any fail, it fails, and thus the chain *rejects*. It is important to note that all promises in the when *continue*. Promises have *no* control over the tasks they represent, promises are merely are wrapper around tasks.
* We provide `when(resolved:)`. This variant waits even if one or more of its promises fails, consequently the result of this promise is an array of `Result<T>`, and consequently this variant requires that all its promises have the same generic type. See our advanced patterns guide for work arounds for this limitation.
* We provide `race`, this variant allows you to *race* several promises, whichever finishes first is the result. See our advanced patterns guide for typical usage.
## Swift Closure Inference
Swift automatically infers returns and return types for one-line closures.
The following two forms are the same:
Swift will automatically infer returns and return types for one line closures,
thus these are the same:
```swift
foo.then {
@ -503,33 +326,23 @@ foo.then { baz -> Promise<String> in
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
However this is a blessing and a curse, as the Swift compiler often will fail
to infer return types. See our [Troubleshooting Guide](Troubleshooting.md) if
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
The above is the 90% you will use. We **strongly** suggest reading the
[sources], though strictly we are only suggesting you read the function
documentation, don't worry about the internals. There are numerous little
functions that may be useful to you and the documentation for all of the above
is more thorough at the source.
In Xcode, dont forget to option-click on PromiseKit functions to access this
documentation while you're coding.
In Xcode dont forget to `⌥` click on PromiseKit functions to get at this
documentation while you are developing.
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).
Otherwise return to our [contents page](..).
[API Reference]: https://mxcl.dev/PromiseKit/reference/v6/Classes/Promise.html
[sources]: https://github.com/mxcl/PromiseKit/tree/master/Sources

View File

@ -1,74 +1,36 @@
# Xcode 8.3, 9.x or 10.x / Swift 3 or 4
# Xcode 8, 9 or 10 / Swift 3 or 4
We recommend Carthage over CocoaPods, but both installation methods are supported.
We recommend CocoaPods.
## CocoaPods
```ruby
use_frameworks!
target "Change Me!" do
pod "PromiseKit", "~> 6.8"
end
pod "PromiseKit", "~> 4.4"
```
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:
Since CocoaPods 1.0 you will (probably) need to add the `pod` line to a `target`,
eg:
```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
use_frameworks!
target "MyTarget" do
pod "PromiseKit", "~> 4.4"
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
github "mxcl/PromiseKit" ~> 4.4
```
> 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
```ruby
package.dependencies.append(
.package(url: "https://github.com/mxcl/PromiseKit", from: "6.8.0")
.Package(url: "https://github.com/mxcl/PromiseKit", majorVersion: 4)
)
```
@ -82,15 +44,14 @@ You can just drop `PromiseKit.xcodeproj` into your project and then add
PromiseKit contains Swift, so there have been rev-lock issues with Xcode:
| PromiseKit | Swift | Xcode | CI Status | Release Notes |
| ---------- | ----------------------- | -------- | ------------ | ----------------- |
| 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 | Swift | Xcode | CI Status | Release Notes |
| ---------- | -------- | -------- | ------------ | ----------------- |
| 4 | 3.x, 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 | Unsupported | [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.
@ -106,7 +67,7 @@ We also maintain a series of branches to aid migration for PromiseKit 2:
| 7.1 | 2.1 | 2 | [swift-2.0-minimal-changes] | ![ci-20] |
| 7.0 | 2.0 | 2 | [swift-2.0-minimal-changes] | ![ci-20] |
We do **not** usually backport fixes to these branches, but pull requests are welcome.
We do **not** usually backport fixes to these branches, but pull-requests are welcome.
## Xcode 8 / Swift 2.3 or Xcode 7
@ -120,7 +81,6 @@ pod "PromiseKit", "~> 3.5"
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
@ -128,10 +88,9 @@ github "mxcl/PromiseKit" ~> 3.5
[ci-23]: https://travis-ci.org/mxcl/PromiseKit.svg?branch=swift-2.3-minimal-changes
[ci-22]: https://travis-ci.org/mxcl/PromiseKit.svg?branch=swift-2.2-minimal-changes
[ci-20]: https://travis-ci.org/mxcl/PromiseKit.svg?branch=swift-2.0-minimal-changes
[news-2]: http://mxcl.dev/PromiseKit/news/2015/05/PromiseKit-2.0-Released/
[news-2]: http://promisekit.org/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/
[news-4]: http://promisekit.org/news/2016/09/PromiseKit-4.0-Released/
[swift-2.3-minimal-changes]: https://github.com/mxcl/PromiseKit/tree/swift-2.3-minimal-changes
[swift-2.2-minimal-changes]: https://github.com/mxcl/PromiseKit/tree/swift-2.2-minimal-changes
[swift-2.0-minimal-changes]: https://github.com/mxcl/PromiseKit/tree/swift-2.0-minimal-changes
@ -139,25 +98,25 @@ github "mxcl/PromiseKit" ~> 3.5
# Using Git Submodules for PromiseKits Extensions
> *Note*: This is a more advanced technique.
> Please 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
If you use CocoaPods and a few PromiseKit extensions then importing PromiseKit
causes that module to import all the extension frameworks. Thus if you have an
app and a few app-extensions (eg. iOS app, iOS watch extension, iOS Today
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.
This isnt that bad, but every framework that loads is overhead and 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.
Its better and worse with Carthage since we build individual micro-frameworks
for each PromiseKit-extension, so at least all your final products only link
against the Apple frameworks that they actually need. However, Apple have
advised that apps only link against “about 12” frameworks for performance
reasons, so for Carthage, we are worse off for this metric.
The solution is to instead import only CorePromise:
The solution is to instead only import CorePromise:
```ruby
# CocoaPods
@ -177,7 +136,7 @@ git submodule add https://github.com/PromiseKit/UIKit Submodules/PMKUIKit
Then in Xcode you can add these sources to your targets on a per-target basis.
Then when you `pod update`, ensure that you also update your submodules:
Then when you `pod update`, ensure you also update your submodules:
pod update && git submodule update --recursive --remote
@ -185,13 +144,9 @@ Then when you `pod update`, ensure that you also update your submodules:
# Release History
## [6.0](https://github.com/mxcl/PromiseKit/releases/tag/6.0.0) Feb 13th, 2018
* [PromiseKit 6 announcement post][news-6].
## [4.0](https://github.com/mxcl/PromiseKit/releases/tag/4.0.0)
* [PromiseKit 4 announcement post][news-4].
* [PromiseKit 4 announcement post](http://promisekit.org/news/2016/09/PromiseKit-4.0-Released/).
## [3.0](https://github.com/mxcl/PromiseKit/releases/tag/3.0.0) Oct 1st, 2015
@ -211,7 +166,7 @@ If you still are using Xcode 6 and Swift 1.2 then use PromiseKit 2.
## [2.0](https://github.com/mxcl/PromiseKit/releases/tag/2.0.0) May 14th, 2015
[PromiseKit 2 announcement post](http://mxcl.dev/PromiseKit/news/2015/05/PromiseKit-2.0-Released/).
[PromiseKit 2 announcement post](http://promisekit.org/news/2015/05/PromiseKit-2.0-Released/).
## [1.5](https://github.com/mxcl/PromiseKit/releases/tag/1.5.0)

View File

@ -5,18 +5,19 @@ 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:
Each is designed to be an approproate promise implementation for the strong
points of its language:
* `Promise<T>` is strict, defined and precise.
* `AnyPromise` is loose and dynamic.
Unlike most libraries, we have extensive bridging support, you can use PromiseKit
Unlike most libraries we have extensive bridging support, you can use PromiseKit
in mixed projects with mixed language targets and mixed language libraries.
# Using PromiseKit with Objective-C
`AnyPromise` is our promise class for Objective-C. It behaves almost identically to `Promise<T>`, our Swift promise class.
`AnyPromise` is our promise class for Objective-C. It behaves almost identically to `Promise<T>` (our Swift promise class).
```objc
myPromise.then(^(NSString *bar){
@ -50,7 +51,9 @@ myPromise.then(^{
});
```
One important feature is the syntactic flexibility of your handlers:
---
One important feature is the syntactic flexability of your handlers:
```objc
myPromise.then(^{
@ -76,7 +79,7 @@ We do runtime inspection of the block you pass to achieve this magic.
Another important distinction is that the equivalent function to Swifts `recover` is combined with `AnyPromise`s `catch`. This is typical to other “dynamic” promise implementations and thus achieves our goal that `AnyPromise` is loose and dynamic while `Promise<T>` is strict and specific.
A sometimes unexpected consequence of this fact is that returning nothing from a `catch` *resolves* the returned promise:
A sometimes unexpected consequence of this is that returning nothing from a `catch` *resolves* the returned promise:
```objc
myPromise.catch(^{
@ -88,7 +91,7 @@ myPromise.catch(^{
---
Another important distinction is that the `value` property returns even if the promise is rejected; in that case, it returns the `NSError` object with which the promise was rejected.
Another important distinction is that the `value` property returns even if the promise is rejected, in that case it returns the `NSError` object with which the promise was rejected.
# Bridging Between Objective-C & Swift
@ -101,8 +104,9 @@ Lets say you have:
@end
```
Ensure that this interface is included in your bridging header. You can now use the
following pattern in your Swift code:
Ensure this interface is included in your bridging header.
You can now use this in your Swift code:
```swift
let foo = Foo()
@ -125,9 +129,9 @@ Lets say you have:
@objc class Bar: NSObject { /*…*/ }
```
Ensure that your project is generating a `…-Swift.h` header so that Objective-C can see your Swift code.
Ensure that your project is generating a `…-Swift.h` header so your Objective-C can see your Swift code.
If you built this project and opened the `…-Swift.h` header, you would only see this:
If you built this and opened the `…-Swift.h` header you would only see this:
```objc
@interface Foo
@ -137,7 +141,7 @@ If you built this project and opened the `…-Swift.h` header, you would only se
@end
```
That's because Objective-C cannot import Swift objects that are generic. So we need to write some stubs:
This is because Objective-C cannot import Swift objects that are generic. Thus we need to write some stubs:
```swift
@objc class Foo: NSObject {
@ -150,7 +154,7 @@ That's because Objective-C cannot import Swift objects that are generic. So we n
}
```
If we built this and opened our generated header, we would now see:
If we built this and opened our generated header we would now see:
```objc
@interface Foo
@ -164,56 +168,5 @@ If we built this and opened our generated header, we would now see:
Perfect.
Note that AnyPromise can only bridge objects that conform to `AnyObject` or derive 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
Note that AnyPromise can only bridge objects that conform to `AnyObject` or derive `NSObject`. This is a limitation of Objective-C.

View File

@ -10,5 +10,3 @@
* [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,241 +1,109 @@
# Troubleshooting
## Compilation errors
Sadly, Swift often gives misleading diagnostic messages for compile errors with
PromiseKit.
99% of compilation issues involving PromiseKit can be addressed or diagnosed by one of the fixes below.
## Compile Errors
### Check your handler
### Cannot convert return expression of type … to return type AnyPromise
Specify the return type for the closure. DONT return `AnyPromise` (unless you
wanted to).
### Missing return in a closure expected to return AnyPromise
Specify the return type for the closure. DONT return `AnyPromise` (unless you
wanted to).
### Ambiguous *bar*
Specify the return type for the closure.
### Confusing Errors
The best one I saw lately was *`UIImage` is not convertible to `UIImage?`*
Swifts diagnostic reporting will be flat-out misleading and wrong inside
complicated closures, and with PromiseKit we have a lot of those.
When specifying the `return` types doesnt help try the advice in the next
section:
## Pain Free Swift Promises
The best way is to appease Swift is to decompose your chains into separated
local functions. For example:
```swift
return firstly {
URLSession.shared.dataTask(.promise, with: url)
}.compactMap {
JSONSerialization.jsonObject(with: $0.data) as? [String: Any]
}.then { dict in
User(dict: dict)
}
```
func foo() -> Promise<Int>
func step1() -> Promise<String> {
// many
// lines
// of
// code
}
func step2() -> Promise<Int> {
// many
// lines
// of
// code
}
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
step1()
}.then {
when(fulfilled: p1, p2) // both p1 & p2 are `Promise<Void>`
step2()
}
}
```
Fails to compile with the error:
Otherwise try to always have your chain return to something that specifies the
promise type. This is easy when you are writing functions that return promises,
and in general this is what you should aim for anyway: because it makes your
promises more composable.
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.
When you return promises from functions that specify the return type Swift can
infer the types properly and it makes writing chains that much easier.
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."
## Neither `catch` or `then` are called in my chain
Check something didnt throw a `CancellableError`. Easiest way is to amend your
`catch`:
```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 {
foo.then {
//…
}.catch(policy: .allErrorsIncludingCancellation) {
// cancelled errors are handled *as well*
}
```
becomes:
## `Pending Promise Deallocated!`
If you see this warning you have a path in your `Promise` initializer where
neither `fulfill` or `reject` are called:
```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
Promise<String> { fulfill, reject in
task { value, error in
if let value = value as? String {
seal.fulfill(value)
fulfill(value)
} else if let error = error {
seal.reject(error)
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.
There are two missing paths here and if either occur the promise will soon be
deallocated without resolving. This will show itself as a bug in your app,
probably the awful: infinite spinner.
So lets be thorough:
```swift
Promise<String> { seal in
Promise<String> { fulfill, reject in
task { value, error in
if let value = value as? String {
fulfill(value)
@ -251,49 +119,8 @@ Promise<String> { seal in
}
```
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!
If this seems tedious it shouldnt. You would have to be this thorough without promises too, the difference is without promises you wouldnt get a warning in the console letting you know your mistake!
### Slow compilation / compiler cannot solve in reasonable time
## Slow Compilation / Compiler Cannot Solve in Reasonable Time
Add return types to your closures.
### My promise never resolves
There are several potential causes:
#### 1. Check to be sure that your asynchronous task even *starts*
Youd be surprised how often this is the cause.
For example, if you are using `URLSession` without our extension (but
dont do that; *use* our extension! we know all the pitfalls), did you forget
to call `resume` on the task? If so, the task never actually starts, and so of
course it never finishes, either.
#### 2. Check that all paths in your custom Promise initializers are handled
See “Pending Promise Deallocated” above. Usually you will see this warning if
you are not handling a path, but that requires your promise deallocate, so you
may not see this warning yet you are still not handling all paths.
Unhandled paths mean the promise will not resolve.
#### 3. Ensure the queue your promise handler runs upon is not blocked
If the thread is blocked the handlers cannot execute. Commonly you can see this
if you are using our `wait()` function. Please read the documentation for `wait()`
for suggestions and caveats.
### `Result of call to 'done(on:_:)' is unused`, `Result of call to 'then(on:_:)' is unused`
PromiseKit deliberately avoids the `@discardableResult` annotation because the
unused result warning is a hint that you have not handled the error in your
chain. So do one of these:
1. Add a `catch`
2. `return` the promise (thus punting the error handling to the caller)
3. Use `cauterize()` to silence the warning.
Obviously, do 1 or 2 in preference to 3.

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

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

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

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

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

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

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

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

@ -1 +1 @@
Subproject commit c4ea4dba6e0516986b3cb1d93c86f6991ce46861
Subproject commit 5636e88018ee9cfcea68bccd39f74409c1faea53

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

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

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

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

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

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

@ -1 +1 @@
Subproject commit 5ec40a68f168255dcc339b9620e2dcf62079ae6b
Subproject commit ac0e2140d57c0193222582872586bf3ff30cc634

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

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

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

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

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

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

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

View File

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

View File

@ -1,30 +1,22 @@
// 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"),
]
let package = Package(
name: "PromiseKit",
exclude: [
"Sources/AnyPromise.swift",
"Sources/Promise+AnyPromise.swift",
"Sources/AnyPromise.m",
"Sources/dispatch_promise.m",
"Sources/GlobalState.m",
"Sources/hang.m",
"Sources/NSMethodSignatureForBlock.m",
"Sources/join.m",
"Sources/PMKCallVariadicBlock.m",
"Sources/when.m",
"Sources/after.m",
"Sources/AnyPromise+Private.h",
"Sources/AnyPromise.h",
"Sources/PromiseKit.h",
"Tests"
]
)

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

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

View File

@ -1,7 +1,10 @@
Pod::Spec.new do |s|
s.name = "PromiseKit"
s.version = '0.0.1'
`xcodebuild -project PromiseKit.xcodeproj -showBuildSettings` =~ /CURRENT_PROJECT_VERSION = ((\d\.)+\d)/
abort("No version detected") if $1.nil?
abort("Not tagged") unless `git tag`.split.include? $1
s.version = $1
s.source = {
:git => "https://github.com/mxcl/#{s.name}.git",
@ -11,27 +14,21 @@ Pod::Spec.new do |s|
s.license = 'MIT'
s.summary = 'Promises for Swift & ObjC.'
s.homepage = 'http://mxcl.dev/PromiseKit/'
s.homepage = 'http://promisekit.org'
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.documentation_url = 'http://promisekit.org/docs/'
s.default_subspecs = 'Foundation', 'UIKit', 'QuartzCore'
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.osx.deployment_target = '10.11'
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/*'
@ -75,7 +72,7 @@ Pod::Spec.new do |s|
s.subspec 'Bolts' do |ss|
ss.source_files = 'Extensions/Bolts/Sources/*'
ss.dependency 'PromiseKit/CorePromise'
ss.dependency 'Bolts', '~> 1.9.0'
ss.dependency 'Bolts', '~> 1.6.0'
ss.ios.deployment_target = '8.0'
ss.osx.deployment_target = '10.10'
ss.watchos.deployment_target = '2.0'
@ -86,9 +83,9 @@ Pod::Spec.new do |s|
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.ios.deployment_target = '8.0'
ss.osx.deployment_target = '10.10'
ss.tvos.deployment_target = '9.0'
ss.watchos.deployment_target = '3.0'
end
@ -105,7 +102,7 @@ Pod::Spec.new do |s|
hh = Dir['Sources/*.h'] - Dir['Sources/*+Private.h']
cc = Dir['Sources/*.swift'] - ['Sources/SwiftPM.swift']
cc << 'Sources/{after,AnyPromise,GlobalState,dispatch_promise,hang,join,PMKPromise,when,race}.m'
cc << 'Sources/{after,AnyPromise,GlobalState,dispatch_promise,hang,join,PMKPromise,when}.m'
cc += hh
ss.source_files = cc
@ -150,24 +147,7 @@ Pod::Spec.new do |s|
ss.watchos.deployment_target = '2.0'
ss.tvos.deployment_target = '9.0'
end
s.subspec 'HealthKit' do |ss|
ss.source_files = Dir['Extensions/HealthKit/Sources/*']
ss.dependency 'PromiseKit/CorePromise'
ss.frameworks = 'HealthKit'
ss.ios.deployment_target = '9.0'
ss.watchos.deployment_target = '2.0'
end
s.subspec 'HomeKit' do |ss|
ss.source_files = Dir['Extensions/HomeKit/Sources/*']
ss.dependency 'PromiseKit/CorePromise'
ss.frameworks = 'HomeKit'
ss.ios.deployment_target = '8.0'
ss.watchos.deployment_target = '3.0'
ss.tvos.deployment_target = '9.0'
end
s.subspec 'MapKit' do |ss|
ss.ios.source_files = ss.osx.source_files = ss.tvos.source_files = 'Extensions/MapKit/Sources/*'
ss.ios.frameworks = ss.osx.frameworks = ss.tvos.frameworks = 'MapKit'

File diff suppressed because it is too large Load Diff

View File

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

View File

@ -1,13 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1200"
LastUpgradeVersion = "1000"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "NO">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "NO"
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
@ -26,18 +26,7 @@
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>
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
<TestableReference
skipped = "NO">
@ -59,6 +48,16 @@
ReferencedContainer = "container:PromiseKit.xcodeproj">
</BuildableReference>
</TestableReference>
<TestableReference
skipped = "NO">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "631411121D5978D700E24B9E"
BuildableName = "PMKDispatchTests.xctest"
BlueprintName = "PMKDispatchTests"
ReferencedContainer = "container:PromiseKit.xcodeproj">
</BuildableReference>
</TestableReference>
<TestableReference
skipped = "NO">
<BuildableReference
@ -73,23 +72,24 @@
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"
BlueprintIdentifier = "631411471D597A0400E24B9E"
BuildableName = "PMKExtensionTests.xctest"
BlueprintName = "PMKExtensionTests"
ReferencedContainer = "container:PromiseKit.xcodeproj">
</BuildableReference>
</TestableReference>
</Testables>
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "63B0AC561D595E1B00FA21D9"
BuildableName = "PromiseKit.framework"
BlueprintName = "PromiseKit"
ReferencedContainer = "container:PromiseKit.xcodeproj">
</BuildableReference>
</MacroExpansion>
<AdditionalOptions>
</AdditionalOptions>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
@ -110,6 +110,8 @@
ReferencedContainer = "container:PromiseKit.xcodeproj">
</BuildableReference>
</MacroExpansion>
<AdditionalOptions>
</AdditionalOptions>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"

173
README.md
View File

@ -1,6 +1,6 @@
![PromiseKit](../gh-pages/public/img/logo-tight.png)
[![badge-pod][]][cocoapods] ![badge-languages][] ![badge-pms][] ![badge-platforms][] [![badge-travis][]][travis]
![badge-pod][] ![badge-languages][] ![badge-pms][] ![badge-platforms][] [![Build Status](https://travis-ci.org/mxcl/PromiseKit.svg?branch=master)](https://travis-ci.org/mxcl/PromiseKit)
---
@ -11,15 +11,12 @@ 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
when(URLSession.dataTask(with: url).asImage(), CLLocationManager.promise())
}.then { image, location -> Void in
self.imageView.image = image
self.label.text = "\(location)"
}.ensure {
}.always {
UIApplication.shared.isNetworkActivityIndicatorVisible = false
}.catch { error in
self.show(UIAlertController(for: error), sender: self)
@ -27,20 +24,8 @@ firstly {
```
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].
platform with a `swiftc`, it has *excellent* Objective-C bridging and
*delightful* specializations for iOS, macOS, tvOS and watchOS.
# Quick Start
@ -50,44 +35,14 @@ In your [Podfile]:
use_frameworks!
target "Change Me!" do
pod "PromiseKit", "~> 6.8"
pod "PromiseKit", "~> 4.4"
end
```
> The above gives an Xcode warning? See our [Installation Guide].
PromiseKit 4 supports Xcode 8.1, 8.2, 8.3, 9.0, 9.1, 9.2, 10.0 and 10.1; Swift 3.0, 3.1, 3.3, 4.0, 4.1 and 4.2; iOS, macOS, tvOS, watchOS, Linux and Android; CocoaPods, Carthage and SwiftPM; ([CI Matrix](https://travis-ci.org/mxcl/PromiseKit)).
PromiseKit 6, 5 and 4 support Xcode 8.3, 9.x and 10.0; Swift 3.1,
3.2, 3.3, 3.4, 4.0, 4.1, 4.2, 4.3 and 5.0 (development snapshots); iOS, macOS,
tvOS, watchOS, Linux and Android; CocoaPods, Carthage and SwiftPM;
([CI Matrix](https://travis-ci.org/mxcl/PromiseKit)).
For Carthage, SwiftPM, Accio, etc., or for instructions when using older Swifts or Xcodes, see our [Installation Guide]. 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
For Carthage, SwiftPM, etc., or for instructions when using older Swifts or
Xcodes see our [Installation Guide](Documentation/Installation.md).
# Documentation
@ -98,114 +53,80 @@ help me continue my work, I appreciate it 🙏🏻
* Manual
* [Installation Guide](Documentation/Installation.md)
* [Objective-C Guide](Documentation/ObjectiveC.md)
* [Troubleshooting](Documentation/Troubleshooting.md) (e.g., solutions to common compile errors)
* [Troubleshooting](Documentation/Troubleshooting.md) (eg. solutions to common compile errors)
* [Appendix](Documentation/Appendix.md)
* [API Reference](https://mxcl.dev/PromiseKit/reference/v6/Classes/Promise.html)
If you are looking for a functions documentation, then please note
[our sources](Sources/) are thoroughly documented.
# Extensions
Promises are only as useful as the asynchronous tasks they represent. Thus, we
Promises are only as useful as the asynchronous tasks they represent, thus we
have converted (almost) all of Apples APIs to promises. The default CocoaPod
provides Promises and the extensions for Foundation and UIKit. The other
extensions are available by specifying additional subspecs in your `Podfile`,
e.g.:
comes with promises for UIKit and Foundation, the rest can be installed by
specifying additional subspecs in your `Podfile`, eg:
```ruby
pod "PromiseKit/MapKit" # MKDirections().calculate().then { /*…*/ }
pod "PromiseKit/CoreLocation" # CLLocationManager.requestLocation().then { /*…*/ }
pod "PromiseKit/MapKit" # MKDirections().promise().then { /*…*/ }
pod "PromiseKit/CoreLocation" # CLLocationManager.promise().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`:
Promise chains are commonly started with networking, thus we offer multiple
options: [Alamofire], [OMGHTTPURLRQ] and of course (vanilla) `NSURLSession`:
```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
// pod 'PromiseKit/Alamofire'
// https://github.com/PromiseKit/Alamofire
Alamofire.request("http://example.com", method: .post).responseJSON().then { json in
//…
}.catch { error in
//…
}
func makeUrlRequest() throws -> URLRequest {
var rq = URLRequest(url: url)
rq.httpMethod = "POST"
rq.addValue("application/json", forHTTPHeaderField: "Content-Type")
rq.addValue("application/json", forHTTPHeaderField: "Accept")
rq.httpBody = try JSONEncoder().encode(obj)
return rq
}
```
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
// pod 'PromiseKit/OMGHTTPURLRQ'
// https://github.com/PromiseKit/OMGHTTPURLRQ
URLSession.POST("http://example.com").asDictionary().then { json in
//…
}.catch { error in
//…
}
// pod 'PromiseKit/Foundation'
// https://github.com/PromiseKit/Foundation
URLSession.shared.dataTask(url).asDictionary().then { json in
// …
}.catch { error in
//…
}
```
Nowadays, considering that:
Nobody ever got fired for using Alamofire, but at the end of the day, its
just a small wrapper around `NSURLSession`. OMGHTTPURLRQ supplements
`NSURLRequest` to make generating REST style requests easier, and the PromiseKit
extensions extend `NSURLSession` to make OMG usage more convenient. But since a
while now most servers accept JSON, so writing a simple API class that uses
vanilla `NSURLSession` and our promises is not hard, and gives you the most
control with the fewest black-boxes.
* 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.
The choice is yours.
# 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
Ask your question at our [Gitter chat channel] or on [our bug tracker].
[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-pms]: https://img.shields.io/badge/supports-CocoaPods%20%7C%20Carthage%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-
[OMGHTTPURLRQ]: https://github.com/mxcl/OMGHTTPURLRQ
[Alamofire]: http://alamofire.org
[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

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

View File

@ -1,77 +1,43 @@
#import <Foundation/Foundation.h>
#import <dispatch/dispatch.h>
#import <PromiseKit/fwd.h>
#import "fwd.h"
/// INTERNAL DO NOT USE
@class __AnyPromise;
/// Provided to simplify some usage sites
typedef void (^PMKResolver)(id __nullable) NS_REFINED_FOR_SWIFT;
typedef NS_ENUM(NSInteger, PMKCatchPolicy) {
PMKCatchPolicyAllErrors,
PMKCatchPolicyAllErrorsExceptCancellation
} NS_SWIFT_NAME(CatchPolicy);
/// An Objective-C implementation of the promise pattern.
@interface AnyPromise: NSObject
#if __has_include("PromiseKit-Swift.h")
#pragma clang diagnostic push
#pragma clang diagnostic ignored"-Wdocumentation"
#import "PromiseKit-Swift.h"
#pragma clang diagnostic pop
#else
// this hack because `AnyPromise` is Swift, but we add
// our own methods via the below category. This hack is
// only required while building PromiseKit since, once
// built, the generated -Swift header exists.
__attribute__((objc_subclassing_restricted)) __attribute__((objc_runtime_name("AnyPromise")))
@interface AnyPromise : NSObject
@property (nonatomic, readonly) BOOL resolved;
@property (nonatomic, readonly) BOOL pending;
@property (nonatomic, readonly) __nullable id value;
+ (instancetype __nonnull)promiseWithResolverBlock:(void (^ __nonnull)(__nonnull PMKResolver))resolveBlock;
+ (instancetype __nonnull)promiseWithValue:(__nullable id)value;
@end
#endif
@interface AnyPromise (obj)
@property (nonatomic, readonly) __nullable id value;
/**
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.
The provided block is executed when its receiver is resolved.
If you provide a block that takes a parameter, the value of the receiver will be passed as that parameter.
@ -127,10 +93,9 @@ typedef void (^PMKResolver)(id __nullable) NS_REFINED_FOR_SWIFT;
@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.
@warning *Note* Since catch is a c++ keyword, this method is not available in Objective-C++ files. Instead use catchWithPolicy.
@see catchOn
@see catchInBackground
@see catchWithPolicy
*/
- (AnyPromise * __nonnull(^ __nonnull)(id __nonnull))catch NS_REFINED_FOR_SWIFT;
#endif
@ -146,8 +111,7 @@ typedef void (^PMKResolver)(id __nullable) NS_REFINED_FOR_SWIFT;
@warning *Note* Since catch is a c++ keyword, this method is not available in Objective-C++ files. Instead use catchWithPolicy.
@see catch
@see catchOn
@see catchWithPolicy
*/
- (AnyPromise * __nonnull(^ __nonnull)(id __nonnull))catchInBackground NS_REFINED_FOR_SWIFT;
@ -161,33 +125,50 @@ typedef void (^PMKResolver)(id __nullable) NS_REFINED_FOR_SWIFT;
@warning *Note* Cancellation errors are not caught.
@see catch
@see catchInBackground
@see catchWithPolicy
*/
- (AnyPromise * __nonnull(^ __nonnull)(dispatch_queue_t __nonnull, id __nonnull))catchOn NS_REFINED_FOR_SWIFT;
/**
The provided block is executed when the receiver is rejected with the specified policy.
Specify the policy with which to catch as the first parameter to your block. Either for all errors, or all errors *except* cancellation errors.
@see catch
*/
- (AnyPromise * __nonnull(^ __nonnull)(PMKCatchPolicy, id __nonnull))catchWithPolicy NS_REFINED_FOR_SWIFT;
/**
The provided block is executed when the receiver is rejected with the specified policy.
Specify the policy with which to catch as the first parameter to your block. Either for all errors, or all errors *except* cancellation errors.
The provided block always runs on queue provided.
@see catch
*/
- (AnyPromise * __nonnull(^ __nonnull)(dispatch_queue_t __nonnull, PMKCatchPolicy, id __nonnull))catchOnWithPolicy NS_REFINED_FOR_SWIFT;
/**
The provided block is executed when the receiver is resolved.
The provided block always runs on the main queue.
@see ensureOn
@see alwaysOn
*/
- (AnyPromise * __nonnull(^ __nonnull)(dispatch_block_t __nonnull))ensure NS_REFINED_FOR_SWIFT;
- (AnyPromise * __nonnull(^ __nonnull)(dispatch_block_t __nonnull))always NS_REFINED_FOR_SWIFT;
/**
The provided block is executed on the dispatch queue of your choice when the receiver is resolved.
@see ensure
@see always
*/
- (AnyPromise * __nonnull(^ __nonnull)(dispatch_queue_t __nonnull, dispatch_block_t __nonnull))ensureOn NS_REFINED_FOR_SWIFT;
- (AnyPromise * __nonnull(^ __nonnull)(dispatch_queue_t __nonnull, dispatch_block_t __nonnull))alwaysOn 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;
/// @see always
- (AnyPromise * __nonnull(^ __nonnull)(dispatch_block_t __nonnull))finally __attribute__((deprecated("Use always")));
/// @see alwaysOn
- (AnyPromise * __nonnull(^ __nonnull)(dispatch_block_t __nonnull, dispatch_block_t __nonnull))finallyOn __attribute__((deprecated("Use always")));
/**
Create a new promise with an associated resolver.
@ -216,6 +197,16 @@ typedef void (^PMKResolver)(id __nullable) NS_REFINED_FOR_SWIFT;
@end
@interface AnyPromise (Unavailable)
- (instancetype __nonnull)init __attribute__((unavailable("It is illegal to create an unresolvable promise.")));
+ (instancetype __nonnull)new __attribute__((unavailable("It is illegal to create an unresolvable promise.")));
@end
typedef void (^PMKAdapter)(id __nullable, NSError * __nullable) NS_REFINED_FOR_SWIFT;
typedef void (^PMKIntegerAdapter)(NSInteger, NSError * __nullable) NS_REFINED_FOR_SWIFT;
typedef void (^PMKBooleanAdapter)(BOOL, NSError * __nullable) NS_REFINED_FOR_SWIFT;
@ -239,7 +230,7 @@ typedef void (^PMKBooleanAdapter)(BOOL, NSError * __nullable) NS_REFINED_FOR_SWI
@warning *Important* If both parameters are nil, the promise fulfills,
if both are non-nil the promise rejects. This is per the convention.
@see https://github.com/mxcl/PromiseKit/blob/master/Documentation/GettingStarted.md#making-promises
@see http://promisekit.org/sealing-your-own-promises/
*/
+ (instancetype __nonnull)promiseWithAdapterBlock:(void (^ __nonnull)(PMKAdapter __nonnull adapter))block NS_REFINED_FOR_SWIFT;
@ -290,17 +281,15 @@ extern id __nonnull __PMKArrayWithCount(NSUInteger, ...);
#endif
@interface AnyPromise (Unavailable)
@interface AnyPromise (Deprecations)
- (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")));
+ (instancetype __nonnull)new:(__nullable id)resolvers __attribute__((unavailable("See +promiseWithResolverBlock:")));
+ (instancetype __nonnull)when:(__nullable id)promises __attribute__((unavailable("See PMKWhen()")));
+ (instancetype __nonnull)join:(__nullable id)promises __attribute__((unavailable("See PMKJoin()")));
@end
__attribute__((unavailable("See AnyPromise")))
@interface PMKPromise
@end

View File

@ -1,60 +1,22 @@
#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"
extern dispatch_queue_t PMKDefaultDispatchQueue(void);
NSString *const PMKErrorDomain = @"PMKErrorDomain";
@implementation AnyPromise {
__AnyPromise *d;
}
- (instancetype)initWith__D:(__AnyPromise *)dd {
self = [super init];
if (self) self->d = dd;
return self;
}
@implementation AnyPromise (objc)
- (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 class] promiseWithResolverBlock:^(PMKResolver resolve){
*resolver = resolve;
}];
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 [self __thenOn:PMKDefaultDispatchQueue() execute:^(id obj) {
return PMKCallVariadicBlock(block, obj);
}];
};
@ -62,7 +24,7 @@ NSString *const PMKErrorDomain = @"PMKErrorDomain";
- (AnyPromise *(^)(dispatch_queue_t, id))thenOn {
return ^(dispatch_queue_t queue, id block) {
return [self->d __thenOn:queue execute:^(id obj) {
return [self __thenOn:queue execute:^(id obj) {
return PMKCallVariadicBlock(block, obj);
}];
};
@ -70,7 +32,7 @@ NSString *const PMKErrorDomain = @"PMKErrorDomain";
- (AnyPromise *(^)(id))thenInBackground {
return ^(id block) {
return [self->d __thenOn:dispatch_get_global_queue(0, 0) execute:^(id obj) {
return [self __thenOn:dispatch_get_global_queue(0, 0) execute:^(id obj) {
return PMKCallVariadicBlock(block, obj);
}];
};
@ -78,7 +40,7 @@ NSString *const PMKErrorDomain = @"PMKErrorDomain";
- (AnyPromise *(^)(dispatch_queue_t, id))catchOn {
return ^(dispatch_queue_t q, id block) {
return [self->d __catchOn:q execute:^(id obj) {
return [self __catchOn:q withPolicy:PMKCatchPolicyAllErrorsExceptCancellation execute:^(id obj) {
return PMKCallVariadicBlock(block, obj);
}];
};
@ -86,7 +48,7 @@ NSString *const PMKErrorDomain = @"PMKErrorDomain";
- (AnyPromise *(^)(id))catch {
return ^(id block) {
return [self->d __catchOn:dispatch_get_main_queue() execute:^(id obj) {
return [self __catchOn:PMKDefaultDispatchQueue() withPolicy:PMKCatchPolicyAllErrorsExceptCancellation execute:^(id obj) {
return PMKCallVariadicBlock(block, obj);
}];
};
@ -94,50 +56,40 @@ NSString *const PMKErrorDomain = @"PMKErrorDomain";
- (AnyPromise *(^)(id))catchInBackground {
return ^(id block) {
return [self->d __catchOn:dispatch_get_global_queue(0, 0) execute:^(id obj) {
return [self __catchOn:dispatch_get_global_queue(0, 0) withPolicy:PMKCatchPolicyAllErrorsExceptCancellation execute:^(id obj) {
return PMKCallVariadicBlock(block, obj);
}];
};
}
- (AnyPromise *(^)(dispatch_block_t))ensure {
- (AnyPromise *(^)(dispatch_queue_t, PMKCatchPolicy, id))catchOnWithPolicy {
return ^(dispatch_queue_t q, PMKCatchPolicy policy, id block) {
return [self __catchOn:q withPolicy:policy execute:^(id obj) {
return PMKCallVariadicBlock(block, obj);
}];
};
}
- (AnyPromise *(^)(PMKCatchPolicy, id))catchWithPolicy {
return ^(PMKCatchPolicy policy, id block) {
return [self __catchOn:PMKDefaultDispatchQueue() withPolicy:policy execute:^(id obj) {
return PMKCallVariadicBlock(block, obj);
}];
};
}
- (AnyPromise *(^)(dispatch_block_t))always {
return ^(dispatch_block_t block) {
return [self->d __ensureOn:dispatch_get_main_queue() execute:block];
return [self __alwaysOn:PMKDefaultDispatchQueue() execute:block];
};
}
- (AnyPromise *(^)(dispatch_queue_t, dispatch_block_t))ensureOn {
- (AnyPromise *(^)(dispatch_queue_t, dispatch_block_t))alwaysOn {
return ^(dispatch_queue_t queue, dispatch_block_t block) {
return [self->d __ensureOn:queue execute:block];
return [self __alwaysOn: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
@ -176,4 +128,14 @@ NSString *const PMKErrorDomain = @"PMKErrorDomain";
}];
}
- (id)value {
id obj = [self valueForKey:@"__value"];
if ([obj isKindOfClass:[PMKArray class]]) {
return obj[0];
} else {
return obj;
}
}
@end

View File

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

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

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

View File

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

76
Sources/GlobalState.m Normal file
View File

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

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

@ -109,12 +109,6 @@ 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;
return PMKProcessUnhandledException(thrown);
}
}

View File

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

View File

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

View File

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

View File

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

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

219
Sources/State.swift Normal file
View File

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

16
Sources/SwiftPM.swift Normal file
View File

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

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

80
Sources/Zalgo.swift Normal file
View File

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

View File

@ -2,45 +2,36 @@ import struct Foundation.TimeInterval
import Dispatch
/**
after(seconds: 1.5).then {
//
}
- Returns: A guarantee that resolves after the specified duration.
- Returns: A new promise that fulfills 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
@available(*, deprecated: 4.3, message: "Use after(seconds:)")
public func after(interval: TimeInterval) -> Promise<Void> {
return after(seconds: interval)
}
/**
after(.seconds(2)).then {
//
}
- Returns: A guarantee that resolves after the specified duration.
- Returns: A new promise that fulfills 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)
public func after(seconds: TimeInterval) -> Promise<Void> {
return Promise { fulfill, _ in
let when = DispatchTime.now() + seconds
DispatchQueue.global().asyncAfter(deadline: when) { fulfill(()) }
}
}
/**
- Returns: A new promise that fulfills after the specified duration.
*/
public func after(interval: DispatchTimeInterval) -> Promise<Void> {
return Promise { fulfill, _ in
let when = DispatchTime.now() + interval
#if swift(>=4.0)
DispatchQueue.global().asyncAfter(deadline: when) { fulfill(()) }
#else
DispatchQueue.global().asyncAfter(deadline: when, execute: fulfill)
#endif
}
}

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

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

View File

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

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

60
Sources/join.swift Normal file
View File

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

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 +1,40 @@
@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
Resolves with the first resolving promise from a set of promises.
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
- Returns: A new promise that resolves when the first promise in the provided promises resolves.
- Warning: If any of the provided promises reject, the returned promise is rejected.
- Warning: aborts if the array is empty.
*/
public func race<U: Thenable>(_ thenables: U...) -> Promise<U.T> {
return _race(thenables)
public func race<T>(promises: [Promise<T>]) -> Promise<T> {
guard promises.count > 0 else {
fatalError("Cannot race with an empty array of promises")
}
return _race(promises: promises)
}
/**
Waits for one promise to resolve
Resolves with the first resolving promise from a set of promises.
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
- Returns: A new promise that resolves when the first promise in the provided promises resolves.
- Warning: If any of the provided promises reject, the returned promise is rejected.
- Warning: aborts if the array is empty.
*/
public func race<U: Thenable>(_ thenables: [U]) -> Promise<U.T> {
guard !thenables.isEmpty else {
return Promise(error: PMKError.badInput)
}
return _race(thenables)
public func race<T>(_ promises: Promise<T>...) -> Promise<T> {
return _race(promises: promises)
}
/**
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
private func _race<T>(promises: [Promise<T>]) -> Promise<T> {
return Promise(sealant: { resolve in
for promise in promises {
promise.state.pipe(resolve)
}
})
}

View File

@ -6,11 +6,6 @@
@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/
@ -72,7 +67,7 @@ AnyPromise *PMKWhen(id promises) {
if (IsError(value)) {
progress.completedUnitCount = progress.totalUnitCount;
NSMutableDictionary *userInfo = [NSMutableDictionary dictionaryWithDictionary:[(NSError *)value userInfo] ?: @{}];
NSMutableDictionary *userInfo = [NSMutableDictionary dictionaryWithDictionary:[value userInfo] ?: @{}];
userInfo[PMKFailingPromiseIndexKey] = key;
[userInfo setObject:value forKey:NSUnderlyingErrorKey];
id err = [[NSError alloc] initWithDomain:[value domain] code:[value code] userInfo:userInfo];
@ -103,5 +98,3 @@ AnyPromise *PMKWhen(id promises) {
}
}];
}
#pragma GCC diagnostic pop

View File

@ -1,46 +1,55 @@
import Foundation
import Dispatch
private func _when<U: Thenable>(_ thenables: [U]) -> Promise<Void> {
var countdown = thenables.count
private func _when<T>(_ promises: [Promise<T>]) -> Promise<Void> {
let root = Promise<Void>.pending()
var countdown = promises.count
guard countdown > 0 else {
return .value(Void())
#if swift(>=4.0)
root.fulfill(())
#else
root.fulfill()
#endif
return root.promise
}
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))
let progress = Progress(totalUnitCount: Int64(promises.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
for promise in promises {
promise.state.pipe { resolution in
barrier.sync(flags: .barrier) {
switch result {
case .rejected(let error):
if rp.isPending {
switch resolution {
case .rejected(let error, let token):
token.consumed = true
if root.promise.isPending {
progress.completedUnitCount = progress.totalUnitCount
rp.box.seal(.rejected(error))
root.reject(error)
}
case .fulfilled:
guard rp.isPending else { return }
guard root.promise.isPending else { return }
progress.completedUnitCount += 1
countdown -= 1
if countdown == 0 {
rp.box.seal(.fulfilled(()))
#if swift(>=4.0)
root.fulfill(())
#else
root.fulfill()
#endif
}
}
}
}
}
return rp
return root.promise
}
/**
@ -66,38 +75,38 @@ private func _when<U: Thenable>(_ thenables: [U]) -> Promise<Void> {
- 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! } }
public func when<T>(fulfilled promises: [Promise<T>]) -> Promise<[T]> {
return _when(promises).then(on: zalgo) { promises.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 {
public func when(fulfilled promises: Promise<Void>...) -> Promise<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 {
public func when(fulfilled promises: [Promise<Void>]) -> Promise<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!) }
public func when<U, V>(fulfilled pu: Promise<U>, _ pv: Promise<V>) -> Promise<(U, V)> {
return _when([pu.asVoid(), pv.asVoid()]).then(on: zalgo) { (pu.value!, pv.value!) }
}
/// 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!) }
public func when<U, V, W>(fulfilled pu: Promise<U>, _ pv: Promise<V>, _ pw: Promise<W>) -> Promise<(U, V, W)> {
return _when([pu.asVoid(), pv.asVoid(), pw.asVoid()]).then(on: zalgo) { (pu.value!, pv.value!, pw.value!) }
}
/// 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!) }
public func when<U, V, W, X>(fulfilled pu: Promise<U>, _ pv: Promise<V>, _ pw: Promise<W>, _ px: Promise<X>) -> Promise<(U, V, W, X)> {
return _when([pu.asVoid(), pv.asVoid(), pw.asVoid(), px.asVoid()]).then(on: zalgo) { (pu.value!, pv.value!, pw.value!, px.value!) }
}
/// 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!) }
public func when<U, V, W, X, Y>(fulfilled pu: Promise<U>, _ pv: Promise<V>, _ pw: Promise<W>, _ px: Promise<X>, _ py: Promise<Y>) -> Promise<(U, V, W, X, Y)> {
return _when([pu.asVoid(), pv.asVoid(), pw.asVoid(), px.asVoid(), py.asVoid()]).then(on: zalgo) { (pu.value!, pv.value!, pw.value!, px.value!, py.value!) }
}
/**
@ -116,32 +125,30 @@ public func when<U: Thenable, V: Thenable, W: Thenable, X: Thenable, Y: Thenable
guard url = urlGenerator.next() else {
return nil
}
return downloadFile(url)
}
when(generator, concurrently: 3).done { datas in
when(generator, concurrently: 3).then { datum: [Data] -> Void 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 {
public func when<T, PromiseIterator: IteratorProtocol>(fulfilled promiseIterator: PromiseIterator, concurrently: Int) -> Promise<[T]> where PromiseIterator.Element == Promise<T> {
guard concurrently > 0 else {
return Promise(error: PMKError.badInput)
return Promise(error: PMKError.whenConcurrentlyZero)
}
var generator = promiseIterator
let root = Promise<[It.Element.T]>.pending()
var root = Promise<[T]>.pending()
var pendingPromises = 0
var promises: [It.Element] = []
var promises: [Promise<T>] = []
let barrier = DispatchQueue(label: "org.promisekit.barrier.when", attributes: [.concurrent])
@ -154,11 +161,15 @@ public func when<It: IteratorProtocol>(fulfilled promiseIterator: It, concurrent
}
guard shouldDequeue else { return }
var promise: It.Element!
var index: Int!
var promise: Promise<T>!
barrier.sync(flags: .barrier) {
guard let next = generator.next() else { return }
promise = next
index = promises.count
pendingPromises += 1
promises.append(next)
}
@ -166,11 +177,11 @@ public func when<It: IteratorProtocol>(fulfilled promiseIterator: It, concurrent
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
#if !swift(>=3.3) || (swift(>=4) && !swift(>=4.1))
root.fulfill(promises.flatMap{ $0.value })
#else
root.fulfill(promises.compactMap{ $0.value })
#endif
}
}
}
@ -179,7 +190,7 @@ public func when<It: IteratorProtocol>(fulfilled promiseIterator: It, concurrent
return testDone()
}
promise.pipe { resolution in
promise.state.pipe { resolution in
barrier.sync(flags: .barrier) {
pendingPromises -= 1
}
@ -188,8 +199,9 @@ public func when<It: IteratorProtocol>(fulfilled promiseIterator: It, concurrent
case .fulfilled:
dequeue()
testDone()
case .rejected(let error):
root.resolver.reject(error)
case .rejected(let error, let token):
token.consumed = true
root.reject(error)
}
}
@ -204,7 +216,7 @@ public func when<It: IteratorProtocol>(fulfilled promiseIterator: It, concurrent
/**
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(fulfilled:)` rejects as soon as one of the provided promises rejects. `when(resolved:)` waits on all provided promises and **never** rejects.
when(resolved: promise1, promise2, promise3).then { results in
for result in results where case .fulfilled(let value) {
@ -214,45 +226,36 @@ public func when<It: IteratorProtocol>(fulfilled promiseIterator: It, concurrent
// 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
- Returns: A new promise that resolves once all the provided promises resolve.
- Warning: The returned promise can *not* be rejected.
- Note: Any promises that error are implicitly consumed, your UnhandledErrorHandler will not be called.
*/
public func when<T>(resolved promises: Promise<T>...) -> Guarantee<[Result<T>]> {
public func when<T>(resolved promises: Promise<T>...) -> Promise<[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([])
}
/// Waits on all provided promises.
public func when<T>(resolved promises: [Promise<T>]) -> Promise<[Result<T>]> {
guard !promises.isEmpty else { return Promise(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 Promise { fulfill, reject in
for promise in promises {
promise.state.pipe { resolution in
if case .rejected(_, let token) = resolution {
token.consumed = true // all errors are implicitly consumed
}
var done = false
barrier.sync(flags: .barrier) {
countdown -= 1
done = countdown == 0
}
if done {
fulfill(promises.map { Result($0.state.get()!) })
}
}
}
}
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()
}

79
Sources/wrap.swift Normal file
View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -8,7 +8,7 @@ class Test227: XCTestCase {
testFulfilled { promise1, expectation, _ in
let sentinel = arc4random()
let promise2 = promise1.done { _ in throw Error.sentinel(sentinel) }
let promise2 = promise1.then { _ in throw Error.sentinel(sentinel) }
promise2.catch {
if case Error.sentinel(let x) = $0, x == sentinel {
@ -19,7 +19,7 @@ class Test227: XCTestCase {
testRejected { promise1, expectation, _ in
let sentinel = arc4random()
let promise2 = promise1.recover { _ -> Promise<UInt32> in throw Error.sentinel(sentinel) }
let promise2 = promise1.recover { _ -> UInt32 in throw Error.sentinel(sentinel) }
promise2.catch { error in
if case Error.sentinel(let x) = error, x == sentinel {

View File

@ -6,7 +6,7 @@ class Test231: XCTestCase {
describe("2.3.1: If `promise` and `x` refer to the same object, reject `promise` with a `TypeError' as the reason.") {
specify("via return from a fulfilled promise") { d, expectation in
var promise: Promise<Void>!
promise = Promise().then { () -> Promise<Void> in
promise = Promise(value: ()).then { () -> Promise<Void> in
return promise
}
promise.catch { err in

View File

@ -29,32 +29,32 @@ class Test232: XCTestCase {
let sentinel = arc4random()
func xFactory() -> Promise<UInt32> {
return .value(sentinel)
return Promise(value: sentinel)
}
testPromiseResolution(factory: xFactory) { promise, expectation in
promise.done {
XCTAssertEqual($0, sentinel)
promise.then { value -> Void in
XCTAssertEqual(value, sentinel)
expectation.fulfill()
}.silenceWarning()
}
}
}
describe("`x` is eventually-fulfilled") {
let sentinel = arc4random()
func xFactory() -> Promise<UInt32> {
return Promise { seal in
return Promise { fulfill, _ in
after(ticks: 2) {
seal.fulfill(sentinel)
fulfill(sentinel)
}
}
}
testPromiseResolution(factory: xFactory) { promise, expectation in
promise.done {
XCTAssertEqual($0, sentinel)
promise.then { value -> Void in
XCTAssertEqual(value, sentinel)
expectation.fulfill()
}.silenceWarning()
}
}
}
}
@ -79,9 +79,9 @@ class Test232: XCTestCase {
let sentinel = arc4random()
func xFactory() -> Promise<UInt32> {
return Promise { seal in
return Promise { _, reject in
after(ticks: 2) {
seal.reject(Error.sentinel(sentinel))
reject(Error.sentinel(sentinel))
}
}
}
@ -105,7 +105,7 @@ class Test232: XCTestCase {
extension Test232 {
fileprivate func testPromiseResolution(factory: @escaping () -> Promise<UInt32>, line: UInt = #line, test: (Promise<UInt32>, XCTestExpectation) -> Void) {
specify("via return from a fulfilled promise", file: #file, line: line) { d, expectation in
let promise = Promise.value(arc4random()).then { _ in factory() }
let promise = Promise(value: arc4random()).then { _ in factory() }
test(promise, expectation)
}
specify("via return from a rejected promise", file: #file, line: line) { d, expectation in

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