意外的Future.map()执行顺序

时间:2016-02-28 20:28:27

标签: scala

我有以下Scala计划:

object FutureMapTest extends App {
   println("start")

   val f: Future[Long] = Future {
     Thread.sleep(2000)
     val x = 1
     println(s"started with ${x}")
     x
   }
   f.map { i =>
     println(s"mapped to ${i*2}")
   }
   f.map {
     val nothing = "nothing"
     println(s"mapped to ${nothing}")
     _ * 2
   }

   Thread.sleep(3000)
   println("end")
}

我希望它能在控制台上打印

start
started with 1

然后(以任何顺序):

mapped to 2
mapped to nothing

接着是

end

它实际印刷的是:

start
mapped to nothing
started with 1
mapped to 2
end

所以,它似乎是第二个"地图"块立即执行,无需等待原始未来完成。怎么可能?

你甚至可以从原来的未来块中删除Thread.sleep(),结果仍然是相同的。

1 个答案:

答案 0 :(得分:4)

这里有几个混乱的来源。

此:

f.map {
  val nothing = "nothing"
  println(s"mapped to ${nothing}")
  _ * 2
}

扩展为:

f.map {
  val nothing = "nothing"
  println(s"mapped to ${nothing}")
  i => i * 2
}

这是什么意思?对于某些Future#mapA => B需要Future[A]的函数参数。表达式:

val nothing = "nothing"
println(s"mapped to ${nothing}")
i => i * 2

..评估到Long => Long,但是val赋值和println首先计算 ,因为它们是返回函数的表达式的一部分。在i => i * 2完成之前,f不会被执行。这类似于(Scala puzzler 001):

scala> List(1, 2, 3) map {
     |    val a = 1 // this only happens once, not three times
     |    i => a + i + 1
     | }
res0: List[Int] = List(3, 4, 5)

将其更改为此将显示您期望的行为(现在val赋值和println 函数体的一部分):

f.map { i =>
  val nothing = "nothing"
  println(s"mapped to ${nothing}")
  i * 2
}

以下是另一种观察方式:

f.map {
  println("evaluated immediately")
  i => { println("evaluated after f"); i * 2 }
}