Skip to content

Commit

Permalink
Showing 86 changed files with 1,790 additions and 2,608 deletions.
2 changes: 1 addition & 1 deletion Extensions/AVFoundation
2 changes: 1 addition & 1 deletion Extensions/Accounts
2 changes: 1 addition & 1 deletion Extensions/AddressBook
2 changes: 1 addition & 1 deletion Extensions/Alamofire
2 changes: 1 addition & 1 deletion Extensions/AssetsLibrary
2 changes: 1 addition & 1 deletion Extensions/Bolts
2 changes: 1 addition & 1 deletion Extensions/CloudKit
2 changes: 1 addition & 1 deletion Extensions/CoreBluetooth
2 changes: 1 addition & 1 deletion Extensions/EventKit
2 changes: 1 addition & 1 deletion Extensions/OMGHTTPURLRQ
2 changes: 1 addition & 1 deletion Extensions/Photos
2 changes: 1 addition & 1 deletion Extensions/QuartzCore
2 changes: 1 addition & 1 deletion Extensions/SystemConfiguration
2 changes: 1 addition & 1 deletion Extensions/WatchConnectivity
12 changes: 5 additions & 7 deletions PromiseKit.playground/Contents.swift
Original file line number Diff line number Diff line change
@@ -7,21 +7,19 @@ import PromiseKit


func promise3() -> Promise<Int> {
return after(seconds: 1).then {
return 3
}
return after(.seconds(1)).map{ 3 }
}

firstly {
Promise(value: 1)
}.then { _ in
}.map { _ in
2
}.then { _ in
promise3()
}.then {
}.done {
print($0) // => 3
}.always {
// always happens
}.ensure {
PlaygroundPage.current.finishExecution()
}.catch { error in
// only happens for errors
}
7 changes: 5 additions & 2 deletions PromiseKit.podspec
Original file line number Diff line number Diff line change
@@ -3,7 +3,6 @@ Pod::Spec.new do |s|

`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 = {
@@ -19,7 +18,7 @@ Pod::Spec.new do |s|
s.social_media_url = 'https://twitter.com/mxcl'
s.authors = { 'Max Howell' => '[email protected]' }
s.documentation_url = 'http://promisekit.org/docs/'
s.default_subspecs = 'Foundation', 'UIKit', 'QuartzCore'
s.default_subspecs = 'CorePromise'
s.requires_arc = true

# CocoaPods requires us to specify the root deployment targets
@@ -29,6 +28,10 @@ Pod::Spec.new do |s|
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/*'
192 changes: 90 additions & 102 deletions PromiseKit.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "0900"
LastUpgradeVersion = "0830"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
18 changes: 11 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
@@ -41,11 +41,15 @@ target "Change Me!" do
end
```

PromiseKit 4 supports Xcode 8.1, 8.2, 8.3 and 9.0; Swift 3.0, 3.1, 3.2 and 4.0; iOS, macOS, tvOS, watchOS, Linux and Android; CocoaPods, Carthage and SwiftPM; ([CI Matrix](https://travis-ci.org/mxcl/PromiseKit)).
PromiseKit 4 and 5 support Xcode 8.1, 8.2, 8.3 and 9.0; Swift 3.0, 3.1, 3.2 and 4.0; iOS, macOS, tvOS, watchOS, Linux and Android; CocoaPods, Carthage and SwiftPM; ([CI Matrix](https://travis-ci.org/mxcl/PromiseKit)).

For Carthage, SwiftPM, etc., or for instructions when using older Swifts or
Xcodes see our [Installation Guide](Documentation/Installation.md).

# PromiseKit 5

PromiseKit 5 has been released, but is not yet fully documented, so we advise sticking with version 4 for the time being.

# Documentation

* Handbook
@@ -64,13 +68,13 @@ If you are looking for a function’s documentation, then please note
# Extensions

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
comes with promises for UIKit and Foundation, the rest can be installed by
specifying additional subspecs in your `Podfile`, eg:
have converted (almost) all of Apple’s APIs to promises. By default PromiseKit
only provides promises, the extensions are available by specifying additional
subspecs in your `Podfile`, eg:

```ruby
pod "PromiseKit/MapKit" # MKDirections().promise().then { /*…*/ }
pod "PromiseKit/CoreLocation" # CLLocationManager.promise().then { /*…*/ }
pod "PromiseKit/MapKit" # MKDirections().calculate().then { /*…*/ }
pod "PromiseKit/CoreLocation" # CLLocationManager.requestLocation().then { /*…*/ }
```

All our extensions are separate repositories at the [PromiseKit organization].
@@ -107,7 +111,7 @@ URLSession.shared.dataTask(url).asDictionary().then { json in
```

Nobody ever got fired for using Alamofire, but at the end of the day, it’s
just a small wrapper around `NSURLSession`. OMGHTTPURLRQ supplements
just a 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
4 changes: 1 addition & 3 deletions Sources/AnyPromise+Private.h
Original file line number Diff line number Diff line change
@@ -27,12 +27,10 @@

@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)__catchOn:(__nonnull dispatch_queue_t)q 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;
96 changes: 36 additions & 60 deletions Sources/AnyPromise.h
Original file line number Diff line number Diff line change
@@ -4,12 +4,6 @@

typedef void (^PMKResolver)(id __nullable) NS_REFINED_FOR_SWIFT;

typedef NS_ENUM(NSInteger, PMKCatchPolicy) {
PMKCatchPolicyAllErrors,
PMKCatchPolicyAllErrorsExceptCancellation
} NS_SWIFT_NAME(CatchPolicy);


#if __has_include("PromiseKit-Swift.h")
#pragma clang diagnostic push
#pragma clang diagnostic ignored"-Wdocumentation"
@@ -23,18 +17,30 @@ typedef NS_ENUM(NSInteger, PMKCatchPolicy) {

__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;
+ (instancetype __nonnull)promiseWithResolverBlock:(void (^ __nonnull)(__nonnull PMKResolver))resolveBlock NS_REFINED_FOR_SWIFT;
+ (instancetype __nonnull)promiseWithValue:(__nullable id)value NS_REFINED_FOR_SWIFT;
@end
#endif


@interface AnyPromise (obj)

@property (nonatomic, readonly) __nullable id value;
/**
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;


/**
The provided block is executed when its receiver is resolved.
@@ -93,9 +99,10 @@ typedef NS_ENUM(NSInteger, PMKCatchPolicy) {
@warning *Note* Cancellation errors are not caught.
@warning *Note* Since catch is a c++ keyword, this method is not available in Objective-C++ files. Instead use catchWithPolicy.
@warning *Note* Since catch is a c++ keyword, this method is not available in Objective-C++ files. Instead use catchOn.
@see catchWithPolicy
@see catchOn
@see catchInBackground
*/
- (AnyPromise * __nonnull(^ __nonnull)(id __nonnull))catch NS_REFINED_FOR_SWIFT;
#endif
@@ -111,7 +118,8 @@ typedef NS_ENUM(NSInteger, PMKCatchPolicy) {
@warning *Note* Since catch is a c++ keyword, this method is not available in Objective-C++ files. Instead use catchWithPolicy.
@see catchWithPolicy
@see catch
@see catchOn
*/
- (AnyPromise * __nonnull(^ __nonnull)(id __nonnull))catchInBackground NS_REFINED_FOR_SWIFT;

@@ -125,50 +133,26 @@ typedef NS_ENUM(NSInteger, PMKCatchPolicy) {
@warning *Note* Cancellation errors are not caught.
@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
@see catchInBackground
*/
- (AnyPromise * __nonnull(^ __nonnull)(dispatch_queue_t __nonnull, PMKCatchPolicy, id __nonnull))catchOnWithPolicy NS_REFINED_FOR_SWIFT;
- (AnyPromise * __nonnull(^ __nonnull)(dispatch_queue_t __nonnull, id __nonnull))catchOn NS_REFINED_FOR_SWIFT;

/**
The provided block is executed when the receiver is resolved.
The provided block always runs on the main queue.
@see alwaysOn
@see ensureOn
*/
- (AnyPromise * __nonnull(^ __nonnull)(dispatch_block_t __nonnull))always NS_REFINED_FOR_SWIFT;
- (AnyPromise * __nonnull(^ __nonnull)(dispatch_block_t __nonnull))ensure NS_REFINED_FOR_SWIFT;

/**
The provided block is executed on the dispatch queue of your choice when the receiver is resolved.
@see always
@see ensure
*/
- (AnyPromise * __nonnull(^ __nonnull)(dispatch_queue_t __nonnull, dispatch_block_t __nonnull))alwaysOn 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")));
- (AnyPromise * __nonnull(^ __nonnull)(dispatch_queue_t __nonnull, dispatch_block_t __nonnull))ensureOn NS_REFINED_FOR_SWIFT;

/**
Create a new promise with an associated resolver.
@@ -197,16 +181,6 @@ typedef NS_ENUM(NSInteger, PMKCatchPolicy) {
@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;
@@ -281,15 +255,17 @@ extern id __nonnull __PMKArrayWithCount(NSUInteger, ...);
#endif


@interface AnyPromise (Deprecations)
@interface AnyPromise (Unavailable)

+ (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()")));
- (instancetype __nonnull)init __attribute__((unavailable("It is illegal to create an unresolvable promise.")));
+ (instancetype __nonnull)new __attribute__((unavailable("It is illegal to create an unresolvable promise.")));
- (AnyPromise * __nonnull(^ __nonnull)(dispatch_block_t __nonnull))always __attribute__((unavailable("See -ensure")));
- (AnyPromise * __nonnull(^ __nonnull)(dispatch_block_t __nonnull))alwaysOn __attribute__((unavailable("See -ensureOn")));
- (AnyPromise * __nonnull(^ __nonnull)(dispatch_block_t __nonnull))finally __attribute__((unavailable("See -ensure")));
- (AnyPromise * __nonnull(^ __nonnull)(dispatch_block_t __nonnull, dispatch_block_t __nonnull))finallyOn __attribute__((unavailable("See -ensureOn")));

@end


__attribute__((unavailable("See AnyPromise")))
@interface PMKPromise
@end
36 changes: 11 additions & 25 deletions Sources/AnyPromise.m
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
#import "PMKCallVariadicBlock.m"
#import "AnyPromise+Private.h"

extern dispatch_queue_t PMKDefaultDispatchQueue(void);

NSString *const PMKErrorDomain = @"PMKErrorDomain";


@@ -16,7 +14,7 @@ - (instancetype)initWithResolver:(PMKResolver __strong *)resolver {

- (AnyPromise *(^)(id))then {
return ^(id block) {
return [self __thenOn:PMKDefaultDispatchQueue() execute:^(id obj) {
return [self __thenOn:dispatch_get_main_queue() execute:^(id obj) {
return PMKCallVariadicBlock(block, obj);
}];
};
@@ -40,56 +38,44 @@ - (instancetype)initWithResolver:(PMKResolver __strong *)resolver {

- (AnyPromise *(^)(dispatch_queue_t, id))catchOn {
return ^(dispatch_queue_t q, id block) {
return [self __catchOn:q withPolicy:PMKCatchPolicyAllErrorsExceptCancellation execute:^(id obj) {
return [self __catchOn:q execute:^(id obj) {
return PMKCallVariadicBlock(block, obj);
}];
};
}

- (AnyPromise *(^)(id))catch {
return ^(id block) {
return [self __catchOn:PMKDefaultDispatchQueue() withPolicy:PMKCatchPolicyAllErrorsExceptCancellation execute:^(id obj) {
return [self __catchOn:dispatch_get_main_queue() execute:^(id obj) {
return PMKCallVariadicBlock(block, obj);
}];
};
}

- (AnyPromise *(^)(id))catchInBackground {
return ^(id block) {
return [self __catchOn:dispatch_get_global_queue(0, 0) withPolicy:PMKCatchPolicyAllErrorsExceptCancellation execute:^(id obj) {
return [self __catchOn:dispatch_get_global_queue(0, 0) execute:^(id obj) {
return PMKCallVariadicBlock(block, obj);
}];
};
}

- (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 {
- (AnyPromise *(^)(dispatch_block_t))ensure {
return ^(dispatch_block_t block) {
return [self __alwaysOn:PMKDefaultDispatchQueue() execute:block];
return [self __alwaysOn:dispatch_get_main_queue() execute:block];
};
}

- (AnyPromise *(^)(dispatch_queue_t, dispatch_block_t))alwaysOn {
- (AnyPromise *(^)(dispatch_queue_t, dispatch_block_t))ensureOn {
return ^(dispatch_queue_t queue, dispatch_block_t block) {
return [self __alwaysOn:queue execute:block];
};
}

- (BOOL)pending {
return [[self valueForKey:@"__pending"] boolValue];
}

@end


361 changes: 171 additions & 190 deletions Sources/AnyPromise.swift

Large diffs are not rendered by default.

96 changes: 96 additions & 0 deletions Sources/Box.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
import Dispatch

enum Sealant<R> {
case pending(Handlers<R>)
case resolved(R)
}

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

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 {
func async(_ body: @escaping() -> Void) {
switch self {
case .none:
body()
case .some(let q):
q.async(execute: body)
}
}
}
135 changes: 135 additions & 0 deletions Sources/Catchable.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
import Dispatch

public protocol CatchMixin: Thenable
{}

public extension CatchMixin {
@discardableResult
func `catch`(on: DispatchQueue? = conf.Q.return, 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 {
body(error)
finalizer.pending.resolve(())
}
case .fulfilled:
finalizer.pending.resolve(())
}
}
return finalizer
}
}

public class PMKFinalizer {
let pending = Guarantee<Void>.pending()

public func finally(_ body: @escaping () -> Void) {
pending.guarantee.done(body)
}
}


public extension CatchMixin {
func recover<U: Thenable>(on: DispatchQueue? = conf.Q.map, policy: CatchPolicy = conf.catchPolicy, _ body: @escaping(Error) throws -> U) -> Promise<T> where U.T == T {
let rp = Promise<U.T>(.pending)
pipe {
switch $0 {
case .fulfilled(let value):
rp.box.seal(.fulfilled(value))
case .rejected(let error):
if policy == .allErrors || !error.isCancelled {
on.async {
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
}

/// recover into a Guarantee, note it is logically impossible for this to take a catchPolicy, thus allErrors are handled
@discardableResult
func recover(on: DispatchQueue? = conf.Q.map, _ 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 {
body(error).pipe(to: rg.box.seal)
}
}
}
return rg
}

func ensure(on: DispatchQueue? = conf.Q.return, _ body: @escaping () -> Void) -> Promise<T> {
let rp = Promise<T>(.pending)
pipe { result in
on.async {
body()
rp.box.seal(result)
}
}
return rp
}

func cauterize() {
self.catch {
print("PromiseKit:cauterized-error:", $0)
}
}
}


public extension CatchMixin where T == Void {
@discardableResult
func recover(on: DispatchQueue? = conf.Q.map, _ 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 {
body(error)
rg.box.seal(())
}
}
}
return rg
}

func recover(on: DispatchQueue? = conf.Q.map, _ 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):
on.async {
do {
rg.box.seal(.fulfilled(try body(error)))
} catch {
rg.box.seal(.rejected(error))
}
}
}
}
return rg
}
}
13 changes: 13 additions & 0 deletions Sources/Configuration.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import Dispatch

public struct PMKConfiguration {
/// the default queues that promises handlers dispatch to
public var Q = (map: DispatchQueue.main, return: DispatchQueue.main)

public var catchPolicy = CatchPolicy.allErrorsExceptCancellation
}

//TODO disallow modification of this after first promise instantiation
//TODO this should be per module too, eg. frameworks you use that provide promises
// should be confident about the queues their code runs on
public var conf = PMKConfiguration()
26 changes: 26 additions & 0 deletions Sources/CustomStringConvertible.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@

extension Promise: CustomStringConvertible {
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 {
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))"
}
}
}
53 changes: 0 additions & 53 deletions Sources/DispatchQueue+Promise.swift

This file was deleted.

172 changes: 38 additions & 134 deletions Sources/Error.swift
Original file line number Diff line number Diff line change
@@ -1,13 +1,6 @@
import Foundation

public enum PMKError: Error {
/**
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.
@@ -20,153 +13,64 @@ public enum PMKError: Error {
*/
case returnedSelf

/** `when()` was called with a concurrency of <= 0 */
case whenConcurrentlyZero

/** AnyPromise.toPromise failed to cast as requested */
case castError(Any.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?)
/** `when()`, `race()` etc. were called with invalid parameters, eg. an empty array. */
case badInput

/**
The data could not be decoded using the encoding specified by the HTTP
response headers.
*/
case stringEncoding(URLRequest, Data, URLResponse)
/// The operation was cancelled
case cancelled

/**
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
}
}
/// `nil` was returned from `flatMap`
case flatMap(Any, Any.Type)
}

extension PMKURLError: CustomStringConvertible {
extension PMKError: CustomStringConvertible {
public var description: 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)"
case .flatMap(let obj, let type):
return "Could not `flatMap<\(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 operation was cancelled"
}
}
}

public enum JSONError: Error {
/// The JSON response was different to that requested
case unexpectedRootNode(Any)
}


//////////////////////////////////////////////////////////// Cancellation

public protocol CancellableError: Error {
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 isCancelledError: Bool {
if let ce = self as? CancellableError {
return ce.isCancelled
} else {
#if SWIFT_PACKAGE
return false
#else
let ne = self as NSError
return cancelledErrorIdentifiers.contains(ErrorPair(ne.domain, ne.code))
#endif
public var isCancelled: Bool {
do {
throw self
} catch PMKError.cancelled {
return true
} catch let error as CancellableError {
return error.isCancelled
} catch let error as NSError {
switch (error.domain, error.code) {
case (NSCocoaErrorDomain, CocoaError.userCancelled.rawValue):
return true
case (NSURLErrorDomain, URLError.cancelled.rawValue):
return true
default:
return false
}
} catch {
return false // Linux requires this
}
}
}


//////////////////////////////////////////////////////// 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
}
}
public enum CatchPolicy {
case allErrors
case allErrorsExceptCancellation
}
76 changes: 0 additions & 76 deletions Sources/GlobalState.m

This file was deleted.

145 changes: 145 additions & 0 deletions Sources/Guarantee.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
import class Foundation.Thread
import Dispatch

public class Guarantee<T>: Thenable {
let box: Box<T>

public init(value: T) {
box = SealedBox(value: value)
}

public init(_: PMKUnambiguousInitializer, resolver body: (@escaping(T) -> Void) -> Void) {
box = EmptyBox()
body(box.seal)
}

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

public var result: Result<T>? {
switch box.inspect() {
case .pending:
return nil
case .resolved(let value):
return .fulfilled(value)
}
}

init(_: PMKUnambiguousInitializer) {
box = EmptyBox()
}

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, _ body: @escaping(T) -> Void) -> Guarantee<Void> {
let rg = Guarantee<Void>(.pending)
pipe { (value: T) in
on.async {
body(value)
rg.box.seal(())
}
}
return rg
}

func map<U>(on: DispatchQueue? = conf.Q.map, _ body: @escaping(T) -> U) -> Guarantee<U> {
let rg = Guarantee<U>(.pending)
pipe { value in
on.async {
rg.box.seal(body(value))
}
}
return rg
}

@discardableResult
func then<U>(on: DispatchQueue? = conf.Q.map, _ body: @escaping(T) -> Guarantee<U>) -> Guarantee<U> {
let rg = Guarantee<U>(.pending)
pipe { value in
on.async {
body(value).pipe(to: rg.box.seal)
}
}
return rg
}

public 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.
*/
public func wait() -> T {

if Thread.isMainThread {
print("PromiseKit: warning: `wait()` called on main thread!")
}

var result = value

if result == nil {
let group = DispatchGroup()
group.enter()
pipe { (foo: T) in result = foo; group.leave() }
group.wait()
}

return result!
}
}

#if swift(>=3.1)
public extension Guarantee where T == Void {
convenience init() {
self.init(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.
*/
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
}
}
8 changes: 7 additions & 1 deletion Sources/PMKCallVariadicBlock.m
Original file line number Diff line number Diff line change
@@ -109,6 +109,12 @@ static id PMKCallVariadicBlock(id frock, id result) {
@try {
return _PMKCallVariadicBlock(frock, result);
} @catch (id thrown) {
return PMKProcessUnhandledException(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;
}
}
41 changes: 0 additions & 41 deletions Sources/Promise+AnyPromise.swift

This file was deleted.

57 changes: 0 additions & 57 deletions Sources/Promise+Properties.swift

This file was deleted.

676 changes: 92 additions & 584 deletions Sources/Promise.swift

Large diffs are not rendered by default.

77 changes: 77 additions & 0 deletions Sources/Resolver.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
public class Resolver<T> {
let box: Box<Result<T>>

init(_ box: Box<Result<T>>) {
self.box = box
}

deinit {
if case .pending = box.inspect() {
print("PromiseKit: warning: pending promise deallocated")
}
}
}

public extension Resolver {
func fulfill(_ value: T) {
box.seal(.fulfilled(value))
}

func reject(_ error: Error) {
box.seal(.rejected(error))
}

public func resolve(_ result: Result<T>) {
box.seal(result)
}

public func resolve(_ obj: T?, _ error: Error?) {
if let error = error {
reject(error)
} else if let obj = obj {
fulfill(obj)
} else {
reject(PMKError.invalidCallingConvention)
}
}

public func resolve(_ obj: T, _ error: Error?) {
if let error = error {
reject(error)
} else {
fulfill(obj)
}
}

public func resolve(_ error: Error?, _ obj: T?) {
resolve(obj, error)
}
}

#if swift(>=3.1)
extension Resolver where T == Void {
public func resolve(_ error: Error?) {
if let error = error {
reject(error)
} else {
fulfill(())
}
}
}
#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 changes: 0 additions & 219 deletions Sources/State.swift

This file was deleted.

16 changes: 0 additions & 16 deletions Sources/SwiftPM.swift

This file was deleted.

226 changes: 226 additions & 0 deletions Sources/Thenable.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,226 @@
import Dispatch


//TODO fire off a message to the Queue's runloop to avoid zalgo
// if no runloop, show warning about zalgo
// in case of `nil` don't do that and also don't dispatch to queue later
// in caes of `main` we can optimize and avoid a dispatch if we're on the main queue and zalgo was avoided


public protocol Thenable: class {
associatedtype T
func pipe(to: @escaping(Result<T>) -> Void)
var result: Result<T>? { get }
}

public extension Thenable {
public func then<U: Thenable>(on: DispatchQueue? = conf.Q.map, file: StaticString = #file, line: UInt = #line, _ body: @escaping(T) throws -> U) -> Promise<U.T> {
let rp = Promise<U.T>(.pending)
pipe {
switch $0 {
case .fulfilled(let value):
on.async {
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
}

func map<U>(on: DispatchQueue? = conf.Q.map, _ transform: @escaping(T) throws -> U) -> Promise<U> {
let rp = Promise<U>(.pending)
pipe {
switch $0 {
case .fulfilled(let value):
on.async {
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
}

func flatMap<U>(on: DispatchQueue? = conf.Q.map, _ transform: @escaping(T) throws -> U?) -> Promise<U> {
let rp = Promise<U>(.pending)
pipe {
switch $0 {
case .fulfilled(let value):
on.async {
do {
if let rv = try transform(value) {
rp.box.seal(.fulfilled(rv))
} else {
throw PMKError.flatMap(value, U.self)
}
} catch {
rp.box.seal(.rejected(error))
}
}
case .rejected(let error):
rp.box.seal(.rejected(error))
}
}
return rp
}

func done(on: DispatchQueue? = conf.Q.return, _ body: @escaping(T) throws -> Void) -> Promise<Void> {
let rp = Promise<Void>(.pending)
pipe {
switch $0 {
case .fulfilled(let value):
on.async {
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
}

/// Immutably access the fulfilled value; the returned Promise maintains that value.
func get(_ body: @escaping (T) throws -> Void) -> Promise<T> {
return map(on: PromiseKit.conf.Q.return) {
try body($0)
return $0
}
}

public 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 {
func map<U>(on: DispatchQueue? = conf.Q.map, _ transform: @escaping(T.Iterator.Element) throws -> U) -> Promise<[U]> {
return map(on: on){ try $0.map(transform) }
}

func thenMap<U: Thenable>(on: DispatchQueue? = conf.Q.map, _ transform: @escaping(T.Iterator.Element) throws -> U) -> Promise<[U.T]> {
return then(on: on) {
when(fulfilled: try $0.map(transform))
}
}

func flatMap<U>(on: DispatchQueue? = conf.Q.map, _ transform: @escaping(T.Iterator.Element) throws -> U?) -> Promise<[U]> {
return map(on: on){ try $0.flatMap(transform) }
}

func thenFlatMap<U: Thenable>(on: DispatchQueue? = conf.Q.map, _ transform: @escaping(T.Iterator.Element) throws -> U) -> Promise<[U.T.Iterator.Element]> where U.T: Sequence {
return then(on: on){
when(fulfilled: try $0.map(transform))
}.map(on: nil) {
$0.flatMap{ $0 }
}
}

func filter(on: DispatchQueue? = conf.Q.map, test: @escaping (T.Iterator.Element) -> Bool) -> Promise<[T.Iterator.Element]> {
return map(on: on) { $0.filter(test) }
}
}

public extension Thenable where T: Collection {
var first: Promise<T.Iterator.Element> {
return map(on: nil) { aa in
if let a1 = aa.first {
return a1
} else {
throw PMKError.badInput
}
}
}

var last: Promise<T.Iterator.Element> {
return map(on: nil) { aa in
if aa.isEmpty {
throw PMKError.badInput
} else {
let i = aa.index(aa.endIndex, offsetBy: -1)
return aa[i]
}
}
}
}

public extension Thenable where T: Sequence, T.Iterator.Element: Comparable {
func sorted(on: DispatchQueue? = conf.Q.map) -> Promise<[T.Iterator.Element]> {
return map(on: on){ $0.sorted() }
}
}
80 changes: 0 additions & 80 deletions Sources/Zalgo.swift

This file was deleted.

45 changes: 23 additions & 22 deletions Sources/after.swift
Original file line number Diff line number Diff line change
@@ -1,37 +1,38 @@
import struct Foundation.TimeInterval
import Dispatch

/**
- Returns: A new promise that fulfills after the specified duration.
*/
@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 new promise that fulfills after the specified duration.
*/
public func after(seconds: TimeInterval) -> Promise<Void> {
return Promise { fulfill, _ in
let when = DispatchTime.now() + seconds
DispatchQueue.global().asyncAfter(deadline: when) { fulfill(()) }
}
public func after(seconds: TimeInterval) -> Guarantee<Void> {
let (rg, seal) = Guarantee<Void>.pending()
let when = DispatchTime.now() + seconds
#if swift(>=4.0)
DispatchQueue.global().asyncAfter(deadline: when) { seal(()) }
#else
DispatchQueue.global().asyncAfter(deadline: when, execute: seal)
#endif
return rg
}

/**
after(seconds: 1.5).then {
//…
}

- 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
}
public func after(_ interval: DispatchTimeInterval) -> Guarantee<Void> {
let (rg, seal) = Guarantee<Void>.pending()
let when = DispatchTime.now() + interval
#if swift(>=4.0)
DispatchQueue.global().asyncAfter(deadline: when) { seal(()) }
#else
DispatchQueue.global().asyncAfter(deadline: when, execute: seal)
#endif
return rg
}
37 changes: 37 additions & 0 deletions Sources/firstly.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
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)
}
*/
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()
}
79 changes: 0 additions & 79 deletions Sources/fwd.h
Original file line number Diff line number Diff line change
@@ -10,7 +10,6 @@ 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
@@ -117,51 +116,6 @@ 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.
@@ -202,39 +156,6 @@ 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))

/**
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.
*/
#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,
};
2 changes: 1 addition & 1 deletion Sources/hang.m
Original file line number Diff line number Diff line change
@@ -15,7 +15,7 @@ id PMKHang(AnyPromise *promise) {
CFRunLoopSourceRef runLoopSource = CFRunLoopSourceCreate(NULL, 0, &context);
CFRunLoopAddSource(runLoop, runLoopSource, kCFRunLoopDefaultMode);

promise.always(^{
promise.ensure(^{
CFRunLoopStop(runLoop);
});
while (promise.pending) {
60 changes: 0 additions & 60 deletions Sources/join.swift

This file was deleted.

48 changes: 34 additions & 14 deletions Sources/race.swift
Original file line number Diff line number Diff line change
@@ -1,3 +1,12 @@
@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
}

/**
Resolves with the first resolving promise from a set of promises.

@@ -9,11 +18,8 @@
- Warning: If any of the provided promises reject, the returned promise is rejected.
- Warning: aborts if the array is empty.
*/
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)
public func race<U: Thenable>(_ thenables: U...) -> Promise<U.T> {
return _race(thenables)
}

/**
@@ -25,16 +31,30 @@ public func race<T>(promises: [Promise<T>]) -> Promise<T> {

- 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.
- Remark: Returns promise rejected with PMKError.badInput if empty array provided
*/
public func race<T>(_ promises: Promise<T>...) -> Promise<T> {
return _race(promises: promises)
public func race<U: Thenable>(_ thenables: [U]) -> Promise<U.T> {
guard !thenables.isEmpty else {
return Promise(error: PMKError.badInput)
}
return _race(thenables)
}

private func _race<T>(promises: [Promise<T>]) -> Promise<T> {
return Promise(sealant: { resolve in
for promise in promises {
promise.state.pipe(resolve)
}
})
/**
Resolves with the first resolving Guarantee from a set of promises.

race(promise1, promise2, promise3).then { winner in
//…
}

- Returns: A new guarantee that resolves when the first promise in the provided promises resolves.
- Warning: If any of the provided promises reject, the returned promise is rejected.
- Remark: Returns promise rejected with PMKError.badInput if empty array provided
*/
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
}
130 changes: 66 additions & 64 deletions Sources/when.swift
Original file line number Diff line number Diff line change
@@ -1,55 +1,46 @@
import Foundation
import Dispatch

private func _when<T>(_ promises: [Promise<T>]) -> Promise<Void> {
let root = Promise<Void>.pending()
var countdown = promises.count
private func _when<U: Thenable>(_ thenables: [U]) -> Promise<Void> {
var countdown = thenables.count
guard countdown > 0 else {
#if swift(>=4.0)
root.fulfill(())
#else
root.fulfill()
#endif
return root.promise
return Promise(value: Void())
}

let rp = Promise<Void>(.pending)

#if PMKDisableProgress || os(Linux)
var progress: (completedUnitCount: Int, totalUnitCount: Int) = (0, 0)
#else
let progress = Progress(totalUnitCount: Int64(promises.count))
let progress = Progress(totalUnitCount: Int64(thenables.count))
progress.isCancellable = false
progress.isPausable = false
#endif

let barrier = DispatchQueue(label: "org.promisekit.barrier.when", attributes: .concurrent)

for promise in promises {
promise.state.pipe { resolution in
for promise in thenables {
promise.pipe { result in
barrier.sync(flags: .barrier) {
switch resolution {
case .rejected(let error, let token):
token.consumed = true
if root.promise.isPending {
switch result {
case .rejected(let error):
if rp.isPending {
progress.completedUnitCount = progress.totalUnitCount
root.reject(error)
rp.box.seal(.rejected(error))
}
case .fulfilled:
guard root.promise.isPending else { return }
guard rp.isPending else { return }
progress.completedUnitCount += 1
countdown -= 1
if countdown == 0 {
#if swift(>=4.0)
root.fulfill(())
#else
root.fulfill()
#endif
rp.box.seal(.fulfilled(()))
}
}
}
}
}

return root.promise
return rp
}

/**
@@ -75,38 +66,38 @@ private func _when<T>(_ promises: [Promise<T>]) -> Promise<Void> {
- Note: `when` provides `NSProgress`.
- SeeAlso: `when(resolved:)`
*/
public func when<T>(fulfilled promises: [Promise<T>]) -> Promise<[T]> {
return _when(promises).then(on: zalgo) { promises.map{ $0.value! } }
public func when<U: Thenable>(fulfilled thenables: [U]) -> Promise<[U.T]> {
return _when(thenables).map(on: nil) { thenables.map{ $0.value! } }
}

/// Wait for all promises in a set to fulfill.
public func when(fulfilled promises: Promise<Void>...) -> Promise<Void> {
public func when<U: Thenable>(fulfilled promises: U...) -> Promise<Void> where U.T == Void {
return _when(promises)
}

/// Wait for all promises in a set to fulfill.
public func when(fulfilled promises: [Promise<Void>]) -> Promise<Void> {
public func when<U: Thenable>(fulfilled promises: [U]) -> Promise<Void> where U.T == Void {
return _when(promises)
}

/// Wait for all promises in a set to fulfill.
public func when<U, V>(fulfilled pu: Promise<U>, _ pv: Promise<V>) -> Promise<(U, V)> {
return _when([pu.asVoid(), pv.asVoid()]).then(on: zalgo) { (pu.value!, pv.value!) }
public func when<U: Thenable, V: Thenable>(fulfilled pu: U, _ pv: V) -> Promise<(U.T, V.T)> {
return _when([pu.asVoid(), pv.asVoid()]).map(on: nil) { (pu.value!, pv.value!) }
}

/// Wait for all promises in a set to fulfill.
public func when<U, 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!) }
public func when<U: Thenable, V: Thenable, W: Thenable>(fulfilled pu: U, _ pv: V, _ pw: W) -> Promise<(U.T, V.T, W.T)> {
return _when([pu.asVoid(), pv.asVoid(), pw.asVoid()]).map(on: nil) { (pu.value!, pv.value!, pw.value!) }
}

/// Wait for all promises in a set to fulfill.
public func when<U, 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!) }
public func when<U: Thenable, V: Thenable, W: Thenable, X: Thenable>(fulfilled pu: U, _ pv: V, _ pw: W, _ px: X) -> Promise<(U.T, V.T, W.T, X.T)> {
return _when([pu.asVoid(), pv.asVoid(), pw.asVoid(), px.asVoid()]).map(on: nil) { (pu.value!, pv.value!, pw.value!, px.value!) }
}

/// Wait for all promises in a set to fulfill.
public func when<U, 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!) }
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!) }
}

/**
@@ -125,30 +116,32 @@ public func when<U, V, W, X, Y>(fulfilled pu: Promise<U>, _ pv: Promise<V>, _ pw
guard url = urlGenerator.next() else {
return nil
}

return downloadFile(url)
}

when(generator, concurrently: 3).then { datum: [Data] -> Void in
when(generator, concurrently: 3).done { datas in
// ...
}

No more than three downloads will occur simultaneously.

- Note: The generator is called *serially* on a *background* queue.
- Warning: Refer to the warnings on `when(fulfilled:)`
- Parameter promiseGenerator: Generator of promises.
- Returns: A new promise that resolves when all the provided promises fulfill or one of the provided promises rejects.
- SeeAlso: `when(resolved:)`
*/

public func when<T, PromiseIterator: IteratorProtocol>(fulfilled promiseIterator: PromiseIterator, concurrently: Int) -> Promise<[T]> where PromiseIterator.Element == Promise<T> {
public func when<It: IteratorProtocol>(fulfilled promiseIterator: It, concurrently: Int) -> Promise<[It.Element.T]> where It.Element: Thenable {

guard concurrently > 0 else {
return Promise(error: PMKError.whenConcurrentlyZero)
return Promise(error: PMKError.badInput)
}

var generator = promiseIterator
var root = Promise<[T]>.pending()
var root = Promise<[It.Element.T]>.pending()
var pendingPromises = 0
var promises: [Promise<T>] = []
var promises: [It.Element] = []

let barrier = DispatchQueue(label: "org.promisekit.barrier.when", attributes: [.concurrent])

@@ -162,7 +155,7 @@ public func when<T, PromiseIterator: IteratorProtocol>(fulfilled promiseIterator
guard shouldDequeue else { return }

var index: Int!
var promise: Promise<T>!
var promise: It.Element!

barrier.sync(flags: .barrier) {
guard let next = generator.next() else { return }
@@ -177,7 +170,7 @@ public func when<T, PromiseIterator: IteratorProtocol>(fulfilled promiseIterator
func testDone() {
barrier.sync {
if pendingPromises == 0 {
root.fulfill(promises.flatMap{ $0.value })
root.resolver.fulfill(promises.flatMap{ $0.value })
}
}
}
@@ -186,7 +179,7 @@ public func when<T, PromiseIterator: IteratorProtocol>(fulfilled promiseIterator
return testDone()
}

promise.state.pipe { resolution in
promise.pipe { resolution in
barrier.sync(flags: .barrier) {
pendingPromises -= 1
}
@@ -195,9 +188,8 @@ public func when<T, PromiseIterator: IteratorProtocol>(fulfilled promiseIterator
case .fulfilled:
dequeue()
testDone()
case .rejected(let error, let token):
token.consumed = true
root.reject(error)
case .rejected(let error):
root.resolver.reject(error)
}
}

@@ -225,33 +217,43 @@ public func when<T, PromiseIterator: IteratorProtocol>(fulfilled promiseIterator
- 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.
- Remark: Doesn't take Thenable due to protocol associatedtype paradox
*/
public func when<T>(resolved promises: Promise<T>...) -> Promise<[Result<T>]> {
public func when<T>(resolved promises: Promise<T>...) -> Guarantee<[Result<T>]> {
return when(resolved: promises)
}

/// Waits on all provided promises.
public func when<T>(resolved promises: [Promise<T>]) -> Promise<[Result<T>]> {
guard !promises.isEmpty else { return Promise(value: []) }
public func when<T>(resolved promises: [Promise<T>]) -> Guarantee<[Result<T>]> {
guard !promises.isEmpty else {
return Guarantee(value: [])
}

var countdown = promises.count
let barrier = DispatchQueue(label: "org.promisekit.barrier.join", attributes: .concurrent)

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()!) })
let rg = Guarantee<[Result<T>]>(.pending)
for promise in promises {
promise.pipe { result in
barrier.sync(flags: .barrier) {
countdown -= 1
}
barrier.sync {
if countdown == 0 {
rg.box.seal(promises.map{ $0.result! })
}
}
}
}
return rg
}

/// Waits on all provided Guarantees.
public func when(_ guarantees: Guarantee<Void>...) -> Guarantee<Void> {
return when(guarantees: guarantees)
}

// Waits on all provided Guarantees.
public func when(guarantees: [Guarantee<Void>]) -> Guarantee<Void> {
return when(fulfilled: guarantees).recover{ _ in }.asVoid()
}
79 changes: 0 additions & 79 deletions Sources/wrap.swift

This file was deleted.

25 changes: 14 additions & 11 deletions Tests/A+/0.0.0.swift
Original file line number Diff line number Diff line change
@@ -10,19 +10,22 @@ private let timeout: TimeInterval = 10

extension XCTestCase {
func describe(_ description: String, file: StaticString = #file, line: UInt = #line, body: () throws -> Void) {

PromiseKit.conf.Q.map = .main

do {
try body()
} catch {
XCTFail(description, file: file, line: line)
}
}

func specify(_ description: String, file: StaticString = #file, line: UInt = #line, body: (Promise<Void>.PendingTuple, XCTestExpectation) throws -> Void) {
func specify(_ description: String, file: StaticString = #file, line: UInt = #line, body: ((promise: Promise<Void>, fulfill: () -> Void, reject: (Error) -> Void), XCTestExpectation) throws -> Void) {
let expectation = self.expectation(description: description)
let pending = Promise<Void>.pending()
let (pending, seal) = Promise<Void>.pending()

do {
try body(pending, expectation)
try body((pending, seal.fulfill, seal.reject), expectation)
waitForExpectations(timeout: timeout) { err in
if let _ = err {
XCTFail("wait failed: \(description)", file: file, line: line)
@@ -53,16 +56,16 @@ extension XCTestCase {
return (Promise(value: value), {})
}
specify("immediately-fulfilled") { value in
let (promise, fulfill, _) = Promise<UInt32>.pending()
let (promise, seal) = Promise<UInt32>.pending()
return (promise, {
fulfill(value)
seal.fulfill(value)
})
}
specify("eventually-fulfilled") { value in
let (promise, fulfill, _) = Promise<UInt32>.pending()
let (promise, seal) = Promise<UInt32>.pending()
return (promise, {
after(ticks: 5) {
fulfill(value)
seal.fulfill(value)
}
})
}
@@ -76,16 +79,16 @@ extension XCTestCase {
return (Promise(error: Error.sentinel(sentinel)), {})
}
specify("immediately-rejected") { sentinel in
let (promise, _, reject) = Promise<UInt32>.pending()
let (promise, seal) = Promise<UInt32>.pending()
return (promise, {
reject(Error.sentinel(sentinel))
seal.reject(Error.sentinel(sentinel))
})
}
specify("eventually-rejected") { sentinel in
let (promise, _, reject) = Promise<UInt32>.pending()
let (promise, seal) = Promise<UInt32>.pending()
return (promise, {
after(ticks: 50) {
reject(Error.sentinel(sentinel))
seal.reject(Error.sentinel(sentinel))
}
})
}
4 changes: 2 additions & 2 deletions Tests/A+/2.1.2.swift
Original file line number Diff line number Diff line change
@@ -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)
}
}
6 changes: 3 additions & 3 deletions Tests/A+/2.1.3.swift
Original file line number Diff line number Diff line change
@@ -11,22 +11,22 @@ 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()
}
}

specify("trying to reject immediately then fulfill delayed") { d, expectation in
d.promise.test(onFulfilled: { XCTFail() }, onRejected: expectation.fulfill)
d.reject(Error.dummy)
after(ticks: 1) {
d.fulfill(())
d.fulfill()
}
}
}
44 changes: 22 additions & 22 deletions Tests/A+/2.2.2.swift
Original file line number Diff line number Diff line change
@@ -6,8 +6,8 @@ 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.then { value -> Void in
XCTAssertEqual(sentinel, value)
promise.done {
XCTAssertEqual(sentinel, $0)
expectation.fulfill()
}
}
@@ -16,75 +16,75 @@ class Test222: XCTestCase {
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.then { _ -> Void in
d.promise.done {
called = true
expectation.fulfill()
}
after(ticks: 5) {
XCTAssertFalse(called)
d.fulfill(())
d.fulfill()
}
}
specify("never fulfilled") { d, expectation in
d.promise.then{ XCTFail() }
d.promise.done{ XCTFail() }
after(ticks: 1000, execute: expectation.fulfill)
}
}

describe("2.2.2.3: it must not be called more than once.") {
specify("already-fulfilled") { _, expectation in
let ex = (expectation, mkex())
Promise(value: ()).then {
Promise(value: ()).done {
ex.0.fulfill()
}
after(ticks: 1000) {
ex.1.fulfill()
}
}
specify("trying to fulfill a pending promise more than once, immediately") { d, expectation in
d.promise.then(execute: expectation.fulfill)
d.fulfill(())
d.fulfill(())
d.promise.done(expectation.fulfill)
d.fulfill()
d.fulfill()
}
specify("trying to fulfill a pending promise more than once, delayed") { d, expectation in
d.promise.then(execute: expectation.fulfill)
d.promise.done(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.then(execute: ex.0.fulfill)
d.fulfill(())
d.promise.done(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
var ex = (expectation, self.expectation(description: ""), self.expectation(description: ""), self.expectation(description: ""))

do {
d.promise.then(execute: ex.0.fulfill)
d.promise.done(ex.0.fulfill)
}
after(ticks: 5) {
d.promise.then(execute: ex.1.fulfill)
d.promise.done(ex.1.fulfill)
}
after(ticks: 10) {
d.promise.then(execute: ex.2.fulfill)
d.promise.done(ex.2.fulfill)
}
after(ticks: 15) {
d.fulfill(())
d.fulfill()
ex.3.fulfill()
}
}
specify("when `then` is interleaved with fulfillment") { d, expectation in
var ex = (expectation, self.expectation(description: ""), self)

d.promise.then(execute: ex.0.fulfill)
d.fulfill(())
d.promise.then(execute: ex.1.fulfill)
d.promise.done(ex.0.fulfill)
d.fulfill()
d.promise.done(ex.1.fulfill)
}
}
}
22 changes: 11 additions & 11 deletions Tests/A+/2.2.4.swift
Original file line number Diff line number Diff line change
@@ -8,7 +8,7 @@ class Test224: XCTestCase {
describe("`then` returns before the promise becomes fulfilled or rejected") {
testFulfilled { promise, expectation, dummy in
var thenHasReturned = false
promise.then { _ -> Void in
promise.done { _ in
XCTAssert(thenHasReturned)
expectation.fulfill()
}
@@ -28,17 +28,17 @@ 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.then { _ -> Void in
d.promise.done {
onFulfilledCalled = true
expectation.fulfill()
}
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.then { _ -> Void in
d.fulfill()
d.promise.done {
onFulfilledCalled = true
expectation.fulfill()
}
@@ -47,8 +47,8 @@ class Test224: XCTestCase {
specify("when one `onFulfilled` is added inside another `onFulfilled`") { _, expectation in
var firstOnFulfilledFinished = false
let promise = Promise(value: ())
promise.then { _ -> Void in
promise.then { _ -> Void in
promise.done {
promise.done {
XCTAssertTrue(firstOnFulfilledFinished)
expectation.fulfill()
}
@@ -62,7 +62,7 @@ class Test224: XCTestCase {
var firstOnRejectedFinished = false

promise1.catch { _ in
promise2.then { _ -> Void in
promise2.done {
XCTAssertTrue(firstOnRejectedFinished)
expectation.fulfill()
}
@@ -74,11 +74,11 @@ class Test224: XCTestCase {
var firstStackFinished = false

after(ticks: 1) {
d.fulfill(())
d.fulfill()
firstStackFinished = true
}

d.promise.then { _ -> Void in
d.promise.done {
XCTAssertTrue(firstStackFinished)
expectation.fulfill()
}
@@ -109,7 +109,7 @@ class Test224: XCTestCase {
var promise2 = Promise<Void>(error: Error.dummy)
var firstOnFulfilledFinished = false

promise1.then { _ -> Void in
promise1.done { _ in
promise2.catch { _ in
XCTAssertTrue(firstOnFulfilledFinished)
expectation.fulfill()
120 changes: 62 additions & 58 deletions Tests/A+/2.2.6.swift
Original file line number Diff line number Diff line change
@@ -8,26 +8,26 @@ class Test226: XCTestCase {
describe("multiple boring fulfillment handlers") {
testFulfilled(withExpectationCount: 4) { promise, exes, sentinel -> () in
var orderValidator = 0
promise.then { value -> Void in
XCTAssertEqual(value, sentinel)
promise.done {
XCTAssertEqual($0, sentinel)
XCTAssertEqual(++orderValidator, 1)
exes[0].fulfill()
}
promise.catch { _ in XCTFail() }
promise.then { value -> Void in
XCTAssertEqual(value, sentinel)
promise.done {
XCTAssertEqual($0, sentinel)
XCTAssertEqual(++orderValidator, 2)
exes[1].fulfill()
}
promise.catch { _ in XCTFail() }
promise.then { value -> Void in
XCTAssertEqual(value, sentinel)
promise.done {
XCTAssertEqual($0, sentinel)
XCTAssertEqual(++orderValidator, 3)
exes[2].fulfill()
}
promise.catch { _ in XCTFail() }
promise.then { value -> Void in
XCTAssertEqual(value, sentinel)
promise.done {
XCTAssertEqual($0, sentinel)
XCTAssertEqual(++orderValidator, 4)
exes[3].fulfill()
}
@@ -36,27 +36,27 @@ class Test226: XCTestCase {
describe("multiple fulfillment handlers, one of which throws") {
testFulfilled(withExpectationCount: 4) { promise, exes, sentinel in
var orderValidator = 0
promise.then { value -> Void in
XCTAssertEqual(value, sentinel)
promise.done {
XCTAssertEqual($0, sentinel)
XCTAssertEqual(++orderValidator, 1)
exes[0].fulfill()
}
promise.catch { _ in XCTFail() }
promise.then { value -> Void in
XCTAssertEqual(value, sentinel)
promise.done {
XCTAssertEqual($0, sentinel)
XCTAssertEqual(++orderValidator, 2)
exes[1].fulfill()
}
promise.catch { _ in XCTFail() }
promise.then { value -> Void in
XCTAssertEqual(value, sentinel)
promise.done {
XCTAssertEqual($0, sentinel)
XCTAssertEqual(++orderValidator, 3)
exes[2].fulfill()
throw Error.dummy
}
promise.catch { value in XCTFail() }
promise.then { value -> Void in
XCTAssertEqual(value, sentinel)
promise.done {
XCTAssertEqual($0, sentinel)
XCTAssertEqual(++orderValidator, 4)
exes[3].fulfill()
}
@@ -68,41 +68,41 @@ class Test226: XCTestCase {
let sentinel2 = 672
let sentinel3 = 673

promise.then { _ -> Int in
promise.map { _ in
return sentinel1
}.then { value -> Void in
XCTAssertEqual(sentinel1, value)
exes[0].fulfill()
}.done { value in
XCTAssertEqual(sentinel1, value)
exes[0].fulfill()
}

promise.then { _ -> Int in
promise.done { _ in
throw NSError(domain: PMKErrorDomain, code: sentinel2, userInfo: nil)
}.catch { err in
XCTAssertEqual((err as NSError).code, sentinel2)
exes[1].fulfill()
}.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()
promise.map { _ in
sentinel3
}.done {
XCTAssertEqual($0, sentinel3)
exes[2].fulfill()
}
}
}
describe("`onFulfilled` handlers are called in the original order") {
testFulfilled(withExpectationCount: 3) { promise, exes, memo in
var orderValidator = 0

promise.then { _ -> Void in
promise.done { _ in
XCTAssertEqual(++orderValidator, 1)
exes[0].fulfill()
}
promise.then { _ -> Void in
promise.done { _ in
XCTAssertEqual(++orderValidator, 2)
exes[1].fulfill()
}
promise.then { _ -> Void in
promise.done { _ in
XCTAssertEqual(++orderValidator, 3)
exes[2].fulfill()
}
@@ -111,17 +111,17 @@ class Test226: XCTestCase {
describe("even when one handler is added inside another handler") {
testFulfilled(withExpectationCount: 3) { promise, exes, memo in
var x = 0
promise.then { _ -> Void in
promise.done { _ in
XCTAssertEqual(x, 0)
x += 1
exes[0].fulfill()
promise.then{ _ -> Void in
promise.done { _ in
XCTAssertEqual(x, 2)
x += 1
exes[1].fulfill()
}
}
promise.then { _ -> Void in
promise.done { _ in
XCTAssertEqual(x, 1)
x += 1
exes[2].fulfill()
@@ -139,19 +139,19 @@ class Test226: XCTestCase {
XCTAssertEqual(++ticket, 1)
exes[0].fulfill()
}
promise.then { _ in XCTFail() }
promise.done { _ 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.then { _ in XCTFail() }
promise.done { _ 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.then { _ in XCTFail() }
promise.done { _ in XCTFail() }
promise.catch { err in
guard case Error.sentinel(let x) = err, x == sentinel else { return XCTFail() }
XCTAssertEqual(++ticket, 4)
@@ -168,20 +168,24 @@ class Test226: XCTestCase {
XCTAssertEqual(++orderValidator, 1)
exes[0].fulfill()
}
promise.then { _ in XCTFail() }
promise.done { _ 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.then { _ in XCTFail() }
promise.recover { err -> UInt32 in
guard case Error.sentinel(let x) = err, x == sentinel else { XCTFail(); return 123 }
promise.done { _ in XCTFail() }
promise.recover { err -> Promise<UInt32> in
if case Error.sentinel(let x) = err {
XCTAssertEqual(x, sentinel)
} else {
XCTFail()
}
XCTAssertEqual(++orderValidator, 3)
exes[2].fulfill()
throw Error.dummy
}
promise.then { _ in XCTFail() }
promise.done { _ in XCTFail() }
promise.catch { err in
guard case Error.sentinel(let x) = err, x == sentinel else { return XCTFail() }
XCTAssertEqual(++orderValidator, 4)
@@ -195,26 +199,26 @@ class Test226: XCTestCase {
let sentinel2 = arc4random()
let sentinel3 = arc4random()

promise.recover { _ -> UInt32 in
return sentinel1
}.then { value -> Void in
XCTAssertEqual(sentinel1, value)
exes[0].fulfill()
promise.recover { _ in
return Promise(value: sentinel1)
}.done { value in
XCTAssertEqual(sentinel1, value)
exes[0].fulfill()
}

promise.recover { _ -> UInt32 in
promise.recover { _ -> Promise<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 { _ -> UInt32 in
return sentinel3
}.then { value -> Void in
XCTAssertEqual(value, sentinel3)
exes[2].fulfill()
promise.recover { _ in
Promise(value: sentinel3)
}.done { value in
XCTAssertEqual(value, sentinel3)
exes[2].fulfill()
}
}
}
4 changes: 2 additions & 2 deletions Tests/A+/2.2.7.swift
Original file line number Diff line number Diff line change
@@ -8,7 +8,7 @@ class Test227: XCTestCase {

testFulfilled { promise1, expectation, _ in
let sentinel = arc4random()
let promise2 = promise1.then { _ in throw Error.sentinel(sentinel) }
let promise2 = promise1.done { _ 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 { _ -> UInt32 in throw Error.sentinel(sentinel) }
let promise2 = promise1.recover { _ -> Promise<UInt32> in throw Error.sentinel(sentinel) }

promise2.catch { error in
if case Error.sentinel(let x) = error, x == sentinel {
16 changes: 8 additions & 8 deletions Tests/A+/2.3.2.swift
Original file line number Diff line number Diff line change
@@ -33,8 +33,8 @@ class Test232: XCTestCase {
}

testPromiseResolution(factory: xFactory) { promise, expectation in
promise.then { value -> Void in
XCTAssertEqual(value, sentinel)
promise.done {
XCTAssertEqual($0, sentinel)
expectation.fulfill()
}
}
@@ -43,16 +43,16 @@ class Test232: XCTestCase {
let sentinel = arc4random()

func xFactory() -> Promise<UInt32> {
return Promise { fulfill, _ in
return Promise(.pending) { seal in
after(ticks: 2) {
fulfill(sentinel)
seal.fulfill(sentinel)
}
}
}

testPromiseResolution(factory: xFactory) { promise, expectation in
promise.then { value -> Void in
XCTAssertEqual(value, sentinel)
promise.done {
XCTAssertEqual($0, sentinel)
expectation.fulfill()
}
}
@@ -79,9 +79,9 @@ class Test232: XCTestCase {
let sentinel = arc4random()

func xFactory() -> Promise<UInt32> {
return Promise { _, reject in
return Promise(.pending) { seal in
after(ticks: 2) {
reject(Error.sentinel(sentinel))
seal.reject(Error.sentinel(sentinel))
}
}
}
Loading

0 comments on commit 47367f1

Please sign in to comment.