The thing is that find
returns not a User
, but a Future[User]
.
Future[User]
can be either DefaultPromise[User]
or never
.
Future
's map(..)
and recover(..)
redirect to Try
's map(..)
and recover(..)
via Future
's transform(..)
. And the transform(..)
is defined differently for both cases.
trait Future[+T] extends Awaitable[T] {
def map[S](f: T => S)(implicit executor: ExecutionContext): Future[S] = transform(_ map f)
def recover[U >: T](pf: PartialFunction[Throwable, U])(implicit executor: ExecutionContext): Future[U] =
transform { _ recover pf }
def transform[S](f: Try[T] => Try[S])(implicit executor: ExecutionContext): Future[S]
//...
}
private[concurrent] trait Promise[T] extends scala.concurrent.Promise[T] with scala.concurrent.Future[T] {
override def transform[S](f: Try[T] => Try[S])(implicit executor: ExecutionContext): Future[S] = {
val p = new DefaultPromise[S]()
onComplete { result => p.complete(try f(result) catch { case NonFatal(t) => Failure(t) }) }
p.future
}
//...
}
class DefaultPromise[T] extends AtomicReference[AnyRef](Nil) with Promise[T] { /*...*/ }
final object never extends Future[Nothing] {
override def transform[S](f: Try[Nothing] => Try[S])(implicit executor: ExecutionContext): Future[S] = this
//...
}
https://github.com/scala/scala/blob/2.13.x/src/library/scala/concurrent/Future.scala
Try[User]
can be either Failure[User]
or Success[User]
. And map(..)
and recover(..)
are defined there differenty for both cases.
sealed abstract class Try[+T] extends Product with Serializable {
def map[U](f: T => U): Try[U]
def recover[U >: T](@deprecatedName('f) pf: PartialFunction[Throwable, U]): Try[U]
//...
}
final case class Failure[+T](exception: Throwable) extends Try[T] {
override def map[U](f: T => U): Try[U] = this.asInstanceOf[Try[U]]
override def recover[U >: T](@deprecatedName('rescueException) pf: PartialFunction[Throwable, U]): Try[U] =
try { if (pf isDefinedAt exception) Success(pf(exception)) else this } catch { case NonFatal(e) => Failure(e) }
//...
}
final case class Success[+T](value: T) extends Try[T] {
override def map[U](f: T => U): Try[U] = Try[U](f(value))
override def recover[U >: T](@deprecatedName('rescueException) pf: PartialFunction[Throwable, U]): Try[U] = this
//...
}
https://github.com/scala/scala/blob/2.13.x/src/library/scala/util/Try.scala
So basically all cases are dealt with:
- when 1st call is success (then 2nd call is trivial, you can see above that implementation of
recover(..)
for Success
is trivially just this
)
- when 1st call is failure but 2nd is success
- when both calls are failures
Maybe you should read more about Try
, Future
and other monads, monadic chaining of calculations etc.
Error Handling With Try
Welcome to the Future
Promises and Futures in Practice
Futures and Promises