Created
March 14, 2025 17:58
-
-
Save robertmryan/654fff6d603bd4c358f3781d2f31b334 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
class ImageManager { | |
func downloadInOrderIndependentDictionary() async throws -> [String: Image] { | |
try await withThrowingTaskGroup(of: (String, Image?).self) { group in | |
let items = await fetchList() | |
for item in items { | |
group.addTask { | |
do { | |
return try await (item.imageName, self.fetch(imageName: item.imageName)) | |
} catch is CancellationError { | |
throw CancellationError() | |
} catch { | |
return (item.imageName, nil) | |
} | |
} | |
} | |
return try await group.reduce(into: [:]) { $0[$1.0] = $1.1 } | |
} | |
} | |
func fetchList() async -> [Item] {…} | |
func fetch(imageName: String) async throws -> Image { | |
try await Task.sleep(for: .seconds(1)) | |
… | |
} | |
} | |
struct MyAppTests { | |
@Test func testDownloadTimeout() async throws { | |
let imageManager = ImageManager() | |
do { | |
try await withThrowingTaskGroup(of: Void.self) { group in | |
group.addTask { | |
_ = try await imageManager.downloadInOrderIndependentDictionary() | |
#expect(Bool(false), "The download should have been canceled before finishing") | |
} | |
group.addTask { | |
try await Task.sleep(for: .seconds(0.5)) | |
} | |
try await group.next() | |
group.cancelAll() | |
try await group.waitForAll() | |
} | |
#expect(Bool(false), "The above should have thrown an error and we should not get here") | |
} catch { | |
#expect(error is CancellationError) | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
FWIW, the details of the above implementation are of less import than the general idea of just making sure your asynchronous routines throw
CancellationError
if canceled. Fortunately, most Apple cancelableasync
API do this already, so you just need to make sure you let those percolate up the call chain. Once you embrace the existing cancelable API, writing tests to detect cancelation are quite simple.