Add async-await support to Promises

This commit is contained in:
Max Radermacher 2023-09-25 12:49:33 -05:00 committed by GitHub
parent da1f1fd582
commit ea99e9002f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 118 additions and 0 deletions

View File

@ -158,6 +158,10 @@ public class AnyPromise: NSObject {
anyPromise.asVoid()
}
public func asAny() -> Promise<Any> {
return anyPromise
}
@objc
@available(swift, obsoleted: 1.0)
public class func when(fulfilled promises: [AnyPromise]) -> AnyPromise {

View File

@ -64,6 +64,34 @@ public extension Guarantee {
func asVoid() -> Guarantee<Void> { map { _ in } }
}
extension Guarantee {
/// Wraps a Swift Concurrency async function in a Guarantee.
///
/// The Task is created with the default arguments. To configure the task's
/// priority, the caller should create its own Guarantee instance.
public static func wrapAsync(_ block: @escaping () async -> Value) -> Self {
let guarantee = Self()
Task {
guarantee.future.resolve(await block())
}
return guarantee
}
/// Converts a Guarantee to a Swift Concurrency async function.
public func awaitable() async -> Value {
await withCheckedContinuation { continuation in
observe(on: SyncScheduler()) { result in
switch result {
case .success(let value):
continuation.resume(returning: value)
case .failure(let error):
owsFail("Unexpectedly received error result from unfailable promise \(error)")
}
}
}
}
}
public extension Guarantee {
func map<T>(
on scheduler: Scheduler? = nil,

View File

@ -82,6 +82,33 @@ public extension Promise {
}
}
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)
}
}
}
}
public extension Promise {
class func pending() -> (Promise<Value>, Future<Value>) {
let promise = Promise<Value>()

View File

@ -0,0 +1,47 @@
//
// Copyright 2023 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
//
import Foundation
/// Scheduler that always runs any work synchronously on the current thread.
/// Useful if you don't care what thread your work runs on and want to incur
/// the least overhead. Should NOT be used for scheduled methods, e.g.:
/// `promise.after(seconds: 10, on: SyncScheduler()` would be a bad form and
/// fall back to scheduling in the future on the main thread.
public class SyncScheduler: Scheduler {
public init() {}
public func async(_ work: @escaping () -> Void) {
work()
}
public func sync(_ work: () -> Void) {
work()
}
public func sync<T>(_ work: () throws -> T) rethrows -> T {
return try work()
}
public func sync<T>(_ work: () -> T) -> T {
return work()
}
public func asyncAfter(deadline: DispatchTime, _ work: @escaping () -> Void) {
owsFailDebug("Should not schedule on async queue. Using main queue instead.")
DispatchQueue.main.asyncAfter(deadline: deadline, work)
}
public func asyncAfter(wallDeadline: DispatchWallTime, _ work: @escaping () -> Void) {
owsFailDebug("Should not schedule on async queue. Using main queue instead.")
DispatchQueue.main.asyncAfter(wallDeadline: wallDeadline, work)
}
public func asyncIfNecessary(execute work: @escaping () -> Void) {
work()
}
}

View File

@ -302,4 +302,16 @@ class PromiseTests: XCTestCase {
XCTAssert(doneCalled)
XCTAssertEqual(try future.result?.get(), 10)
}
func test_asyncAwait() async throws {
let v1 = try await Promise.wrapAsync { await self.arbitraryAsyncAction() }.awaitable()
XCTAssertEqual(v1, 42)
let v2 = await Guarantee.wrapAsync { await self.arbitraryAsyncAction() }.awaitable()
XCTAssertEqual(v2, 42)
}
private func arbitraryAsyncAction() async -> Int {
await Task.yield()
return 42
}
}