6.1 Future与Promise


6.1 Future 与 Promise

Future[T] 表示"现在还没有、稍后会到的 T";Promise[T] 是写进这个值的那一端。Future 的组合子与 Option、List 同构——第三章的 Monad 直觉在这里全额兑现。

Future 是时间上的容器

import scala.concurrent.*, ExecutionContext.Implicits.global val f: Future[Int] = Future { expensiveCompute() // 已提交到线程池,立即返回"凭据" } f.map(_ * 2) // Future[Int] f.flatMap(n => fetchUser(n)) // Future[User],压扁两层 for n <- compute() u <- fetchUser(n) ord <- placeOrder(u) yield ord // Future[Order]

每个 Future 都需要一个 ExecutionContext(5.3 的 given 在此值班)——它决定代码在哪个线程池上跑。生产环境应显式提供专用池而非全局默认,避免把 IO 阻塞任务和 CPU 计算混进同一个池。

失败分支是类型的一部分:

fetchPrice("BTC") .recover { case ex: TimeoutException => BigDecimal(-1) } .fallbackTo(defaultPrice)

组合并行:别用串行 for

for 推导是顺序依赖的表达。两个互不依赖的请求,先后 flatMap 会白白排队:

// 串行:先等用户再查库存,总耗时相加 for u <- findUser(id); s <- findStock(id) yield (u, s) // 并行:同时出发,总耗时约等于最慢的那个 val combined = findUser(id).zip(findStock(id)) // Future[(User, Stock)] val all = Future.sequence(List(fa, fb, fc)) // Future[List[A]]

串行与并行的耗时对比

Promise:从回调世界回来

老式 API 只给你回调,没有 Future。Promise 是桥:

def fetchAsync(url: String): Future[String] = val p = Promise[String]() legacyClient.get(url, onSuccess = resp => p.success(resp.body), // 完成承诺 onFailure = ex => p.failure(ex) ) p.future // 读端照常组合

Promise 只能完成一次,第二次 complete 抛异常——语义上它就是单赋值的 val,只是赋值发生在未来。

阻塞等待仅限边界处(如测试或 main 收尾):

import scala.concurrent.duration.* Await.result(combined, 3.seconds)

在异步管线中途 Await 是典型事故来源:线程池被占满后死锁。规则是边界阻塞、内部组合

完整案例:并发抓取三路数据再聚合

背景:页面渲染需要用户信息、订单列表与积分三路数据,串行要 900 毫秒,并发可以压到最慢一路的耗时。操作过程:

import scala.concurrent.*, ExecutionContext.Implicits.global import scala.concurrent.duration.* def fetchUser(id: Long): Future[User] = Future { Thread.sleep(300); User(id) } def fetchOrders(u: User): Future[List[Order]] = Future { Thread.sleep(400); Nil } def fetchPoints(u: User): Future[Int] = Future { Thread.sleep(200); 65 } val page = for u <- fetchUser(1L) // 300ms ord <- fetchOrders(u) // 与积分并发:400ms pts <- fetchPoints(u) yield (u, ord, pts) page.onComplete { case Success((u, ord, pts)) => render(u, ord, pts) case Failure(e) => println(s"页面失败: ${e.getMessage}") } Await.result(page, 2.seconds) // 总耗时约 700ms 而非 900ms

结果:总耗时由三路之和变成三路之最大值。解读:for 推导在 Future 上与在 Option 上是同一套 flatMap 语义——"上一步成功才做下一步,任一步失败整体失败",第三章练的手感在这里全额兑现。变式:三路彼此不依赖时可先各自启动再 zip,还能再省 300 毫秒;这是 for 推导(串行依赖)与 zip(并行独立)的分界线,写并发前先画依赖图。

Future 的两条铁律

  • Future 创建即执行,不是"冷"的;想延迟执行要把体包在函数里(LazyFuture 模式或 Cats 的 IO)。
  • onComplete 之外的回调里改共享可变状态,等于把竞态请进门;需要汇聚结果就用 map/zip/for。

Promise:把回调式 API 接进 Future 世界

Future 描述"将有的值",Promise 是"承诺将来交付"的写端。典型场景是把旧式回调 SDK 包一层:

val p = Promise[String]() legacySdk.fetch("k", (err, data) => // 回调风格的老 SDK if err == null then p.success(data) else p.failure(err)) p.future // 拿到 Future,接入 for 推导

一个 Promise 只能完成一次,第二次 success 抛异常——这本身就是并发语义的保护。包好回调后,老 SDK 与新代码之间就只剩下 Future 一种货币。

本节要点回顾

  • Future 是 Monad,map/flatMap/for 与第三章同一套直觉。
  • ExecutionContext 是 given,生产环境显式选池。
  • 互不依赖用 zip / Future.sequence 并行,for 推导表达的是顺序。
  • Promise 桥接回调式 API,一次性完成;Await 只许出现在边界。

作者与出处
原作者: 灏天文库
来源:灏天文库
整理: 灏天文库整理
由灏天文库平台收录,内容或由平台用户上传,仅供学习交流
发布者: 作者: 灏天文库 转发
评论区 (0)
U