withThrowingTaskGroup

|

withThrowingTaskGroup runs child tasks concurrently and cancels the remaining ones the moment any single child throws.

let results = try await withThrowingTaskGroup(of: Data.self) { group in
    for url in urls {
        group.addTask { try await URLSession.shared.data(from: url).0 }
    }

    var collected: [Data] = []
    for try await data in group {
        collected.append(data)
    }
    return collected
}

All addTask closures start immediately and run in parallel. The for try await loop collects results as they finish — not in submission order.

First throw cancels the group:

group.addTask { throw URLError(.badURL) } // this one fails
group.addTask { try await slowFetch() }   // gets cancelled

The error propagates out of the for try await loop, the group cancels remaining tasks, and withThrowingTaskGroup rethrows. You don’t get partial results.

Collecting partial results instead:

If you want to keep whatever succeeded, catch inside addTask:

group.addTask {
    try? await URLSession.shared.data(from: url).0 // nil on failure
}

Or use withTaskGroup (non-throwing) with a Result return type:

withTaskGroup(of: Result<Data, Error>.self) { group in
    group.addTask { await Result { try await fetch(url) } }
}

addTaskUnlessCancelled:

Skips adding the task if the group is already cancelled — useful when building the task list in a loop after some tasks have already failed:

for url in urls {
    guard group.addTaskUnlessCancelled(operation: { try await fetch(url) })
    else { break }
}
← Back to DevLog
rss facebook twitter github youtube mail spotify lastfm instagram linkedin google google-plus pinterest medium vimeo stackoverflow reddit quora