def foo(): Future[Bob] = {
if (flag)
someAsyncCall()
else
throw new RuntimeException()
}In the above code, the exception was thrown outside a future, so it'll never reach a handle/rescue block that was defined somewhere up the call stack. The type here didn't prevent us from doing it because exceptions are an effect not seen by the type system.
If the effect was checked, we'd have something like:
def foo(): Future[Bob @throws[RuntimeException]] = ...In this case, the above code would have failed to compile with something like:
Expected : Future[Bob @throws[RuntimeException]]
But Found : Future[Bob] @throws[RuntimeException]
With regular values, you'd have Future[Try[A]] and Try[Future[A]] that are two different things. So you can't possibly do:
def foo(): Future[Try[Bob]] = {
if (flag)
someAsyncCall()
else
Failure(Future.exception(new RuntimeException))
}