Skip to content

Instantly share code, notes, and snippets.

@rjchatfield
Last active August 29, 2022 18:50
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save rjchatfield/d5ef79c7bc4bbdf4cb0e170ebd68e382 to your computer and use it in GitHub Desktop.
Save rjchatfield/d5ef79c7bc4bbdf4cb0e170ebd68e382 to your computer and use it in GitHub Desktop.
Promised values in Swift Concurrency
/// Wrapper around a lazily resolved using checked continuation.
///
/// Usage:
///
/// ```
/// let promiseA = Promised<String>()
/// async let a1 = promiseA.value
/// promiseA.resolve("AAA")
/// let a2 = await promiseA.value
/// print(await a1, a2)
/// ```
public actor Promised<WrappedValue: Sendable> {
private var _value: WrappedValue?
private var observers: [CheckedContinuation<WrappedValue, Never>] = []
public init() {}
// MARK: -
public var value: WrappedValue {
get async {
if let _value = _value {
return _value
} else {
return await withCheckedContinuation(add(continuation:))
}
}
}
public nonisolated func resolve(_ value: WrappedValue) {
Task {
await _resolve(value: value)
}
}
// MARK: -
private func _resolve(value: WrappedValue) {
guard _value == nil else {
return
}
_value = value
for continuation in observers {
continuation.resume(returning: value)
}
}
private func add(continuation: CheckedContinuation<WrappedValue, Never>) {
observers.append(continuation)
}
}
/// Wrapper around a lazily resolved using checked continuation.
///
/// Usage:
///
/// ```
/// let promiseA = PromisedOnMain<String>()
/// async let a1 = promiseA.value
/// promiseA.resolve("AAA")
/// let a2 = await promiseA.value
/// print(await a1, a2)
/// ```
public final class PromisedOnMain<WrappedValue: Sendable> {
@MainActor
private var _value: WrappedValue?
@MainActor
private var observers: [CheckedContinuation<WrappedValue, Never>] = []
public init() {}
// MARK: -
@MainActor
public var value: WrappedValue {
get async {
assert(Thread.isMainThread)
if let _value = _value {
return _value
} else {
return await withCheckedContinuation { continuation in
observers.append(continuation)
}
}
}
}
public func resolve(_ value: WrappedValue) {
Task { @MainActor in
_resolve(value: value)
}
}
// MARK: -
@MainActor
private func _resolve(value: WrappedValue) {
guard _value == nil else {
return
}
_value = value
for continuation in observers {
continuation.resume(returning: value)
}
}
}
@akbashev
Copy link

akbashev commented Aug 29, 2022

Ah, thx, was lazy to write from scratch and found your gist 😅
Updated for myself a bit though (with Result usage and throws):
https://gist.github.com/akbashev/eb9d4198b0182b156b076704bcb32d12

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment