Compare commits
11 Commits
fix-NSErro
...
v4
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
47f40ef75f | ||
|
|
dd77da78cb | ||
|
|
1001d23725 | ||
|
|
a92c40ca73 | ||
|
|
7fb12976da | ||
|
|
99771a775e | ||
|
|
c70677a12b | ||
|
|
de638abca5 | ||
|
|
52697b25d4 | ||
|
|
6bab5e0c7f | ||
|
|
d7a978d628 |
2
.github/FUNDING.yml
vendored
2
.github/FUNDING.yml
vendored
@ -1,2 +0,0 @@
|
||||
tidelift: "cocoapods/PromiseKit"
|
||||
patreon: "mxcl"
|
||||
15
.github/ISSUE_TEMPLATE.md
vendored
15
.github/ISSUE_TEMPLATE.md
vendored
@ -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 didn’t help? OK, we’re 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.
|
||||
|
||||
19
.github/LinuxMain.stencil
vendored
19
.github/LinuxMain.stencil
vendored
@ -1,19 +0,0 @@
|
||||
@testable import Core
|
||||
@testable import A_
|
||||
import XCTest
|
||||
|
||||
//TODO get this to run on CI and don’t have it committed
|
||||
//NOTE problem is Sourcery doesn’t support Linux currently
|
||||
//USAGE: cd PromiseKit/Sources/.. && sourcery --config .github/sourcery.yml
|
||||
|
||||
{% for type in types.classes|based:"XCTestCase" %}
|
||||
extension {{ type.name }} {
|
||||
static var allTests = [
|
||||
{% for method in type.methods %}{% if method.parameters.count == 0 and method.shortName|hasPrefix:"test" %} ("{{ method.shortName }}", {{type.name}}.{{ method.shortName }}),
|
||||
{% endif %}{% endfor %}]
|
||||
}
|
||||
|
||||
{% endfor %}
|
||||
XCTMain([
|
||||
{% for type in types.classes|based:"XCTestCase" %}{% if not type.annotations.excludeFromLinuxMain %} testCase({{ type.name }}.allTests),
|
||||
{% endif %}{% endfor %}])
|
||||
29
.github/codecov.yml
vendored
29
.github/codecov.yml
vendored
@ -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
18
.github/jazzy.yml
vendored
@ -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
2
.github/ranger.yml
vendored
@ -1,2 +0,0 @@
|
||||
merges:
|
||||
- action: delete_branch
|
||||
17
.github/release
vendored
17
.github/release
vendored
@ -1,17 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
set -euxo pipefail
|
||||
|
||||
if [ -z "$1" ]; then
|
||||
echo "No argument supplied"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
Vn=$1
|
||||
Vo=$(git tag | tail -1)
|
||||
sed -i "" "s/CURRENT_PROJECT_VERSION = $Vo;/CURRENT_PROJECT_VERSION = $Vn;/" PromiseKit.xcodeproj/project.pbxproj
|
||||
git add PromiseKit.xcodeproj/project.pbxproj
|
||||
git commit -m "Tag $Vn"
|
||||
git tag $Vn
|
||||
git push origin $Vn
|
||||
open "https://github.com/mxcl/PromiseKit/releases/tag/$Vn"
|
||||
12
.github/sourcery.yml
vendored
12
.github/sourcery.yml
vendored
@ -1,12 +0,0 @@
|
||||
sources:
|
||||
include:
|
||||
- ../Tests/A+
|
||||
- ../Tests/CorePromise
|
||||
exclude:
|
||||
- ../Tests/A+/0.0.0.swift
|
||||
- ../Tests/CorePromise/Utilities.swift
|
||||
templates:
|
||||
include:
|
||||
- LinuxMain.stencil
|
||||
output:
|
||||
../Tests/LinuxMain.swift
|
||||
5
.gitignore
vendored
5
.gitignore
vendored
@ -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
6
.gitmodules
vendored
@ -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
|
||||
|
||||
@ -1 +0,0 @@
|
||||
extra_ignore_directories: [ Tests ]
|
||||
448
.travis.yml
448
.travis.yml
@ -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
|
||||
|
||||
@ -1,21 +1,21 @@
|
||||
# Common Misusage
|
||||
|
||||
## Doubling up Promises
|
||||
## Doubling Up Promises
|
||||
|
||||
Don’t 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 don’t 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 UIKit’s
|
||||
completion API supplies a `Bool`. However, we usually don’t 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 don’t have to care what queue your functions run on.
|
||||
|
||||
@ -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 isn’t *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 don’t need to worry about *when* your asynchronous operation
|
||||
finishes. Just act like it already has.
|
||||
With promises you don’t 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 don’t have a `cancel` function, but they do support cancellation through a
|
||||
Promises don’t 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 don’t have a `cancel` function because you don’t want code outside of
|
||||
your control to be able to cancel your operations--*unless*, of course, you explicitly
|
||||
want to enable that behavior. In cases where you do want cancellation, the exact way
|
||||
that it should work will vary depending on how the underlying task supports cancellation.
|
||||
PromiseKit provides cancellation primitives but no concrete API.
|
||||
> Promises don’t have a cancel function because you don’t want code outside of
|
||||
> your control to be able to cancel your operations *unless* 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 don’t want an error to cascade. Instead, you want to supply a default result:
|
||||
Sometimes you don’t 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
|
||||
|
||||
Let’s say you have:
|
||||
|
||||
@ -444,51 +383,49 @@ Let’s 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.
|
||||
|
||||
@ -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()
|
||||
|
||||
@ -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()
|
||||
@ -1,87 +1,31 @@
|
||||
# FAQ
|
||||
|
||||
## Why should I use PromiseKit over X-Promises-Foo?
|
||||
|
||||
* PromiseKit has a heavy focus on **developer experience**. You’re 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 Apple’s SDKs rather than having to do all the work of writing the Promise implementations yourself? Then pick PromiseKit.
|
||||
* Do you want to be able to use Promises with Swift 3.x, Swift 4.x, ObjC, iOS, tvOS, watchOS, macOS, Android & Linux? Then pick PromiseKit.
|
||||
* 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 don’t 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)
|
||||
```
|
||||
|
||||
Don’t 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).
|
||||
Don’t 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 don’t 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 can’t 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 can’t 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 we’re 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.
|
||||
|
||||
Here’s 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 we’re 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
|
||||
|
||||
|
||||
@ -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.
|
||||
|
||||
Let’s 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, let’s compare this pattern with its completion handler equivalent:
|
||||
For fun let’s 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 chain’s
|
||||
Use of `guard` and a consolidated error handler help, but the promise chain’s
|
||||
readability speaks for itself.
|
||||
|
||||
|
||||
# `ensure`
|
||||
# `always`
|
||||
|
||||
We have learned to compose asynchronicity. Next let’s 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.
|
||||
|
||||
Let’s compare this pattern with its completion handler equivalent:
|
||||
For fun let’s 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 won’t 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 won’t 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 Apple’s 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 Apple’s 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 doesn’t 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 doesn’t 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.
|
||||
|
||||
Let’s say we have the following method:
|
||||
Let’s 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 Swift’s 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 don’t 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 doesn’t 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 don’t 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 don’t 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 don’t 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 don’t 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 PromiseKit’s more useful functions, and so we offer several variants.
|
||||
`when` is one of PromiseKit’s 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, don’t forget to option-click on PromiseKit functions to access this
|
||||
documentation while you're coding.
|
||||
In Xcode don’t 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
|
||||
|
||||
@ -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 PromiseKit’s 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 isn’t that bad, but every framework that loads entails overhead and
|
||||
lengthens startup time.
|
||||
This isn’t that bad, but every framework that loads is overhead and startup
|
||||
time.
|
||||
|
||||
It’s 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.
|
||||
It’s 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)
|
||||
|
||||
|
||||
@ -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 Swift’s `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 @@ Let’s 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 @@ Let’s 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.
|
||||
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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. DON’T return `AnyPromise` (unless you
|
||||
wanted to).
|
||||
|
||||
### Missing return in a closure expected to return AnyPromise
|
||||
|
||||
Specify the return type for the closure. DON’T 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?`*…
|
||||
|
||||
Swift’s 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 doesn’t 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]) -> _'
|
||||
|
||||
What’s 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 we’d 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 didn’t 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 doesn’t 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 4’s “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 let’s 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 shouldn’t. You would have to be this thorough without promises, too.
|
||||
The difference is that without promises, you wouldn’t get a warning in the console notifying
|
||||
you of your mistake!
|
||||
If this seems tedious it shouldn’t. You would have to be this thorough without promises too, the difference is without promises you wouldn’t 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*
|
||||
|
||||
You’d be surprised how often this is the cause.
|
||||
|
||||
For example, if you are using `URLSession` without our extension (but
|
||||
don’t 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
|
||||
2
LICENSE
2
LICENSE
@ -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
|
||||
|
||||
@ -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"
|
||||
]
|
||||
)
|
||||
|
||||
@ -1,30 +0,0 @@
|
||||
// swift-tools-version:4.2
|
||||
|
||||
import PackageDescription
|
||||
|
||||
let pkg = Package(name: "PromiseKit")
|
||||
pkg.products = [
|
||||
.library(name: "PromiseKit", targets: ["PromiseKit"]),
|
||||
]
|
||||
|
||||
let pmk: Target = .target(name: "PromiseKit")
|
||||
pmk.path = "Sources"
|
||||
pmk.exclude = [
|
||||
"AnyPromise.swift",
|
||||
"AnyPromise.m",
|
||||
"PMKCallVariadicBlock.m",
|
||||
"dispatch_promise.m",
|
||||
"join.m",
|
||||
"when.m",
|
||||
"NSMethodSignatureForBlock.m",
|
||||
"after.m",
|
||||
"hang.m",
|
||||
"race.m",
|
||||
"Deprecations.swift"
|
||||
]
|
||||
pkg.swiftLanguageVersions = [.v3, .v4, .v4_2]
|
||||
pkg.targets = [
|
||||
pmk,
|
||||
.testTarget(name: "A+", dependencies: ["PromiseKit"]),
|
||||
.testTarget(name: "CorePromise", dependencies: ["PromiseKit"], path: "Tests/CorePromise"),
|
||||
]
|
||||
@ -1,33 +0,0 @@
|
||||
// swift-tools-version:5.0
|
||||
|
||||
import PackageDescription
|
||||
|
||||
let pkg = Package(name: "PromiseKit")
|
||||
pkg.platforms = [
|
||||
.macOS(.v10_10), .iOS(.v8), .tvOS(.v9), .watchOS(.v2)
|
||||
]
|
||||
pkg.products = [
|
||||
.library(name: "PromiseKit", targets: ["PromiseKit"]),
|
||||
]
|
||||
|
||||
let pmk: Target = .target(name: "PromiseKit")
|
||||
pmk.path = "Sources"
|
||||
pmk.exclude = [
|
||||
"AnyPromise.swift",
|
||||
"AnyPromise.m",
|
||||
"PMKCallVariadicBlock.m",
|
||||
"dispatch_promise.m",
|
||||
"join.m",
|
||||
"when.m",
|
||||
"NSMethodSignatureForBlock.m",
|
||||
"after.m",
|
||||
"hang.m",
|
||||
"race.m",
|
||||
"Deprecations.swift"
|
||||
]
|
||||
pkg.swiftLanguageVersions = [.v4, .v4_2, .v5]
|
||||
pkg.targets = [
|
||||
pmk,
|
||||
.testTarget(name: "A+", dependencies: ["PromiseKit"]),
|
||||
.testTarget(name: "CorePromise", dependencies: ["PromiseKit"], path: "Tests/CorePromise"),
|
||||
]
|
||||
@ -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
|
||||
|
||||
@ -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
@ -2,6 +2,6 @@
|
||||
<Workspace
|
||||
version = "1.0">
|
||||
<FileRef
|
||||
location = "self:">
|
||||
location = "self:PromiseKit.xcodeproj">
|
||||
</FileRef>
|
||||
</Workspace>
|
||||
|
||||
@ -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
173
README.md
@ -1,6 +1,6 @@
|
||||

|
||||
|
||||
[![badge-pod][]][cocoapods] ![badge-languages][] ![badge-pms][] ![badge-platforms][] [![badge-travis][]][travis]
|
||||
![badge-pod][] ![badge-languages][] ![badge-pms][] ![badge-platforms][] [](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.
|
||||
|
||||
[](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, I’m Max Howell. I’m 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 it’s 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 function’s 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 Apple’s 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 don’t have them:
|
||||
|
||||
```ruby
|
||||
pod "PromiseKit/CorePromise", "~> 6.8"
|
||||
```
|
||||
|
||||
> *Note:* Carthage installations come with no extensions by default.
|
||||
|
||||
## Choose Your Networking Library
|
||||
|
||||
Promise chains commonly start with a network operation. Thus, we offer
|
||||
extensions for `URLSession`:
|
||||
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, it’s
|
||||
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 isn’t 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
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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.
|
||||
|
||||
Don’t use this method if you already have promises! Instead, just return your promise.
|
||||
|
||||
Should you need to fulfill a promise but have no sensical value to use: your promise is a `void` promise: fulfill with `nil`.
|
||||
|
||||
The block you pass is executed immediately on the calling thread.
|
||||
|
||||
- Parameter block: The provided block is immediately executed, inside the block call `resolve` to resolve this promise and cause any attached handlers to execute. If you are wrapping a delegate-based system, we recommend instead to use: initWithResolver:
|
||||
- Returns: A new promise.
|
||||
- Warning: Resolving a promise with `nil` fulfills it.
|
||||
- SeeAlso: https://github.com/mxcl/PromiseKit/blob/master/Documentation/GettingStarted.md#making-promises
|
||||
- SeeAlso: https://github.com/mxcl/PromiseKit/blob/master/Documentation/CommonPatterns.md#wrapping-delegate-systems
|
||||
*/
|
||||
+ (instancetype __nonnull)promiseWithResolverBlock:(void (^ __nonnull)(__nonnull PMKResolver))resolveBlock NS_REFINED_FOR_SWIFT;
|
||||
|
||||
|
||||
/// INTERNAL DO NOT USE
|
||||
- (instancetype __nonnull)initWith__D:(__AnyPromise * __nonnull)d;
|
||||
|
||||
/**
|
||||
Creates a resolved promise.
|
||||
|
||||
When developing your own promise systems, it is occasionally useful to be able to return an already resolved promise.
|
||||
|
||||
- Parameter value: The value with which to resolve this promise. Passing an `NSError` will cause the promise to be rejected, passing an AnyPromise will return a new AnyPromise bound to that promise, otherwise the promise will be fulfilled with the value passed.
|
||||
- Returns: A resolved promise.
|
||||
*/
|
||||
+ (instancetype __nonnull)promiseWithValue:(__nullable id)value NS_REFINED_FOR_SWIFT;
|
||||
|
||||
/**
|
||||
The value of the asynchronous task this promise represents.
|
||||
|
||||
A promise has `nil` value if the asynchronous task it represents has not finished. If the value is `nil` the promise is still `pending`.
|
||||
|
||||
- Warning: *Note* Our Swift variant’s value property returns nil if the promise is rejected where AnyPromise will return the error object. This fits with the pattern where AnyPromise is not strictly typed and is more dynamic, but you should be aware of the distinction.
|
||||
|
||||
- Note: If the AnyPromise was fulfilled with a `PMKManifold`, returns only the first fulfillment object.
|
||||
|
||||
- Returns: The value with which this promise was resolved or `nil` if this promise is pending.
|
||||
*/
|
||||
@property (nonatomic, readonly) __nullable id value NS_REFINED_FOR_SWIFT;
|
||||
|
||||
/// - Returns: if the promise is pending resolution.
|
||||
@property (nonatomic, readonly) BOOL pending NS_REFINED_FOR_SWIFT;
|
||||
|
||||
/// - Returns: if the promise is resolved and fulfilled.
|
||||
@property (nonatomic, readonly) BOOL fulfilled NS_REFINED_FOR_SWIFT;
|
||||
|
||||
/// - Returns: if the promise is resolved and rejected.
|
||||
@property (nonatomic, readonly) BOOL rejected NS_REFINED_FOR_SWIFT;
|
||||
|
||||
|
||||
/**
|
||||
The provided block is executed when its receiver is fulfilled.
|
||||
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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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 don’t 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 variant’s value property returns nil if the promise is rejected where AnyPromise will return the error object. This fits with the pattern where AnyPromise is not strictly typed and is more dynamic, but you should be aware of the distinction.
|
||||
|
||||
- Note: If the AnyPromise was fulfilled with a `PMKManifold`, returns only the first fulfillment object.
|
||||
|
||||
- Returns: The value with which this promise was resolved or `nil` if this promise is pending.
|
||||
*/
|
||||
@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.
|
||||
|
||||
Don’t use this method if you already have promises! Instead, just return your promise.
|
||||
|
||||
Should you need to fulfill a promise but have no sensical value to use: your promise is a `void` promise: fulfill with `nil`.
|
||||
|
||||
The block you pass is executed immediately on the calling thread.
|
||||
|
||||
- Parameter block: The provided block is immediately executed, inside the block call `resolve` to resolve this promise and cause any attached handlers to execute. If you are wrapping a delegate-based system, we recommend instead to use: initWithResolver:
|
||||
|
||||
- Returns: A new promise.
|
||||
- Warning: Resolving a promise with `nil` fulfills it.
|
||||
- SeeAlso: 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
|
||||
|
||||
@ -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 don’t 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 barrier’d
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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 promise’s 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 promise’s 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 don’t 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
|
||||
}
|
||||
}
|
||||
@ -1,35 +0,0 @@
|
||||
import Dispatch
|
||||
|
||||
/**
|
||||
PromiseKit’s 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 application’s lifetime
|
||||
public var conf = PMKConfiguration()
|
||||
@ -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
|
||||
@ -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)
|
||||
}
|
||||
}
|
||||
53
Sources/DispatchQueue+Promise.swift
Normal file
53
Sources/DispatchQueue+Promise.swift
Normal 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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
76
Sources/GlobalState.m
Normal 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;
|
||||
});
|
||||
}
|
||||
@ -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 closure’s 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, don’t 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
|
||||
@ -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)
|
||||
}
|
||||
@ -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 don’t catch objc exceptions: they are meant to crash your app
|
||||
@throw thrown;
|
||||
return PMKProcessUnhandledException(thrown);
|
||||
}
|
||||
}
|
||||
|
||||
42
Sources/Promise+AnyPromise.swift
Normal file
42
Sources/Promise+AnyPromise.swift
Normal 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))
|
||||
}
|
||||
})
|
||||
}
|
||||
57
Sources/Promise+Properties.swift
Normal file
57
Sources/Promise+Properties.swift
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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, so—you know—don’t call this on a serial thread that
|
||||
any part of your chain may use. Like the main thread for example.
|
||||
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 promise’s 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 promise’s 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 promise’s 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
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
#import <PromiseKit/fwd.h>
|
||||
#import <PromiseKit/AnyPromise.h>
|
||||
#import "fwd.h"
|
||||
#import "AnyPromise.h"
|
||||
|
||||
#import <Foundation/NSObjCRuntime.h> // `FOUNDATION_EXPORT`
|
||||
|
||||
|
||||
@ -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
219
Sources/State.swift
Normal 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 couldn’t 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
16
Sources/SwiftPM.swift
Normal 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)
|
||||
{}
|
||||
@ -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 closure’s 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
80
Sources/Zalgo.swift
Normal 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: don’t 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 doesn’t 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)) }
|
||||
}
|
||||
}
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@ -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()
|
||||
}
|
||||
@ -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* Don’t 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 shouldn’t 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,
|
||||
};
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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
60
Sources/join.swift
Normal 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! })
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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];
|
||||
}
|
||||
}];
|
||||
}
|
||||
@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
79
Sources/wrap.swift
Normal 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)
|
||||
}
|
||||
}
|
||||
@ -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
|
||||
|
||||
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@ -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(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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() }
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
Loading…
Reference in New Issue
Block a user