Skip to content

Instantly share code, notes, and snippets.

@christianselig
Created October 29, 2025 20:00
Show Gist options
  • Select an option

  • Save christianselig/2eeba611fcc46e560584f9ea966eedd2 to your computer and use it in GitHub Desktop.

Select an option

Save christianselig/2eeba611fcc46e560584f9ea966eedd2 to your computer and use it in GitHub Desktop.
import SwiftUI
struct ContentView: View {
let downloader = Downloader()
var body: some View {
VStack {
Text("Hello world")
Button("Download") {
Task {
do {
// Give up after 2 seconds
print(try await downloader.download(timeout: .seconds(2)))
} catch {
print("Button download error: \(error)")
}
}
}
}
.task {
do {
// Take as long as you need
print(try await downloader.download(timeout: .seconds(2)))
} catch {
print("Immediate download error: \(error)")
}
}
}
}
actor Downloader {
private var cached: Data?
private var existingTask: Task<Data, Error>?
func download(timeout: Duration? = nil) async throws -> Data {
if let cached { return cached }
if let existingTask {
return try await awaitExistingTask(existingTask, timeout: timeout)
}
let url = URL(string: "https://christianselig.com/dinosaur.mp4")!
let task = Task<Data, Error> {
let request = URLRequest(
url: url,
cachePolicy: .reloadIgnoringLocalAndRemoteCacheData
)
let (data, _) = try await URLSession.shared.data(for: request)
self.cached = data
self.existingTask = nil
return data
}
self.existingTask = task
return try await awaitExistingTask(task, timeout: timeout)
}
private func awaitExistingTask(
_ task: Task<Data, Error>,
timeout: Duration?
) async throws -> Data {
if let timeout {
return try await withTimeout(timeout) {
return try await task.value
}
} else {
return try await task.value
}
}
}
enum TimeoutError: Error { case timedOut }
func withTimeout<T>(
_ interval: Duration,
operation: @escaping () async throws -> T
) async throws -> T {
try await withThrowingTaskGroup(of: T.self) { group in
group.addTask {
try await Task.sleep(for: interval)
throw TimeoutError.timedOut
}
group.addTask {
return try await operation()
}
defer { group.cancelAll() }
return try await group.next()!
}
}
@donnywals
Copy link

nvm I think I misunderstood the question. Will try and cook up an alternative in a bit

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