// Copyright 2018 Google Inc. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at: // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import Foundation /// Waits for all of the given promises to be fulfilled or rejected. /// If all promises are rejected, then the returned promise is rejected with same error /// as the last one rejected. /// If at least one of the promises is fulfilled, the resulting promise is fulfilled with an array /// of `Maybe` enums containing values or `Error`s, matching the original order of fulfilled or /// rejected promises respectively. /// - parameters: /// - queue: A queue to dispatch on. /// - promises: Promises to wait for. /// - returns: Promise of an array of `Maybe` enums containing the values or `Error`s of input /// promises in their original order. public func any( on queue: DispatchQueue = .promises, _ promises: Promise... ) -> Promise<[Maybe]> { return any(on: queue, promises) } /// Waits for all of the given promises to be fulfilled or rejected. /// If all promises are rejected, then the returned promise is rejected with same error /// as the last one rejected. /// If at least one of the promises is fulfilled, the resulting promise is fulfilled with an array /// of `Maybe` enums containing values or `Error`s, matching the original order of fulfilled or /// rejected promises respectively. /// - parameters: /// - queue: A queue to dispatch on. /// - promises: Promises to wait for. /// - returns: Promise of an array of `Maybe` enums containing the values or `Error`s of input /// promises in their original order. public func any( on queue: DispatchQueue = .promises, _ promises: Container ) -> Promise<[Maybe]> where Container.Element == Promise { let promises = promises.map { $0.objCPromise } let promise = Promise<[Maybe]>( Promise<[Maybe]>.ObjCPromise.__onQueue( queue, any: promises ).__onQueue(queue, then: { values in guard let values = values as [AnyObject]? else { preconditionFailure() } return Promise<[Maybe]>.asAnyObject(values.map { asMaybe($0) as Maybe }) }) ) // Keep Swift wrapper alive for chained promises until `ObjCPromise` counterpart is resolved. promises.forEach { $0.__addPendingObject(promise) } return promise } /// Waits for all of the given promises to be fulfilled or rejected. /// If all promises are rejected, then the returned promise is rejected with same error /// as the last one rejected. /// If at least one of the promises is fulfilled, the resulting promise is fulfilled with a tuple /// of `Maybe` enums containing values or `Error`s, matching the original order of fulfilled or /// rejected promises respectively. /// - parameters: /// - queue: A queue to dispatch on. /// - promiseA: Promise of type `A`. /// - promiseB: Promise of type `B`. /// - returns: Promise of a tuple of `Maybe` enums containing the values or `Error`s of input /// promises in their original order. public func any( on queue: DispatchQueue = .promises, _ promiseA: Promise, _ promiseB: Promise ) -> Promise<(Maybe, Maybe)> { let promises = [ promiseA.objCPromise, promiseB.objCPromise ] let promise = Promise<(Maybe, Maybe)>( Promise<(Maybe, Maybe)>.ObjCPromise.__onQueue( queue, any: promises ).__onQueue(queue, then: { objCValues in guard let values = objCValues as [AnyObject]? else { preconditionFailure() } let valueA = asMaybe(values[0]) as Maybe let valueB = asMaybe(values[1]) as Maybe return (valueA, valueB) }) ) // Keep Swift wrapper alive for chained promises until `ObjCPromise` counterpart is resolved. promises.forEach { $0.__addPendingObject(promise) } return promise } /// Waits for all of the given promises to be fulfilled or rejected. /// If all promises are rejected, then the returned promise is rejected with same error /// as the last one rejected. /// If at least one of the promises is fulfilled, the resulting promise is fulfilled with a tuple /// of `Maybe` enums containing values or `Error`s, matching the original order of fulfilled or /// rejected promises respectively. /// - parameters: /// - queue: A queue to dispatch on. /// - promiseA: Promise of type `A`. /// - promiseB: Promise of type `B`. /// - promiseC: Promise of type `C`. /// - returns: Promise of a tuple of `Maybe` enums containing the values or `Error`s of input /// promises in their original order. public func any( on queue: DispatchQueue = .promises, _ promiseA: Promise, _ promiseB: Promise, _ promiseC: Promise ) -> Promise<(Maybe, Maybe, Maybe)> { let promises = [ promiseA.objCPromise, promiseB.objCPromise, promiseC.objCPromise ] let promise = Promise<(Maybe, Maybe, Maybe)>( Promise<(Maybe, Maybe, Maybe)>.ObjCPromise.__onQueue( queue, any: promises ).__onQueue(queue, then: { objCValues in guard let values = objCValues as [AnyObject]? else { preconditionFailure() } let valueA = asMaybe(values[0]) as Maybe let valueB = asMaybe(values[1]) as Maybe let valueC = asMaybe(values[2]) as Maybe return (valueA, valueB, valueC) }) ) // Keep Swift wrapper alive for chained promises until `ObjCPromise` counterpart is resolved. promises.forEach { $0.__addPendingObject(promise) } return promise } /// Wrapper enum for `any` results. /// - value: Contains the value that corresponding promise was fulfilled with. /// - error: Contains the error that corresponding promise was rejected with. public enum Maybe { case value(Value) case error(Error) public init(_ value: Value) { self = .value(value) } public init(_ error: Error) { self = .error(error) } public var value: Value? { if case .value(let value) = self { return value } else { return nil } } public var error: Error? { if case .error(let error) = self { return error } else { return nil } } } // MARK: - Conversion /// Helper functions that facilitates conversion of `Promise.any` results to the results normally /// expected from `ObjCPromise.any`. /// /// Convert a promise created with `any` in Swift to Objective-C: /// /// any([promise1, promise2, promise3]).then { arrayOfMaybeEnums in /// return arrayOfMaybeEnums.map { $0.asAnyObject() } /// }.asObjCPromise() as Promise<[AnyObject?]>.ObjCPromise /// /// Convert a promise created with `any` in Objective-C to Swift: /// /// Promise<[AnyObject]>(objCPromise).then { arrayOfAnyObjects in /// return arrayOfAnyObjects.map { asMaybe($0) as Maybe } /// } public extension Maybe { /// Converts generic `Value` to `AnyObject`. func asAnyObject() -> AnyObject? { switch self { case .value(let value): return Promise.asAnyObject(value) case .error(let error): return error as NSError } } } /// Helper function to wrap the results of `ObjCPromise.any` with the safe `Maybe` enum. public func asMaybe(_ value: AnyObject) -> Maybe { if type(of: value) is NSError.Type { return .error(value as! NSError) } else { guard let value = Promise.asValue(value) else { preconditionFailure() } return .value(value) } } // MARK: - Equatable /// Equality operators for `Maybe`. #if !swift(>=4.1) extension Maybe where Value: Equatable {} #else extension Maybe: Equatable where Value: Equatable {} #endif // !swift(>=4.1) public func == (lhs: Maybe, rhs: Maybe) -> Bool { switch (lhs, rhs) { case (.value(let lhs), .value(let rhs)): return lhs == rhs case (.error(let lhs), .error(let rhs)): return (lhs as NSError).isEqual(rhs as NSError) case (.value, .error), (.error, .value): return false } } public func != (lhs: Maybe, rhs: Maybe) -> Bool { return !(lhs == rhs) } #if !swift(>=4.1) public func == (lhs: Maybe, rhs: Maybe) -> Bool { switch (lhs, rhs) { case (.value(let lhs), .value(let rhs)): switch (lhs, rhs) { case (nil, nil): return true case (nil, _?), (_?, nil): return false case let (lhs?, rhs?): return lhs == rhs } case (.error(let lhs), .error(let rhs)): return (lhs as NSError).isEqual(rhs as NSError) case (.value, .error), (.error, .value): return false } } public func != (lhs: Maybe, rhs: Maybe) -> Bool { return !(lhs == rhs) } public func == (lhs: [Maybe], rhs: [Maybe]) -> Bool { if lhs.count != rhs.count { return false } for (lhs, rhs) in zip(lhs, rhs) where lhs != rhs { return false } return true } public func != (lhs: [Maybe], rhs: [Maybe]) -> Bool { return !(lhs == rhs) } public func == (lhs: [Maybe], rhs: [Maybe]) -> Bool { if lhs.count != rhs.count { return false } for (lhs, rhs) in zip(lhs, rhs) where lhs != rhs { return false } return true } public func != (lhs: [Maybe], rhs: [Maybe]) -> Bool { return !(lhs == rhs) } #endif // !swift(>=4.1)