val syncAndBlocking: HttpResponse = someHttpClient.get("foo.com")
This will block the calling thread until the response is received (as you note).
val asyncButBlocking: Future[HttpResponse] = Future { someHttpClient.get("bar.com") }
As with any call to Future.apply
, this (at least conceptually, there may be optimizations which eliminate some steps):
- Creates a
Function0[HttpResponse]
(I'll call it a thunk, for brevity) whose apply
method is someHttpClient.get("bar.com")
. If someHttpClient
is static, this could theoretically happen at compile time (I don't know off the top of my head if the Scala compiler will perform this optimization), otherwise it will happen at runtime.
- Passes that thunk to
Future.apply
, which then:
- Creates a
Promise[HttpResponse]
.
- Schedules a task on the
ExecutionContext
passed implicitly to Future.apply
. This task is to call the thunk: if the thunk successfully executes, the Promise
's associated Future
is completed (successfully) with the result of the thunk, otherwise that Future
fails (also a completion) with the thrown Throwable
(it may only fail if the Throwable
is matched by NonFatal
, I'm too lazy to check, in which case fatal throws are caught by the ExecutionContext
).
- As soon as the task is scheduled on the
ExecutionContext
(which may or may not be before the thunk is executed), the Future
associated with the Promise
is returned.
The particulars of how the thunk is executed depend on the particular ExecutionContext
and by extension on the particulars of the Scala runtime (for Scala on the JVM, the thunk will be run on a thread determined by the ExecutionContext
, whether this is an OS thread or a green thread depends on the JVM, but OS thread is probably a safe assumption at least for now (Project Loom may affect that); for ScalaJS (since JavaScript doesn't expose threads) and Scala Native (as far as I know for now: conceivably an ExecutionContext
could use OS threads, but there would be risks in the runtime around things like GC), this is probably an event loop with a global thread).
The calling thread is blocked until step 5 has executed, so from the caller's perspective, it's non-blocking, but there's a thread somewhere which is blocked.
val asynAndNotBlocking: Future[HttpResponse] = Future { someNonBlockingHttpClient.get("baz.com") }
...is probably not going to typecheck (assuming that it's the same HttpResponse
type as above) since in order to be non-blocking the HttpResponse
would have to be wrapped in a type which denotes asynchronicity/non-blocking (e.g. Future
), so asyncAndNotBlocking
is of type Future[Future[HttpResponse]]
, which is kind of a pointless type outside of a few specific usecases. You'd be more likely to have something like:
val asyncAndNotBlocking: Future[HttpResponse] = someNonBlockingHttpClient.get("baz.com")
or, if someNonBlockingHttpClient
isn't native to Scala and returns some other asynchrony library, you'd have
val asyncAndNotBlocking: Future[HttpResponse] = SomeConversionToFuture(someNonBlockingHttpClient.get("baz.com"))
SomeConversionToFuture
would basically be like the sketch above of Future.apply
, but could, instead of using an ExecutionContext
use operations in that other asynchrony library to run code to complete the associated Future
when .get
completes.
If you really wanted Future[Future[HttpResponse]]
for some reason, given that it's likely that someNonBlockingHttpClient
will return very quickly from .get
(remember, it's asynchronous, so it can return as early as the request being set up and scheduled for being sent), Future.apply
is probably not the way to go about things: the overhead of steps 1-5 may take longer than the time spent in .get
! For this sort of situation, Future.successful
is useful:
val doubleWrapped: Future[Future[HttpResponse]] = Future.successful( someNonBlockingHttpClient.get("baz.com"))
Future.successful
doesn't involve a thunk, create a Promise
, or schedule a task on the ExecutionContext
(it doesn't even use an ExecutionContext
!). It directly constructs an already-successfully-completed Future
, but the value contained in that Future
is computed (i.e. what would be in the thunk is executed) before Future.successful
is called and it blocks the calling thread. This isn't a problem if the code in question is blocking for just long enough to setup something to execute asynchronously, but it can make something that's blocking for a long time look to the outside world like it's async/non-blocking.
Knowing when to use Future.apply
and Future.successful
is of some importance, especially if you care about performance and scalability. It's probably more common to see Future.successful
used inappropriately than Future.apply
(because it doesn't require an implicit ExecutionContext
, I've seen novices gravitate to it). As Colin Breck put it, don't block your future success by improperly using Future.successful
.
someNoneBlocking...
probably already returns a future. Here is a good overview of blocking/async/sync comaprison: github.com/slouc/concurrency-in-scala-with-ce Don't mind the cats-effect refrerences – Saskiaval asynAndNotBlocking: Future[HttpResponse] = someNonBlockingHttpClient.get("baz.com")
– Bergi