Add async-await support to Promises
This commit is contained in:
parent
da1f1fd582
commit
ea99e9002f
@ -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 {
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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>()
|
||||
|
||||
47
SignalCoreKit/src/Promises/SyncScheduler.swift
Normal file
47
SignalCoreKit/src/Promises/SyncScheduler.swift
Normal 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()
|
||||
}
|
||||
}
|
||||
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user