mastodon-ios/MastodonSDK/Sources/MastodonExtension/Publisher.swift

91 lines
2.8 KiB
Swift

import Combine
// Ref: https://www.swiftbysundell.com/articles/connecting-async-await-with-other-swift-code/
extension Publishers {
public struct MissingOutputError: Error {}
}
extension Publisher {
public func singleOutput() async throws -> Output {
var cancellable: AnyCancellable?
var didReceiveValue = false
return try await withCheckedThrowingContinuation { continuation in
cancellable = sink(
receiveCompletion: { completion in
switch completion {
case .failure(let error):
continuation.resume(throwing: error)
case .finished:
if !didReceiveValue {
continuation.resume(
throwing: Publishers.MissingOutputError()
)
}
}
},
receiveValue: { value in
guard !didReceiveValue else { return }
didReceiveValue = true
cancellable?.cancel()
continuation.resume(returning: value)
}
)
}
}
}
// ref: https://www.swiftbysundell.com/articles/calling-async-functions-within-a-combine-pipeline/
extension Publisher {
public func asyncMap<T>(
_ transform: @escaping (Output) async -> T
) -> Publishers.FlatMap<Future<T, Never>, Self> {
flatMap { value in
Future { promise in
Task {
let output = await transform(value)
promise(.success(output))
}
}
}
}
public func asyncMap<T>(
_ transform: @escaping (Output) async throws -> T
) -> Publishers.FlatMap<Future<T, Error>, Self> {
flatMap { value in
Future { promise in
Task {
do {
let output = try await transform(value)
promise(.success(output))
} catch {
promise(.failure(error))
}
}
}
}
}
public func asyncMap<T>(
_ transform: @escaping (Output) async throws -> T
) -> Publishers.FlatMap<Future<T, Error>,
Publishers.SetFailureType<Self, Error>> {
flatMap { value in
Future { promise in
Task {
do {
let output = try await transform(value)
promise(.success(output))
} catch {
promise(.failure(error))
}
}
}
}
}
}