// // Copyright 2024 Signal Messenger, LLC // SPDX-License-Identifier: AGPL-3.0-only // import Foundation public enum PromiseError: String, Error { case cancelled case whenResolvedRejected } public final class Promise: Thenable, Catchable { private let future: Future public var result: Result? { future.result } public var isSealed: Bool { future.isSealed } public init(future: Future = Future()) { self.future = future } public convenience init() { self.init(future: Future()) } public static func value(_ value: Value) -> Self { let promise = Self() promise.future.resolve(value) return promise } public convenience init(error: Error) { self.init(future: Future(error: error)) } public convenience init( _ block: (Future) throws -> Void ) { self.init() do { try block(self.future) } catch { self.future.reject(error) } } public convenience init( on scheduler: Scheduler, _ block: @escaping (Future) throws -> Void ) { self.init() scheduler.asyncIfNecessary { do { try block(self.future) } catch { self.future.reject(error) } } } public func observe(on scheduler: Scheduler? = nil, block: @escaping (Result) -> Void) { future.observe(on: scheduler, block: block) } } public extension Promise { func wait() throws -> Value { var result = future.result if result == nil { let group = DispatchGroup() group.enter() observe(on: DispatchQueue.global()) { result = $0; group.leave() } group.wait() } switch result! { case .success(let value): return value case .failure(let error): throw error } } } extension Promise { /// Wraps a Swift Concurrency async function in a Promise. /// /// The Task is created with the default arguments. To configure the task's /// priority, the caller should create its own Promise instance. public static func wrapAsync(_ block: @escaping () async throws -> Value) -> Self { let promise = Self() Task { do { promise.future.resolve(try await block()) } catch { promise.future.reject(error) } } return promise } /// Converts a Promise to a Swift Concurrency async function. public func awaitable() async throws -> Value { try await withCheckedThrowingContinuation { continuation in self.observe(on: SyncScheduler()) { result in continuation.resume(with: result) } } } /// Converts a Promise to a Swift Concurrency async function. /// /// When the Swift Concurrency Task is canceled, this stops waiting for the /// result and throws a CancellationError. public func awaitableWithUncooperativeCancellationHandling() async throws -> Value { let cancellableContinuation = CancellableContinuation() self.observe(on: SyncScheduler()) { result in cancellableContinuation.resume(with: result) } return try await cancellableContinuation.wait() } } public extension Promise { class func pending() -> (Promise, Future) { let promise = Promise() return (promise, promise.future) } } public extension Guarantee { func asPromise() -> Promise { let (promise, future) = Promise.pending() observe { result in switch result { case .success(let value): future.resolve(value) case .failure(let error): owsFail("Unexpectedly received error result from unfailable promise \(error)") } } return promise } }