Published 2014-09-22.
Last modified 2019-07-01.
Time to read: 12 minutes.
This lecture consists of a discussion and a code example for every important method provided by the Scala Future class and companion object. This lecture is the culmination of a long sequence of information that started with the Introduction to Scala course and cross-references to other lectures are provided to help you understand the code examples.
You may remember from the Combinators lecture earlier in this course that monads hold values,
and Future is just a regular monad like all others.
Performing an operation on a monad yields another monad of the same type.
That same lecture taught you that combinators are methods that perform transformations on the contents of monads. Futures are composable, which means:
- Operations on a
Futureor a collection ofFutures can be chained together without blocking. - Transformations such as
mapandflatMapcan be applied to collections ofFutures. - Collections of
Futures can be manipulated (for example, reduced).
Futures are immutable, so the result of transforming a Future with a combinator is a new Future.
Chaining multiple combinators together results in intermediate Futures being created.
Some combinators accept partial functions; as you know from the Partial Functions lecture,
partial functions which each handle a subset of possible input values or types can be chained together,
and the resulting chain of partial functions is able to handle the union of all the input types and values of the individual partial functions.
When many combinators are applied to Futures, however the cost is that a new intermediate Future
will be created for each combinator, and that means more expensive context switching.
When the partial functions are computationally expensive,
the extra overhead of the additional context switches is not noticeable,
however a chain of lightweight partial functions would be more efficient if they were consolidated into one partial function with many case statements.
Examples of this are shown where appropriate in this lecture.
The sample code for this lecture can be found in
courseNotes/.
Many of the methods shown require an ExecutionContext;
the code shown on this page assumes that an ExecutionContext is provided.
As we discussed in the MultiThreading lecture,
one way to provide an ExecutionContext is to place the following before the methods that require it:
import concurrent.ExecutionContext.Implicits.global
Some of the code example use postfix notation. You can enable postfix notation with the following:
import language.postfixOps
Exceptions encountered in this lecture can be imported as follows.
import java.io.{FileNotFoundException, IOException}
import java.util.concurrent.TimeoutException
import java.net.{MalformedURLException, UnknownHostException}
Some of the code examples require random numbers. This random number generator was used:
val random = new util.Random()
Combinators for Handling Failure
This section introduces the failure-related combinators. The code examples in this section rely on the following variable values:
lazy val goodUrlStr1 = "https://www.scalacourses.com" lazy val goodUrlStr2 = "https://www.micronauticsresearch.com" lazy val badHostUrlStr = "https://www.not-really-here.com" lazy val badPageUrlStr = "https://scalacourses.com/noPageHere" lazy val badProtocolUrlStr = "blah://scalacourses.com" lazy val badHostFuture: Future[String] = readUrlFuture(badHostUrlStr) lazy val badPageFuture: Future[String] = readUrlFuture(badHostUrlStr) lazy val badProtocolFuture: Future[String] = readUrlFuture(badHostUrlStr) lazy val defaultFuture: Future[String] = readUrlFuture(goodUrlStr1)
readUrlFuture is a method defined in the multi package object.
Package objects were introduced in the Scala Imports and Packages lecture of the
Introduction to Scala course.
This asynchronous method returns a Future of the contents of the web page pointed to by the urlStr parameter.
The method is said to be asynchronous because it returns a Future.
If the web page is longer than the optional maxChars parameter, it is truncated and ellipses are appended to indicate truncation.
/** @return up to first maxChars characters of web page at given url */
def readUrl(url: String, maxChars: Int=500): String = {
val contents = io.Source.fromURL(url).mkString.trim
contents.substring(0, math.min(contents.length, maxChars)) +
(if (contents.length>maxChars) "..." else "")
}
/** @return Future of first maxChars characters of web page at given url */
def readUrlFuture(urlStr: String, maxChars: Int=500): Future[String] =
Future(readUrl(urlStr, maxChars))
Some of the code examples also use the show method.
Take a moment to study it; you should be able to understand what it does by this point in the course.
/** Blocks until contents of web page at urlStr or whatever recoveryFn contains becomes available, then prints contents.
* @param urlStr String passed to java.net.URL that specifies the URL of a web page
* @param msg Optional parameter that specifies a prefix message to be displayed before the web page contents
* @param recoveryFn Function1[Future[String], Future[String]] which is a pluggable recovery strategy for failed web pages
* @return Future of contents of web page at urlStr or recoverFn */
def show(urlStr: String, msg: String="")
(recoveryFn: Future[String] => Future[String]): Future[String] = {
val future = recoveryFn(readUrlFuture(urlStr))
println(s"$urlStr; ${ if (msg.length>0) s"$msg, " else "" }returning " +
Await.result(future, 30 minutes))
future
}
The show method’s recoveryFn parameter has type Function1[Future[String] => Future[String]],
which is a recovery strategy for failed web pages.
If this parameter's type is not familiar to you, please review the Functions are First Class and
Lambda Review & Drill lectures of the
Introduction to Scala course.
This method allows us to write some succinct and efficient code that is easy to work with.
The method returns the Future returned by recoverFn.
Without further ado, following are the Future methods that I promised. The name of each method is followed by its signature on the next line:
-
fallbackTo
fallbackTo[U >: T](given: Future[U]): Future[U] -
fallbackTocreates a newFuturewhich inherits the value of the originalFutureif it completes successfully, or if the originalFuturefails, the result of the givenFutureif it completes successfully. If both the original and the givenFutures fail, the resultingFutureinherits theThrowablefrom the originalFuture.Here is an example that shows this combinator in action, using dot notation and infix notation. The third usage of
fallbackToresults in a failedFuturebecause both the originalFutureand the fallbackFuturefail.Scala codeobject FutureFallbackTo extends App { import multi.futures.FutureFixtures._
println("Dot notation:\n" + Await.result(badHostFuture.fallbackTo(defaultFuture), 30 minutes)) println("\n\nInfix notation:\n" + Await.result(badHostFuture fallbackTo defaultFuture, 30 minutes)) println("\n\n") Await.result(badHostFuture fallbackTo badPageFuture, 30 minutes) }Because
Await.resultis the last expression in the program, and the result is anException, theExceptionis returned by the program.You can run this program by typing:
Shell$ sbt "runMain multi.futures.FutureFallbackTo" Dot notation: <!DOCTYPE html> <html lang="en"> <head> <title>Online Scala Training</title> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> <script src='https://cdn.jsdelivr.net/webjars/jquery/1.11.1/jquery.min.js'></script> <script src='https://cdn.jsdelivr.net/webjars/jquery-cookie/1.4.1/jquery.cookie.js'></script> <script src='https://cdn.jsdelivr.net/webjars/jquery-ui/1.11.1/jquery-ui.min.js'></script> <script src='https://cdn.jsdelivr.net/webjars/bootstrap/2.3.2/js/bootstrap....
Infix notation: <!DOCTYPE html> <html lang="en"> <head> <title>Online Scala Training</title> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> <script src='https://cdn.jsdelivr.net/webjars/jquery/1.11.1/jquery.min.js'></script> <script src='https://cdn.jsdelivr.net/webjars/jquery-cookie/1.4.1/jquery.cookie.js'></script> <script src='https://cdn.jsdelivr.net/webjars/jquery-ui/1.11.1/jquery-ui.min.js'></script> <script src='https://cdn.jsdelivr.net/webjars/bootstrap/2.3.2/js/bootstrap....
Exception in thread "main" java.net.UnknownHostException: www.not-really-here.com ... -
recover
recover[U >: T](pf: PartialFunction[Throwable, U])
(implicit executor: ExecutionContext): Future[U] -
recovercreates a newFuturefrom a partial function that receives anyThrowablethat the original failedFuturemight contain. If the originalFuturewas successful, the newFuturewill have the same value as the originalFuture. If the partial function does not handle the original failedFuture'sThrowable, the newFuturewill inherit the unhandledThrowablefrom the originalFuture.The example recovery strategies for this combinator all call
doSomething, which is intended to suggest to you that you could write whatever code is appropriate in order to handle each type ofException. Here isdoSomethingin all its glory; as you can see, it merely returns theStringit receives.Scala codedef doSomething(msg: String): String = msg
Here is an example of a
Futurethat does not fail. Remember that the recovery strategy is passed into theshowmethod, which is a higher-order function. In this example, therecovermethod is not invoked.Scala codeshow(goodUrlStr1, "no problem") { _.recover { case e: UnknownHostException => doSomething("Handled UnknownHostException") } }Output is:
Outputhttps://www.scalacourses.com; no problem, returning <!DOCTYPE html> <html lang="en"> <head> <title>Online Scala Training</title> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> <script src='https://cdn.jsdelivr.net/webjars/jquery/1.11.1/jquery.min.js'></script> <script src='https://cdn.jsdelivr.net/webjars/jquery-cookie/1.4.1/jquery.cookie.js'></script> <script src='https://cdn.jsdelivr.net/webjars/jquery-ui/1.11.1/jquery-ui.min.js'></script> <script src='https://cdn.jsdelivr.net/webjars/bootstrap/2.3.2/js/bootstrap....
Here is an example how
recoverhandles aFuturethat fails:Scala codeshow(badHostUrlStr) { _.recover { case e: UnknownHostException => doSomething("Handled UnknownHostException") } }Output is:
Outputhttps://www.not-really-here.com; returning Handled UnknownHostException
Here is an example of using
recoverin a partial function that does not handle theExceptionthat is actually thrown.Scala codetry { show(badHostUrlStr) { _.recover { case e: NoSuchElementException => throw new Exception("Should not need to handle NoSuchElementException") } } } catch { case e: Exception => println(s"Did not handle ${e.getClass.getName} exception for $badHostUrlStr") }Output is:
OutputDid not handle java.net.UnknownHostException exception for https://www.not-really-here.com
You can specify multiple
recoverclauses, each with a different partial function. The Scala compiler knows thatMalformedURLExceptionis never thrown so it issues an "unreachable code" warning for that case.Scala codeshow(badHostUrlStr, "handle 4 Exception types in 4 PartialFunctions using recover") { _.recover { case e: FileNotFoundException => doSomething("Handled FileNotFoundException") } .recover { case e: IOException => doSomething("Handled IOException") } .recover { case e: MalformedURLException => doSomething("Handled MalformedURLException") } .recover { case e: UnknownHostException => doSomething("Handled UnknownHostException") } }Output is:
Outputhttps://www.not-really-here.com; handle 4 Exception types in 4 PartialFunctions using recover, returning Handled IOException
Because each
recoverinvocation above creates a newFuture, there will be more context switching than if only one partial function with multiple case statements was supplied to onerecoverinvocation, as shown in this next example. Again, the Scala compiler knows thatMalformedURLExceptionis never thrown so it issues an "unreachable code" warning for that case.Scala codeshow(badHostUrlStr, "handle 4 Exception types in one PartialFunction using recover") { _.recover { case e: FileNotFoundException => doSomething("Handled FileNotFoundException") case e: IOException => doSomething("Handled IOException") case e: MalformedURLException => doSomething("Handled MalformedURLException") case e: UnknownHostException => doSomething("Handled UnknownHostException") } }Output is:
Outputhttps://www.not-really-here.com; handle 4 Exception types in one PartialFunction using recover, returning Handled IOException
You can run this program by typing:
Shell$ sbt "runMain multi.futures.FutureRecover"Output is as shown above.
-
recoverWith
recoverWith[U >: T](pf: PartialFunction[Throwable, Future[U]])(implicit executor: ExecutionContext): Future[U] -
RecoverWithis likerecover, but the partial function references anotherFuture, instead of a value that will be wrapped up into aFuture, and the value of thatFutureis used to create the returnedFutureif therecoverclause is executed.Here is an example of a successful
Future, so the partial function supplied to therecoverWithcombinator is not executed.Scala codeshow(goodUrlStr1, "no problem") { _.recoverWith { case e: UnknownHostException => Future.successful("Handled UnknownHostException") } }Output is:
Outputhttps://www.scalacourses.com; no problem, returning <!DOCTYPE html> <html lang="en"> <head> <title>Online Scala Training</title> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> <script src='https://cdn.jsdelivr.net/webjars/jquery/1.11.1/jquery.min.js'></script> <script src='https://cdn.jsdelivr.net/webjars/jquery-cookie/1.4.1/jquery.cookie.js'></script> <script src='https://cdn.jsdelivr.net/webjars/jquery-ui/1.11.1/jquery-ui.min.js'></script> <script src='https://cdn.jsdelivr.net/webjars/bootstrap/2.3.2/js/bootstrap....
Here is an example of a
Futurethat fails, so the partial function supplied to therecoverWithcombinator is invoked.Scala codeshow(badHostUrlStr) { _.recoverWith { case e: UnknownHostException => Future.successful("Handled UnknownHostException") } }Output is:
Scala codehttps://www.not-really-here.com; returning Handled UnknownHostException Did not handle java.net.UnknownHostException exception for https://www.not-really-here.com
Here is an example of using
recoverWithwith a partial function that does not handle theExceptionthat is actually thrown. In the following code, the Scala compiler knows thatNoSuchElementExceptionis never thrown so it issues an "unreachable code" warning for that case.Scala codetry { show(badHostUrlStr) { _.recoverWith { case e: NoSuchElementException => Future.failed(new Exception("Should not need to handle NoSuchElementException")) } } } catch { case e: Exception => println(s"Did not handle ${e.getClass.getName} exception for $badHostUrlStr") }Output is:
Scala codeDid not handle java.net.UnknownHostException exception for https://www.not-really-here.com
You can specify multiple
recoverWithclauses, each with a different partial function. The Scala compiler knows thatMalformedURLExceptionis never thrown, so it issues an "unreachable code" warning for that case.Scala codeshow(badHostUrlStr, "handle 4 Exception types in 4 PartialFunctions using recoverWith") { _.recoverWith { case e: FileNotFoundException => defaultFuture} .recoverWith { case e: IOException => defaultFuture} .recoverWith { case e: MalformedURLException => defaultFuture} .recoverWith { case e: UnknownHostException => defaultFuture} }Output is:
Outputhttps://www.not-really-here.com; handle 4 Exception types in 1 PartialFunction using recoverWith, returning <!DOCTYPE html> <html lang="en"> <head> <title>Online Scala Training</title> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> <script src='https://cdn.jsdelivr.net/webjars/jquery/1.11.1/jquery.min.js'></script> <script src='https://cdn.jsdelivr.net/webjars/jquery-cookie/1.4.1/jquery.cookie.js'></script> <script src='https://cdn.jsdelivr.net/webjars/jquery-ui/1.11.1/jquery-ui.min.js'></script> <script src='https://cdn.jsdelivr.net/webjars/bootstrap/2.3.2/js/bootstrap....
The above code is equivalent to the following:
Scala codeshow(badHostUrlStr, "handle 4 Exception types in 1 PartialFunction using recoverWith") { _.recoverWith { case e: FileNotFoundException => defaultFuture case e: IOException => defaultFuture case e: MalformedURLException => defaultFuture case e: UnknownHostException => defaultFuture } }Output is the same as before. You can run this program by typing:
Shell$ sbt "runMain multi.futures.FutureRecoverWith"Output is as shown above.
Combinators for Individual Futures
-
collect
collect[S](pf: PartialFunction[T, S])
(implicit executor: ExecutionContext): Future[S] -
collectis likemap, except it accepts a partial function, as we discussed in the Partial Functions lecture. IfrecoverorrecoverWithare chained aftercollect, values not handled by the partial function can be dealt with specially.The penultimate line creates aScala codeval allUrlsWithFutures: List[(String, Future[String])] = urls(includeBad = true) map { url => url -> readUrlFuture(url) }
allUrlsWithFutures foreach { case tuple: (String, Future[String]) => println(s" Examining ${tuple._1}") tuple._2.collect { case content: String if content.toLowerCase.contains("scala") => println(s" Using Future.collect, scala was found in ${tuple._1}") }.recover { case _ => println(s" Future for ${tuple._1} failed") } }
val futures = Future.sequence(allUrlsWithFutures.map { _._2 }) Await.ready(futures, 30 minutes)Future[List[String]]which is used by the last line to determine when to stop blocking the main thread so the program can exit. TheFuture.sequencecombinator is described later in this lecture. You can run this program by typing:Shell$ sbt "runMain multi.futures.FutureCollect" Examining https://www.scalacourses.com Examining https://www.scalacourses.com Examining https://www.not-really-here.com Examining https://scalacourses.com/noPageHere Examining blah://scalacourses.com Future for blah://scalacourses.com failed Future for https://www.not_really_here.com failed Future for https://scalacourses.com/noPageHere failed Using Future.collect, scala was found in https://www.scalacourses.com
Note that the last line of the program is a call to
Await.ready, which returnsUnit. This becomes the returned value of the console application. IfAwait.resultis called instead, theExceptionfrom the first failedFuturewould always be reported. -
filter
filter(p: T ⇒ Boolean)(implicit executor: ExecutionContext): Future[T] -
filtercreates a newFuturethat contains the originalFuture's value if the predicate succeeds, or returns a newFuturewhose value isjava.util.NoSuchElementException("Future.filter predicate is not satisfied")if the predicate fails. IfrecoverorrecoverWithare chained afterfilter, values that failed the predicate can be dealt with.Scala code1 to 10 foreach { i => val future: Future[Int] = Future.successful(random.nextInt(100)) val oddFuture: Future[Int] = future filter { _ % 2 == 1 } val evenFuture: Future[Int] = future filter { _ % 2 == 0 } val oddOr24 = Await.result(oddFuture.recover { case throwable: Throwable => 24}, 30 seconds) val evenOr42 = Await.result(evenFuture.recover { case throwable: Throwable => 42}, 30 seconds) val all = Await.result(evenFuture.recoverWith { case throwable: Throwable => oddFuture }, 30 seconds) println(f"$i%2.0f oddOr24=$oddOr24; evenOr42=$evenOr42; all=$all") }Either
oddFutureorevenFuturemust containFailure(java.util.NoSuchElementException: Future.filter predicate is not satisfied). TheoddOr24andevenOr42variables are created by calling recover on the potentially failedFutures, and provided success values24and42, respectively, in place of failedExceptions. Since we know that all numbers are either even or odd, we can define theallvariable that will contain either the even or the odd number by callingevenFuture.recoverWithto provide the value ofoddFuturein place of theNoSuchElementException.You can run this program by typing:
Shell$ sbt "runMain multi.futures.FutureFilter" 1: oddOr24=24; evenOr42=54; all=54 2: oddOr24=24; evenOr42=70; all=70 3: oddOr24=24; evenOr42=86; all=86 4: oddOr24=24; evenOr42=60; all=60 5: oddOr24=89; evenOr42=42; all=89 6: oddOr24=24; evenOr42=86; all=86 7: oddOr24=24; evenOr42=70; all=70 8: oddOr24=39; evenOr42=42; all=39 9: oddOr24=37; evenOr42=42; all=37 10: oddOr24=24; evenOr42=10; all=10
-
flatMap
flatMap[S](f: T ⇒ Future[S])(implicit executor: ExecutionContext): Future[S] -
flatMapis useful for applying an asynchronous method to aFuture, and it yields a newFuturecontaining the result. Asynchronous methods were introduced in the Futures & Promises lecture. Scala translates for-expressions intoflatMapandmapcalls as discussed in the For-comprehensions With Futures lecture.This code example requires a
Usercase class, which has has two properties:nameandprivilege. We will useflatMapin a functional-style program that first pretends to retrieve aUserinstance from a database. Because this make-believe database has an asynchrous API, theUser.findByIdmethod returns aFuture[User]. User privilege is augmented by callinggrantPrivilege, which is also an asynchronous call involving the make-believe database, so it also returns aFuture[User]. InsidegrantPrivilege, a newUserinstance is copied from the original instance and the extra privilege is added using the case classcopyconstructor. Thecopyconstructor was discussed in the Case Classes lecture. There is a 30% chance thatgrantPrivilegewill fail.Scala codecase class User(name: String, privilege: List[String]) { /** simulate slow database access */ def grantPrivilege(newPriv: String) = Future { Thread.sleep(random.nextInt(1000)) if (System.currentTimeMillis % 3==0) throw new Exception("Unlucky time to grant privilege, not gonna do it!") copy(privilege = newPriv :: privilege) } }
object User { private val userMap = Map( 1L -> "Fred Flintstone", 2L -> "Wilma Flintstone", 3L -> "Barney Rubble", 4L -> "Betty Rubble" )
/** Simulate slow database access */ def findById(id: Long): Future[User] = Future { Thread.sleep(random.nextInt(1000)) User(userMap(id), Nil) } }Future.flatMaphas the same semantics as theflatMapyou already saw for Scala collections in the Combinators lecture. Inside the body of theflatMap, the contents of the monad are available and can be transformed. In this case we callUser.grantPrivilege, pass aStringdescribing the new privilege to be added to theUserinstance'sprivilegeproperty, and obtain aFuture[User]. Ifmaphad been used, aFuture[Future[User]]would have been produced; instead,flatMapflattens the result to produce aFuture[User], which is then passed along to the firstandThencallback.andThenprints out the result, and the 2ndandThencallback signals that the program should terminate. This signalling pattern was introduced in the Future Callbacks lecture.Scala codeval signal = Promise[String]() val user: Future[User] = User.findById(random.nextInt(4)+1) .flatMap { _.grantPrivilege("student") } .andThen { case Success(value) => println(s"""${value.name}'s privilege is now: ${value.privilege.mkString(", ")}.""") case Failure(throwable) => println(s"Problem augmenting student privilege: ${throwable.getMessage}") }.andThen { case _ => signal.success("All done") } Await.ready(signal.future, 30 minutes)You can run this program by typing:
Shell$ sbt "runMain multi.futures.FutureFlatMap"70% of the time output is something like:
OutputFred Flintstone's privilege is now: student.
30% of the time output is:
OutputProblem augmenting student privilege: Unlucky time to grant privilege, not gonna do it!
-
foreach
foreach[U](f: T ⇒ U)
(implicit executor: ExecutionContext): Unit -
Asynchronously processes the value in the
Futureonce the value becomes available. This combinator is only useful for its side effects because it returnsUnit. You might think thatforeachis only useful when dealing with collections of futures, but insteadforeachis a good way for an operation to be performed on aFuturein a non-blocking, asynchronous manner.Scala codeFuture(factorial(12345)).foreach(println)You can run this program by typing:
Shell$ sbt "runMain multi.futures.FutureForeach"Output is:
Scala code34436424691867823916158... very long output ...
The code example in the
courseNotesproject uses aPromiseto signal completion, otherwise no output would be seen. -
map
map[S](f: T ⇒ S)
(implicit executor: ExecutionContext): Future[S] -
mapreturns a newFuturethat contains the value of the originalFuturetransformed according to theFunctionpassed in.mapis useful for applying a synchronous function in order to create a successfulFuture.mapdoes not operate on aFutureif it failed; instead,mappasses along failedFutures unchanged. The following maps aFuture[BigInt]toFuture[Boolean]; the value of the newFutureistrueiffactorialreturned an even number.Scala codeFuture(factorial(12345)).map { _ % 2 == 0 }You can run this program by typing:
Shell$ sbt "runMain multi.futures.FutureMap"The version of the program in the
courseNotesproject has been modified so it generates output before the main thread ends. -
mapTo
mapTo[S](implicit tag: ClassTag[S]): Future[S] -
mapToattempts to create aFuturewith a value type coerced to the given typeS. If unsuccessful aClassCastExceptionis thrown.mapTois useful for downcasting aFuture's value, for example fromAnytoInt.mapTois often used with Akka. This method should rarely be used since it defeats type safety.This code example takes advantage of the fact that
Futureis covariant, so aFuture[Int]can be cast toFuture[Any].mapTodoes not change theFuture's value, it just changes the declared type.Scala codeval x: Future[Any] = Future.successful(1) val y: Future[Int] = x.mapTo[Int]You can run this program by typing:
Shell$ sbt "runMain multi.futures.FutureMapTo" -
transform
transform[S](s: T ⇒ S, f: Throwable ⇒ Throwable)
(implicit executor: ExecutionContext): Future[S] -
transformis likemap, except that in addition to theFunctionthat transforms the value of a successfulFutureit also accepts a method that transforms theThrowableof a failedFuture. This means that unlikemap, which ignores failedFutures,transformoperates on allFutures. If you don't intend to change theThrowableyou should usemaporcollectinstead oftransform. You could think oftransformwith theidentitytransformation as 'transforming theThrowable'. We learned about theidentitytransformation in the Combinators lecture.Here are two examples of how you can use transform on successful and failed
Futures: the first example changes both the value of a successfulFutureand theExceptionof a failedFuture, while the second example preserves the success value by using theidentitytransform, and only changes theExceptionof the failedFuture.Scala codeval f1 = Future.successful(random.nextInt(100)) .filter(_%2==0) .transform(_/2, throwable => new Exception("Allergic to odd numbers... achoo!", throwable)) .andThen { case Success(value) => println(s"The (even) random number divided by 2 = $value") }
val f2 = Future(6/0) .transform(identity, throwable => new Exception("Something went wrong.", throwable))
val signal = Promise[String]() Future.sequence(List(f1, f2)) .andThen { case Success(value) => println(value) case Failure(throwable) => println(throwable.getMessage) }.andThen { case _ => signal.success("All done") } Await.ready(signal.future, 30 minutes)You can run this program by typing:
Shell$ sbt "runMain multi.futures.FutureTransform"50% of the time output is:
OutputAllergic to odd numbers... achoo!
The other 50% of the time output is something like:
OutputThe (even) random number divided by 2 = 7 Something went wrong.
-
zip
zip[U](that: Future[U]): Future[(T, U)] -
zipis useful for associating aFuturewith an index or any arbitrary key. This combinator works with asychronous methods that return aFuture. notice how similar this method is toflatMap;zipaccepts aFuture, whereasflatMapaccepts a method that returns aFuture.This code example simulates a lottery, where users are retrieved from a (slow) database by specifying their
id, and lucky numbers are (slowly) generated, using a database which ensures uniqueness. Thezipmethod will create aFuture[(User, Int)], where theIntis the lucky number.Scala codecase class User(name: String, id: Long)
def getUser: Future[User] = Future { // simulate slow database access Thread.sleep(random.nextInt(1000)) User("Fred Flintstone", 123) }
def lotteryNumber: Future[Int] = Future { // simulate slow database access Thread.sleep(random.nextInt(1000)) random.nextInt(1000000) }
getUser zip lotteryNumber andThen { case Success(tuple) => println(s"User ${tuple._1.name} with id ${tuple._1.id} has lucky number ${tuple._2}.") case Failure(throwable) => println("Problem: " + throwable.getMessage) } andThen { case _ => signal.success("All done") }You can run this program by typing:
Shell$ sbt "runMain multi.futures.FutureZip"Typical output is:
OutputUser Fred Flintstone with id 123 has lucky number 125876.
The version of the program in the
courseNotesproject has been modified so it generates output before the main thread ends.
Combinators for Collections of Futures
These methods are provided by the Future companion object.
Some of them are similar to methods of the same name provided by the
Iterable
companion object, but they are non-blocking.
The Scala compiler ensures that you won't mix up the methods provided by Future and Iterable, however.
-
find
find[T](futures: Iterable[Future[T]])
(p: (T) => Boolean)
(implicit executor: ExecutionContext): Future[Option[T]] -
Completes with a result that satisfies the predicate;
Noneis returned if noFuturesatisfies the predicate.Normally, Scala
Futures always run to completion; invoking thefindcombinator does not change that. If you need to optimize your program's use ofFutures, see the Canceling Futures section in the Working With Collections of Futures lecture.Notice that the
andThencombinator has three cases: two forSuccessand one forNone.Scala codeval f1 = Future(factorial(12345)) val f2 = Future(factorial(23456)) val f3 = Future(factorial(34567)) val signal = Promise[String]() Future .find(List(f1, f2, f3)) { _ % 2 == 0 } .andThen { case Success(Some(result)) => println(s"result = $result") case Success(None) => println(s"No result was even") case Failure(throwable) => println(throwable.getMessage) }.andThen { case _ => signal.success("All done") } Await.ready(signal.future, 30 minutes)You can run this program by typing:
Shell$ sbt "runMain multi.futures.FutureFind" result = 3443642469186... // very long number
-
firstCompletedOf
firstCompletedOf[T](futures: IterableOnce[Future[T]])
(implicit executor: ExecutionContext): Future[T] -
Section to complete.
All
Futures run to completion; ScalaFuturehas no provision for terminating aFutureonce it starts, which is truly unfortunate. Twitter Futures (Finangle) do not have this limitation. The Working With Collections of Futures lecture shows a way of overcoming this limitation.Scala codeval f1 = Future(factorial(12345)) val f2 = Future(factorial(23456)) val f3 = Future(factorial(34567)) Future.firstCompletedOf(List(f1, f2, f3)).andThen { case Success(result) => println(s"result = $result") case Failure(throwable) => println(throwable.getMessage) }You can run this program by typing:
Shell$ sbt "runMain multi.futures.FutureFirstCompletedOf" -
foldLeft
foldLeft[T, R](futures: IterableOnce[Future[T]])
(zero: R)
(op: (R, T) ⇒ R)
(implicit executor: ExecutionContext): Future[R] -
Prior to Scala 2.13 this method was called
fold.foldLeftiterates over the specifiedFutures and accumulates a result, using the given starting valuezero. The computation of the result is performed on the thread used by the lastFutureto complete. The result will be the first failure of any offutures, or any failure encountered byop, or the result of thefoldLeft.Two examples of different
foldLefts are shown below. The firstfoldLeftuses a lambda function (more precisely, aFunction2[BigInt, BigInt, BigInt]) to create a newFuturecontaining the summed value of all theFutures passed to it. The secondfoldLeftuses a namedFunction2[BigInt, BigInt, BigInt]calledbigMaxto create a newFuturecontaining the largest value of all theFutures passed to it.The program is written in such a way that the second
foldLeftdoes not run until the firstfoldLefthas completed, however there is no reason the program could not be written such that bothfoldLefts occurred simultaneously since the computations are independent. The program shuts down after the secondfoldLeft.Scala codeval f1 = Future(factorial(12345)) val f2 = Future(factorial(23456)) val f3 = Future(factorial(34567)) val futures = List(f1, f2, f3)
Future.foldLeft(futures)(BigInt(0))(_ + _) andThen { case Success(result) => println(s"foldLeft addition result = $result") case Failure(throwable) => println(throwable.getMessage) } andThen { case _ => val bigMax: (BigInt, BigInt) => BigInt = (x: BigInt, y: BigInt) => if (x>y) x else y Future .foldLeft(futures)(BigInt(0))(bigMax) .andThen { case Success(result) => println(s"foldLeft max result = $result") case Failure(throwable) => println(throwable.getMessage) } }You can run this program by typing:
Shell$ sbt "runMain multi.futures.FutureFold" foldLeft addition result = 21364444 ... // very big number foldLeft max result = 21364444 ... // very big number
-
reduceLeft
reduceLeft[T, R >: T](futures: collection.immutable.Iterable[Future[T]])
(op: (R, T) => R)
(implicit executor: ExecutionContext): Future[R] -
reduceLeftis likefoldLeft, except that thezerovalue is taken from the value of the firstFutureto complete, instead of being explicitly specified. Since this is normally what you want,reduceLeftis used more frequently thanfoldLeft.This code example is just like the code example for
foldLeft, with thefoldLeft'szeroparameter removed since it is not needed forreduceLeft.Scala codeval f1 = Future(factorial(12345)) val f2 = Future(factorial(23456)) val f3 = Future(factorial(34567)) val futures = List(f1, f2, f3) val bigMax = (x: BigInt, y: BigInt) => if (x>y) x else y val signal = Promise[String]()
Future.reduceLeft(futures)(_+_) .andThen { case Success(result) => println(s"Reduce addition result = $result") case Failure(throwable) => println(throwable.getMessage) } andThen { case _ => Future.reduceLeft(futures)(bigMax) .andThen { case Success(result) => println(s"Reduce max result = $result") case Failure(throwable) => println(throwable.getMessage) }.andThen { case _ => signal.success("All done") } } Await.ready(signal.future, 30 minutes)You can run this program by typing:
Shell$ sbt "runMain multi.futures.FutureReduce" reduce addition result = 2136444 ... // very big number reduce max result = 21364444 ... // very big number
-
sequence
sequence[A, CC[X] <: IterableOnce[X], To](in: CC[Future[A]])
(implicit bf: BuildFrom[CC[Future[A]], A, To], executor: ExecutionContext): Future[To] -
sequencetakes anIterableOnce[Future[A]]and returns an equivalentFuture[IterableOnce[A]]. The newFuturewill complete when allFutures in the collection have completed. If anyFuturein the collection fails, that failure will be propagated to the newFuture, however allFutures will run to completion even if one or moreFutures should fail.For example,
sequencecan be used to transform aList[Future[BigInt]]into aFuture[List[BigInt]], so a callback can be triggered after all the originalFutures have completed.Scala codeval f1 = Future(factorial(12345)) val f2 = Future(factorial(23456)) val f3 = Future(factorial(34567)) val futures = List(f1, f2, f3) val signal = Promise[String]()
Future.sequence(futures) .andThen { case Success(result) => println(s"Sequence result = $result") case Failure(throwable) => println(throwable.getMessage) }.andThen { case _ => signal.success("All done") } Await.ready(signal.future, 30 minutes)You can run this program by typing:
Shell$ sbt "runMain multi.futures.FutureSequence" Reduce result = List(3443642469... very big number ...)
-
traverse
traverse[A, B, M[X] <: IterableOnce[X]](in: M[A])
(fn: (A) => Future[B])
(implicit bf: BuildFrom[M[A], B, M[B]], executor: ExecutionContext): Future[M[B]] -
traversetransforms anIterableOnce[A]into aFuture[IterableOnce[B]]using the givenFunction1[A] => Future[B].traverseis useful for performing a parallel and asynchronousmapand is more flexible thansequence. The resultingFutureonly completes after all theFutures in the passed in collection complete. The order of the elements in the collection of the resultingFuture's value will correspond to the order of the elements of the original collection. If any of theFutures fail, the resultingFuturewill contain theThrowableof the first failedFuture, however allFutures will run to completion even if one or more fail.For example, the following asynchronously applies
factorialto all items oflistin parallel, then prints a summary of all of the completedFutures.Scala codeval signal = Promise[String]() Future.traverse(List(12345, 234, 345)) { x => Future(factorial(x)) andThen { case Success(value) => println(s"""factorial($x)=$value""") } }.andThen { case Success(list) => println(list.mkString("traverse results:\n ", "\n ", "")) case Failure(throwable) => println(throwable.getMessage) }.andThen { case _ => signal.success("All done") } Await.ready(signal.future, 30 minutes)You can run this program by typing:
Shell$ sbt "runMain multi.futures.FutureTraverse"The output shows that each
Future[BigInt]completes asynchronously, however the resultingFuture[List[BigInt]only completes after everyFuture[BigInt]has completed.Outputfactorial(234)=22670150 ... // very big number factorial(345)=24215638 ... // very big number factorial(12345)=35135103 ... // very big number traverse results: 35135103 ... // very big number 22670150 ... // very big number 24215638 ... // very big number
© Copyright 1994-2024 Michael Slinn. All rights reserved.
If you would like to request to use this copyright-protected work in any manner,
please send an email.
This website was made using Jekyll and Mike Slinn’s Jekyll Plugins.