/*:#localized(key: "WhenAChildThrows")
 # When a child throws: `withThrowingTaskGroup`

 Real frames can be corrupt: a truncated file, a bad checksum. If loading a frame
 can `throw`, reach for `withThrowingTaskGroup`. It works exactly like the plain
 version, with two differences: your child closures may throw, and you iterate
 with `for try await`. If **any** child throws, the group **cancels the remaining
 children** and rethrows that error out of the group scope.
 */
//#-editable-code
import Foundation
import PlaygroundSupport

PlaygroundPage.current.needsIndefiniteExecution = true

struct CorruptFrame: Error { let index: Int }

func calibrate(_ index: Int) async throws -> Double {
    try await Task.sleep(for: .milliseconds(300))
    if index == 4 {
        throw CorruptFrame(index: index)   // this frame is unusable
    }
    return Double(index) * 1.5
}

Task {
    do {
        let total = try await withThrowingTaskGroup(of: Double.self) { group in
            for index in 1...8 {
                group.addTask { try await calibrate(index) }
            }

            var sum = 0.0
            for try await brightness in group {
                sum += brightness
            }
            return sum
        }
        print("Stack complete: \(total)")
    } catch let error as CorruptFrame {
        print("Stacking aborted: frame \(error.index) was corrupt")
    }
    PlaygroundPage.current.finishExecution()
}
//#-end-editable-code
/*:#localized(key: "WhenAChildThrows-2")
 **Watch it run:** frame 4 throws, and the `for try await` loop rethrows, which
 exits the group. On the way out, the group **cancels the siblings that are still
 running** and waits for them to wind down, then the error surfaces at your
 `catch`. You'll see `Stacking aborted: frame 4 was corrupt`, and the partial `sum`
 is discarded. One bad frame doesn't leave seven other tasks running in the
 background.

 * callout(Design note): Rethrowing is an *all-or-nothing* stance: one failure
 abandons the batch. If instead you'd rather **skip** bad frames and stack the good
 ones, don't let the child throw out of the group. Have it catch its own error and
 return something like `Double?` (or a small `Result`-style enum), then filter the
 `nil`s as you collect. Choose based on whether a single corrupt frame should doom
 the whole stack.

 [Previous](@previous)  |  page 6 of 9  |  [Next: The Structured Guarantee](@next)
 */
