如果你刚从 C++ 或同步式代码切到 Scala,最容易卡住的地方通常不是语法,而是“任务什么时候开始、结果什么时候可用、失败怎么处理、超时怎么兜底”。本文按这个思路,把 Scala 异步编程里最常见的 `Future`、`Promise`、结果组合和阻塞等待串起来讲一遍。
文中的命令、定义、代码与输出示例均保留原始事实,你可以一边对照示例,一边判断哪些场景适合回调、哪些更适合组合,什么时候又该显式阻塞等待结果。
Future 是什么,最基本怎么用
本文更适合有 C++ 基础、并且已经熟悉 Scala 基本概念的读者。先从 `Future` 本身看起,它的基本概念和 C++ 的 std::Future 类似,定义如下:

trait Future[T]
Future 还提供了 apply 方法,声明方式如下:
def apply[T](b: => T)(implicit e: ExecutionContext): Future[T]
其中 => T 的定义可以参考这篇文章。上面的写法还涉及柯理化与隐式参数,想补这部分背景可以继续看这篇文章。
先看一个最基础的例子:
package TestFuturePromise
import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.Future
object TestFuture {
def main(args: Array[String]): Unit = {
val future = Future { // Future[Unit] 类型的
Thread.sleep(1000)
val tid = Thread.currentThread().getName
println(s"future finished in $tid")
}
while (!future.isCompleted) {
println("main thread wait for future")
Thread.sleep(200)
}
}
// 模拟下载
def downLoadUrl(url: String): String = {
val t = 3
Thread.sleep(1000 * t)
val tid = Thread.currentThread().getName
println(s"finish download after $t s from thread $tid")
"hello world"
}
}
输出结果:
main thread wait for future
main thread wait for future
main thread wait for future
main thread wait for future
main thread wait for future
future finished in scala-execution-context-global-13
这个例子最直观地说明了一点:主线程并没有立即拿到结果,而是在不断检查 future.isCompleted,直到异步任务执行结束。
Future 完成后,怎么拿结果
和 C++ 常见写法不同,Scala 支持直接在 Future 上附加回调,最常见的是使用 foreach。先看接口定义:
def foreach[U](f: scala.Function1[T, U])(implicit executor: scala.concurrent.ExecutionContext): scala.Unit
回调函数接收的是 `Future` 成功完成后的结果,同时还需要一个 `ExecutionContext`。下面直接在前面的示例上做修改:
object TestFuture {
def main(args: Array[String]): Unit = {
val future = Future { // Future[String] 类型的
Thread.sleep(1000)
val tid = Thread.currentThread().getName
println(s"future finished in $tid")
"hello future" // 这里新增返回值
}
// 增加回调函数
future.foreach(s => {
val tid = Thread.currentThread().getName
println(s"callback from $tid get content $s")
})
while (!future.isCompleted) {
println("main thread wait for future")
Thread.sleep(200)
}
}
}
foreach 只会在 `Future` 成功时触发,这一点很关键。原文里也提到,它和 onSuccess 的语义接近;如果是失败情况,可以使用 onFailed。当你明确知道任务可能抛异常时,更常见的做法是直接使用 onComplete,再通过 match 处理 Try[T]。关于 `Try`,可以参考这篇文章。
Future 怎么组合:从 map 开始
仅仅“启动一个异步任务并等它结束”还不够,真实业务里更常见的是把异步结果继续变换。这里最基础的工具就是 map。
原文对它的描述是:map 用于转化 Future,可以理解为链接多个 Future。示例如下:
import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.Future
import scala.util.{Failure, Success}
object TestFuture {
def main(args: Array[String]): Unit = {
val result = Future[Int] {
1
}.map {
num => {
println("int to string map")
num + "234"
}
}
result.onComplete {
case Success(num) => println(num)
case Failure(e) => println(e.getMessage)
}
Thread.sleep(10000)
}
}
最终输出:
int to string map
1234
这里有个很实用的点:在 map 里不需要显式返回 Future,只要返回目标值即可,Scala 会自动包装成对应的 Future。也就是说,`Future[Int]` 经过这次 `map` 之后,变成了 `Future[String]`。
除了 map,原文还提到 filter、flatMap 等其他组合方式,完整说明可以直接参考 Scala 官方文档。
Promise 的作用:把结果生产者和消费者拆开
如果说 `Future` 更像“已经安排出去的一次异步计算”,那 `Promise` 的重点就在于:谁来消费结果,和谁来填充结果,可以分开。

