/* Page prose (zh-Hans) */
"VersionAClassic" = "# 版本 A（经典）：GCD + completion handler + `DispatchGroup`\n\n每一次获取都接受一个 **completion handler**，一个闭包，当（模拟的）网络完成时它会回调这个闭包并带上一个 `Result`。因为这两次获取是互相独立的，我们把两者都推到后台队列上，并使用一个 **`DispatchGroup`** 来得知*两者*何时都已回调。然后我们跳到主线程去合并并报告。";
"VersionAClassic-2" = "**观察运行：** `Fetching menu and stock...` 先打印出来：两个 `fetch` 调用都立即返回了。两次获取并行运行（0.6 秒和 0.4 秒），所以大约 0.6 秒后组清空，你会在主线程上得到：\n\n* callout(输出):\n`Fetching menu and stock...`  \n`Sellable today: [\"Latte\", \"Espresso\"]`\n\nCold brew 被过滤掉了，因为它的库存是 `0`。\n\n* callout(试一试): 把 `fetchStock` 里 Cold brew 的库存从 `0` 改成 `2`。它现在会出现在可售清单里吗？\n\n现在数一数这让你付出了什么：\n\n* 两个可变的 `Result?` 变量活在闭包外面，用来把每个答案从它的 completion handler 里偷偷带出来。\n* 手动的 `enter()`/`leave()` 记账：一个闪失，组就会挂起或崩溃。\n* 结果是在汇合点上有两个**可选的** `Result`，所以你需要一个 `guard` *和*一个 `do`-`try`-`catch`，才能从“两个 `Result?`”走到“两个真正的值或一个错误”。\n* 跳到主线程是*你*记得要做的事（`notify(queue: .main)`）。忘了它，你就会在后台线程上合并并从那里更新 UI，一个编译器从不提及的 bug。\n\n这些都没有错。但每一块都是手动的，而真正的逻辑，“按库存过滤菜单”，被埋在了繁文缛节里。\n\n* callout(关于 data race 的旁注): `menuResult` 和 `stockResult` 从后台线程写入，并在 `notify` 中读取。这碰巧是安全的，因为组的 `leave()` → `notify` 顺序保证了写入在读取之前完成，但*没有任何东西替你检查这一点*。这正是 data race 在一般情况下会造成的那类隐患，也正是现代模型从构造上就消除掉的东西。\n\n[上一页](@previous)  |  第 3 / 8 页  |  [下一页: 版本 B: 现代方式](@next)";
