idb_companion/Utility/FutureBox.swift (40 lines of code) (raw):
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
import Foundation
import FBControlCore
private let futureSerialFullfillmentQueue = DispatchQueue(label: "com.facebook.fbfuture.fullfilment")
enum FBFutureError: Error {
case continuationFullfilledWithoutValues
}
/// Swift compiler does not allow usage of generic parameters of objc classes in extension
/// so we need to create a bridge class
final class FutureBox<T: AnyObject> {
let future: FBFuture<T>
init(_ future: FBFuture<T>) {
self.future = future
}
/// Interop between swift and objc generics are quite bad, so we have to write wrappers like this
init(_ mutableFuture: FBMutableFuture<T>) {
let future: FBFuture<AnyObject> = mutableFuture
self.future = future as! FBFuture<T>
}
/// Awaitable value that waits for publishing from the wrapped future
var value: T {
get async throws {
try await withTaskCancellationHandler {
try await withCheckedThrowingContinuation { continuation in
future.onQueue(futureSerialFullfillmentQueue, notifyOfCompletion: { resultFuture in
if let error = resultFuture.error {
continuation.resume(throwing: error)
} else if let value = resultFuture.result {
continuation.resume(returning: value as! T)
} else {
continuation.resume(throwing: FBFutureError.continuationFullfilledWithoutValues)
}
})
}
} onCancel: {
future.cancel()
}
}
}
}
extension FutureBox where T == NSNull {
/// Created to explicitly indicate that result type is Void and nothing should be returned.
/// And also to make a difference between omitting from call site
func await() async throws {
_ = try await value
}
}