原文里把这个差异解释得很清楚。使用 `Future` 时,我们往往在构造时就把计算逻辑写进去;而使用 `Promise` 时,提供方先把一个 `Future` 暴露给调用者,自己则在合适的时机调用 `success` 或 `failure` 去完成它。这种做法在需要手动桥接回调、事件或底层异步 API 时非常常见。
示例代码如下:
package TestFuturePromise
import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.{Future, Promise}
import scala.util.{Failure, Random, Success}
object TestFuture {
def main(args: Array[String]): Unit = {
val fut = downLoad("www.foo.com", tag = true)
fut.onComplete {
case Success(txt) => println(txt)
case Failure(e) => println(e.getMessage)
}
val fut1 = downLoad("www.foo1.com", tag = false)
fut1.onComplete {
case Success(txt) => println(txt)
case Failure(e) => println(e.getMessage)
}
println("finish add callback")
Thread.sleep(5 * 1000)
println("main thread finished")
}
def downLoad(url: String, tag: Boolean): Future[String] = {
val promise = Promise[String]
// 模拟异步下载
global.execute(() => {
val t = Random.between(1, 4)
Thread.sleep(t * 1000)
val tid = Thread.currentThread().getName
if (tag) {
println(s"$tid download from [$url] takes $t s")
promise.success("hello world")
} else {
println(s"$tid download from [$url] [FAILED] takes $t s")
promise.failure(new Exception("download failed"))
}
})
promise.future
}
}
输出结果:
finish add callback
scala-execution-context-global-14 download from [www.foo1.com] [FAILED] takes 3 s
download failed
scala-execution-context-global-13 download from [www.foo.com] takes 4 s
hello world
main thread finished
这个例子里,调用方只拿到 `Future[String]`,并用 `onComplete` 注册成功或失败回调;真正决定何时返回结果、返回成功还是失败的,是 `downLoad` 方法内部的 `Promise[String]`。这就是 `Promise` 最核心的价值:把“结果出口”和“结果入口”拆开。
没有内建 timeout 时,怎么做超时和抢先返回
原文特别指出,`Future` 本身没有 `timeout` 方法。如果业务要求“超过某个时间就立刻返回”,可以借助 Java 的 `Timer` 来包装一个超时 `Future`。

先实现一个最简单的 timeout
package TestFuturePromise
import java.util.{Timer, TimerTask}
import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.{Future, Promise}
object TestFuture {
def main(args: Array[String]): Unit = {
timeout(1000).foreach(_ => println("timer Future finished"))
Thread.sleep(2000)
}
def timeout(t: Int): Future[Unit] = {
val timer = new Timer(true)
val p = Promise[Unit]
timer.schedule(new TimerTask {
override def run(): Unit = {
p.success(()) // 超时了,此时立刻设置返回值
timer.cancel()
}
}, t)
p.future
}
}
这个版本的作用很简单:等到 `t` 毫秒后,把 `Promise[Unit]` 标记成功,从而让返回的 `Future[Unit]` 结束。单独看它意义有限,但它为后面的“谁先完成就用谁”打下了基础。
再把 timeout 和业务 Future 组合起来
更实用的场景是:给任意 `Future[T]` 增加超时控制,如果超时先到,就返回超时结果;如果业务任务先结束,就直接返回业务结果。原文通过自定义 `or` 方法实现了这个思路:
package TestFuturePromise
import java.util.{Timer, TimerTask}
import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.{Future, Promise}
import scala.util.{Failure, Success}
object TestFuture {
def main(args: Array[String]): Unit = {
// 利用 map,转换成 Future[String]类型,并返回超时的处理结果
val f = timeout(1000).map(_ => "timeout") or Future {
Thread.sleep(8)
println("or Future callback")
"hello timeout"
}
f.onComplete {
case Success(txt) => println(txt)
case Failure(e) => println(e.getMessage)
}
Thread.sleep(2000)
}
def timeout(t: Int): Future[Unit] = {
val timer = new Timer(true)
val p = Promise[Unit]
timer.schedule(new TimerTask {
override def run(): Unit = {
p.success(())
timer.cancel()
}
}, t)
p.future
}
implicit class FutureOp[T](val self: Future[T]) {
def or(that: Future[T]): Future[T] = {
val p = Promise[T]
// tryComplete 只会成功调用一次
self.onComplete(x => p.tryComplete(x))
that.onComplete(y => p.tryComplete(y))
p.future
}
}
}
这里最值得注意的是 `or` 的实现。它内部再创建一个 `Promise[T]`,然后同时为 `self` 和 `that` 注册 `onComplete` 回调,两个回调都尝试调用 `p.tryComplete(...)`。由于 `tryComplete` 只会真正成功一次,因此谁先完成,最终返回的就是谁的结果。
原文这里还用到了 `implicit class` 扩展方法,相关语法可以参考这篇文章。
什么时候该阻塞:Await 的基本用法
虽然 `Future` 和 `Promise` 的核心目的是非阻塞,但在某些边界场景里,显式阻塞仍然有用。比如脚本式入口、测试代码,或者必须在当前线程拿到最终结果的地方,可以使用 `Await`。
示例如下:
package TestFuturePromise
import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.duration.DurationInt
import scala.concurrent.{Await, Future}
object TestFuture {
def main(args: Array[String]): Unit = {
val fut = Future {
Thread.sleep(1000)
println("future weak up")
"hello Await"
}
Await.result(fut, 1200.seconds)
println(fut.value)
}
}
这段代码的重点很直接:Await.result(fut, 1200.seconds) 会阻塞当前线程,直到 `fut` 完成或超时。对于理解异步模型来说,它能帮助你区分两类需求:一类是“任务完成后通知我”,另一类是“我必须现在就拿到结果”。前者更适合回调和组合,后者才考虑阻塞。
怎么选:把几种常见写法放到同一个判断框架里
把全文串起来看,可以得到一套很实用的使用顺序:
- 只想启动异步任务并在完成后处理结果,用
Future加foreach/onComplete。 - 需要把异步结果继续变换、串联后续处理,用
map、flatMap等组合方式。 - 结果的生产时机不在当前计算块里,或者需要手动控制成功与失败,用
Promise。 - 需要“超时就返回”或“两个 Future 谁先完成用谁”,可以用
Promise再包一层竞争逻辑。 - 只有在确实必须同步取值时,才用
Await做阻塞等待。
如果你已经有 C++ 的 `std::Future` 背景,最需要补的其实不是“怎么开异步任务”,而是 Scala 这套围绕 `Future` 提供的回调、组合和手动完成能力。理解了这几块,再看更复杂的异步控制就会顺畅很多。













