/*:#localized(key: "RunningWorkConcurrentlyWithAsyncLet")
 # Running work concurrently with `async let`

 Here's where async/await starts to pay off. Suppose a latte needs two slow
 steps: steam the milk *and* pull the shot. Each takes about a second. Let's
 time the obvious version first:
 */
//#-editable-code
import Foundation
import PlaygroundSupport

PlaygroundPage.current.needsIndefiniteExecution = true

func steamMilk() async -> String {
    try? await Task.sleep(for: .seconds(1))
    return "🥛 Steamed milk"
}

func pullShot() async -> String {
    try? await Task.sleep(for: .seconds(1))
    return "☕️ Espresso shot"
}

Task {
    let start = Date()

    let milk = await steamMilk()   // wait ~1 s here...
    let shot = await pullShot()    // ...THEN start this, wait ~1 s more

    let elapsed = Date().timeIntervalSince(start)
    print("\(milk) + \(shot)")
    print(String(format: "Sequential total: %.2f s", elapsed))
    PlaygroundPage.current.finishExecution()
}
//#-end-editable-code
/*:#localized(key: "RunningWorkConcurrentlyWithAsyncLet-2")
 These two awaits run **sequentially**: each `await` fully finishes before
 the next line begins. The milk steams (1 s), and only *then* does the shot
 start (another 1 s). Total: about **2 seconds**. But steaming milk and
 pulling a shot don't depend on each other; a real barista would do them at
 the same time.

 `async let` lets you say exactly that. It **starts** the async work
 immediately and binds the eventual result to a name; you `await` that name
 only when you actually need the value:
 */
//#-editable-code
Task {
    let start = Date()

    async let milk = steamMilk()   // starts NOW, doesn't wait
    async let shot = pullShot()    // starts NOW too; both are running

    let latte = "\(await milk) + \(await shot)"   // wait for both here

    let elapsed = Date().timeIntervalSince(start)
    print(latte)
    print(String(format: "Concurrent total: %.2f s", elapsed))
    print("==========")
}
//#-end-editable-code
/*:#localized(key: "RunningWorkConcurrentlyWithAsyncLet-3")
 The two `async let` lines kick both pieces of work off back-to-back, without
 waiting. By the time you reach the `await` on the next line, they've been
 steaming and pulling *in parallel* the whole time.

 **Watch it run:** this page prints `Concurrent total: 1.0x s`. Compare that
 with the sequential version above, which prints `Sequential total: 2.0x s`,
 the two one-second waits stacked up instead of overlapping.

 That's the headline: **two 1-second tasks finish in ~1 s, not ~2 s.** You
 didn't add threads or queues by hand; you just told Swift which work was
 independent, and it overlapped the waiting for you. The elapsed numbers
 won't be exactly `1.00` or `2.00` (there's a little overhead), but the
 near-doubling is unmistakable.

 > **Why `async let` and not two `await`s?** A plain `await`
 > *waits* right where it sits, so the second call can't begin until the first
 > returns. `async let` *starts* the work and defers the waiting, which is what
 > lets two independent tasks run at once. Reach for it whenever you have work
 > that doesn't depend on each other's results.

 This is your first taste of **structured concurrency**: starting several
 pieces of work and gathering their results within a clear scope. `async let`
 handles two or three fixed pieces nicely. When you have a whole *collection*
 of work (say, ten drinks at once), you'll want a **`TaskGroup`**, which is
 the focus of the chapter [Task Groups](Task%20Groups.playgroundchapter/Overview). And the details of `Task` itself come first, in the chapter [Tasks and Cancellation](Tasks%20and%20Cancellation.playgroundchapter/Overview).

 * callout(Try it): Change `pullShot()`'s delay to `.seconds(2)` and leave
 `steamMilk()` at `.seconds(1)`, what's the concurrent total now, and which
 step decides it?

 [Previous](@previous)  |  page 7 of 8  |  [Next: Wrapping Up](@next)
 */
