Created
October 29, 2025 20:00
-
-
Save christianselig/2eeba611fcc46e560584f9ea966eedd2 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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()! | |
| } | |
| } |
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
it's a bit quick and dirty but this should do what you want