// 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 FBLPromises /// Promises synchronization construct in Swift. Leverages ObjC implementation internally. public final class Promise { public typealias ObjCPromise = FBLPromise /// Creates a new promise with an existing ObjC promise. public init(_ objCPromise: ObjCPromise) { guard let objCPromise = objCPromise as? ObjCPromise else { preconditionFailure("Cannot cast \(T.self) to \(AnyObject.self)") } self.objCPromise = objCPromise } /// Creates a new pending promise. public static func pending() -> Promise { return Promise.init(ObjCPromise.__pending()) } /// Creates a new pending promise. public convenience init() { self.init(ObjCPromise.__pending()) } /// Creates a new promise rejected with the given `error`. public convenience init(_ error: Error) { self.init(ObjCPromise.__resolved(with: error as NSError)) } /// Creates a new promise resolved with the result of `work` block. public convenience init(_ work: @autoclosure () throws -> Value) { do { let resolution = try work() if type(of: resolution) is NSError.Type { let error = resolution as! NSError self.init(error) } else if let objCPromise = resolution as? ObjCPromise { self.init(objCPromise) } else { self.init(ObjCPromise.__resolved(with: Promise.asAnyObject(resolution))) } } catch let error { self.init(error as NSError) } } /// Resolves `self` with the given `resolution`. public func fulfill(_ resolution: Value) { objCPromise.__fulfill(Promise.asAnyObject(resolution)) } /// Rejects `self` with the given `error`. public func reject(_ error: Error) { objCPromise.__fulfill(error as NSError) } /// Converts `self` into ObjC promise. public func asObjCPromise() -> ObjCPromise { guard let objCPromise = objCPromise as? ObjCPromise else { preconditionFailure("Cannot cast \(AnyObject.self) to \(T.self)") } return objCPromise } // MARK: Internal /// Underlying ObjC counterpart. let objCPromise: ObjCPromise var isPending: Bool { return objCPromise.__isPending } var isFulfilled: Bool { return objCPromise.__isFulfilled } var isRejected: Bool { return objCPromise.__isRejected } var value: Value? { let objCValue = objCPromise.__value if Promise.isBridgedNil(objCValue) { return nil } guard let value = objCValue as? Value else { preconditionFailure("Cannot cast \(type(of: objCValue)) to \(Value.self)") } return value } var error: Error? { guard let objCPromiseError = objCPromise.__error else { return nil } // Convert `NSError` to `PromiseError`, if applicable. return PromiseError(objCPromiseError) ?? objCPromiseError } /// Converts generic `Value` to `AnyObject`. static func asAnyObject(_ value: Value) -> AnyObject? { return Promise.isBridgedNil(value) ? nil : value as AnyObject } /// Converts `AnyObject` to generic `Value`, or `nil` if the conversion is not possible. static func asValue(_ value: AnyObject?) -> Value? { // Swift nil becomes NSNull during bridging. return (value as? Value) ?? NSNull() as AnyObject as? Value } // MARK: Private /// Checks if generic `Value` is bridged ObjC `nil`. private static func isBridgedNil(_ value: Value?) -> Bool { // Swift nil becomes NSNull during bridging. return !(value is NSNull) && (value as AnyObject is NSNull) } } extension Promise: CustomStringConvertible { public var description: String { var description = "nil" if isFulfilled { if let value = value { description = String(describing: value) } return "Fulfilled: \(description)" } if isRejected { if let error = error { description = String(describing: error) } return "Rejected: \(description)" } return "Pending: \(Value.self)" } } public extension DispatchQueue { /// Default dispatch queue used for `Promise`, which is `main` if a queue is not specified. static var promises: DispatchQueue { get { return Promise.ObjCPromise.__defaultDispatchQueue } set { Promise.ObjCPromise.__defaultDispatchQueue = newValue } } }