@@ -14,7 +14,8 @@ permalink: /overviews/core/:title.html
14
14
## Introduction
15
15
16
16
Futures provide a way to reason about performing many operations
17
- in parallel-- in an efficient and non-blocking way.
17
+ in parallel -- in an efficient and non-blocking way.
18
+
18
19
A [ ` Future ` ] ( https://www.scala-lang.org/api/current/scala/concurrent/Future.html )
19
20
is a placeholder object for a value that may not yet exist.
20
21
Generally, the value of the Future is supplied concurrently and can subsequently be used.
@@ -283,7 +284,7 @@ Completion can take one of two forms:
283
284
A ` Future ` has an important property that it may only be assigned
284
285
once.
285
286
Once a ` Future ` object is given a value or an exception, it becomes
286
- in effect immutable-- it can never be overwritten.
287
+ in effect immutable -- it can never be overwritten.
287
288
288
289
The simplest way to create a future object is to invoke the ` Future.apply `
289
290
method which starts an asynchronous computation and returns a
@@ -335,8 +336,8 @@ To obtain the list of friends of a user, a request
335
336
has to be sent over a network, which can take a long time.
336
337
This is illustrated with the call to the method ` getFriends ` that returns ` List[Friend] ` .
337
338
To better utilize the CPU until the response arrives, we should not
338
- block the rest of the program-- this computation should be scheduled
339
- asynchronously. The ` Future.apply ` method does exactly that-- it performs
339
+ block the rest of the program -- this computation should be scheduled
340
+ asynchronously. The ` Future.apply ` method does exactly that -- it performs
340
341
the specified computation block concurrently, in this case sending
341
342
a request to the server and waiting for a response.
342
343
@@ -396,7 +397,7 @@ We are often interested in the result of the computation, not just its
396
397
side-effects.
397
398
398
399
In many future implementations, once the client of the future becomes interested
399
- in its result, it has to block its own computation and wait until the future is completed--
400
+ in its result, it has to block its own computation and wait until the future is completed --
400
401
only then can it use the value of the future to continue its own computation.
401
402
Although this is allowed by the Scala ` Future ` API as we will show later,
402
403
from a performance point of view a better way to do it is in a completely
@@ -428,7 +429,7 @@ value is a `Throwable`.
428
429
Coming back to our social network example, let's assume we want to
429
430
fetch a list of our own recent posts and render them to the screen.
430
431
We do so by calling a method ` getRecentPosts ` which returns
431
- a ` List[String] ` -- a list of recent textual posts:
432
+ a ` List[String] ` -- a list of recent textual posts:
432
433
433
434
{% tabs futures-05 class=tabs-scala-version %}
434
435
{% tab 'Scala 2' for=futures-05 %}
@@ -650,7 +651,7 @@ some other currency. We would have to repeat this pattern within the
650
651
to reason about.
651
652
652
653
Second, the ` purchase ` future is not in the scope with the rest of
653
- the code-- it can only be acted upon from within the ` foreach `
654
+ the code -- it can only be acted upon from within the ` foreach `
654
655
callback. This means that other parts of the application do not
655
656
see the ` purchase ` future and cannot register another ` foreach `
656
657
callback to it, for example, to sell some other currency.
@@ -760,7 +761,7 @@ Here is an example of `flatMap` and `withFilter` usage within for-comprehensions
760
761
{% endtabs %}
761
762
762
763
The ` purchase ` future is completed only once both ` usdQuote `
763
- and ` chfQuote ` are completed-- it depends on the values
764
+ and ` chfQuote ` are completed -- it depends on the values
764
765
of both these futures so its own computation cannot begin
765
766
earlier.
766
767
@@ -1086,7 +1087,7 @@ Here is an example of how to block on the result of a future:
1086
1087
1087
1088
In the case that the future fails, the caller is forwarded the
1088
1089
exception that the future is failed with. This includes the ` failed `
1089
- projection-- blocking on it results in a ` NoSuchElementException `
1090
+ projection -- blocking on it results in a ` NoSuchElementException `
1090
1091
being thrown if the original future is completed successfully.
1091
1092
1092
1093
Alternatively, calling ` Await.ready ` waits until the future becomes
@@ -1095,7 +1096,7 @@ that method will not throw an exception if the future is failed.
1095
1096
1096
1097
The ` Future ` trait implements the ` Awaitable ` trait with methods
1097
1098
` ready() ` and ` result() ` . These methods cannot be called directly
1098
- by the clients-- they can only be called by the execution context.
1099
+ by the clients -- they can only be called by the execution context.
1099
1100
1100
1101
1101
1102
@@ -1105,8 +1106,8 @@ When asynchronous computations throw unhandled exceptions, futures
1105
1106
associated with those computations fail. Failed futures store an
1106
1107
instance of ` Throwable ` instead of the result value. ` Future ` s provide
1107
1108
the ` failed ` projection method, which allows this ` Throwable ` to be
1108
- treated as the success value of another ` Future ` . The following special
1109
- exceptions are treated differently :
1109
+ treated as the success value of another ` Future ` .
1110
+ The following exceptions receive special treatment :
1110
1111
1111
1112
1 . ` scala.runtime.NonLocalReturnControl[_] ` -- this exception holds a value
1112
1113
associated with the return. Typically, ` return ` constructs in method
@@ -1121,11 +1122,225 @@ behind this is to prevent propagation of critical and control-flow related
1121
1122
exceptions normally not handled by the client code and at the same time inform
1122
1123
the client in which future the computation failed.
1123
1124
1124
- Fatal exceptions (as determined by ` NonFatal ` ) are rethrown in the thread executing
1125
+ Fatal exceptions (as determined by ` NonFatal ` ) are rethrown from the thread executing
1125
1126
the failed asynchronous computation. This informs the code managing the executing
1126
1127
threads of the problem and allows it to fail fast, if necessary. See
1127
1128
[ ` NonFatal ` ] ( https://www.scala-lang.org/api/current/scala/util/control/NonFatal$.html )
1128
- for a more precise description of the semantics.
1129
+ for a more precise description of which exceptions are considered fatal.
1130
+
1131
+ ` ExecutionContext.global ` handles fatal exceptions by printing a stack trace, by default.
1132
+
1133
+ A fatal exception means that the ` Future ` associated with the computation will never complete.
1134
+ That is, "fatal" means that the error is not recoverable for the ` ExecutionContext `
1135
+ and is also not intended to be handled by user code. By contrast, application code may
1136
+ attempt recovery from a "failed" ` Future ` , which has completed but with an exception.
1137
+
1138
+ An execution context can be customized with a reporter that handles fatal exceptions.
1139
+ See the factory methods [ ` fromExecutor ` ] ( https://www.scala-lang.org/api/current/scala/concurrent/ExecutionContext$.html#fromExecutor(e:java.util.concurrent.Executor,reporter:Throwable=%3EUnit):scala.concurrent.ExecutionContextExecutor )
1140
+ and [ ` fromExecutorService ` ] ( https://www.scala-lang.org/api/current/scala/concurrent/ExecutionContext$.html#fromExecutorService(e:java.util.concurrent.ExecutorService,reporter:Throwable=%3EUnit):scala.concurrent.ExecutionContextExecutorService ) .
1141
+
1142
+ Since it is necessary to set the [ ` UncaughtExceptionHandler ` ] ( https://docs.oracle.com/en/java/javase/20/docs/api/java.base/java/lang/Thread.UncaughtExceptionHandler.html )
1143
+ for executing threads, as a convenience, when passed a ` null ` executor,
1144
+ ` fromExecutor ` will create a context that is configured the same as ` global ` ,
1145
+ but with the supplied reporter for handling exceptions.
1146
+
1147
+ The following example demonstrates how to obtain an ` ExecutionContext ` with custom error handling
1148
+ and also shows the result of different exceptions, as described above:
1149
+
1150
+ {% tabs exceptions class=tabs-scala-version %}
1151
+ {% tab 'Scala 2' for=exceptions %}
1152
+ ~~~ scala
1153
+ import java .util .concurrent .{ForkJoinPool , TimeoutException }
1154
+ import scala .concurrent .{Await , ExecutionContext , Future }
1155
+ import scala .concurrent .duration .DurationInt
1156
+ import scala .util .{Failure , Success }
1157
+
1158
+ object Test extends App {
1159
+ def crashing (): Int = throw new NoSuchMethodError (" test" )
1160
+ def failing (): Int = throw new NumberFormatException (" test" )
1161
+ def interrupt (): Int = throw new InterruptedException (" test" )
1162
+ def erroring (): Int = throw new AssertionError (" test" )
1163
+
1164
+ // computations can fail in the middle of a chain of combinators, after the initial Future job has completed
1165
+ def testCrashes ()(implicit ec : ExecutionContext ): Future [Int ] =
1166
+ Future .unit.map(_ => crashing())
1167
+ def testFails ()(implicit ec : ExecutionContext ): Future [Int ] =
1168
+ Future .unit.map(_ => failing())
1169
+ def testInterrupted ()(implicit ec : ExecutionContext ): Future [Int ] =
1170
+ Future .unit.map(_ => interrupt())
1171
+ def testError ()(implicit ec : ExecutionContext ): Future [Int ] =
1172
+ Future .unit.map(_ => erroring())
1173
+
1174
+ // Wait for 1 second for the the completion of the passed `future` value and print it
1175
+ def check (future : Future [Int ]): Unit =
1176
+ try {
1177
+ Await .ready(future, 1 .second)
1178
+ for (completion <- future.value) {
1179
+ println(s " completed $completion" )
1180
+ // In case of failure, also print the cause of the exception, when defined
1181
+ completion match {
1182
+ case Failure (exception) if exception.getCause != null =>
1183
+ println(s " caused by ${exception.getCause}" )
1184
+ _ => ()
1185
+ }
1186
+ }
1187
+ } catch {
1188
+ // If the future value did not complete within 1 second, the call
1189
+ // to `Await.ready` throws a TimeoutException
1190
+ case _ : TimeoutException => println(s " did not complete " )
1191
+ }
1192
+
1193
+ def reporter (t : Throwable ) = println(s " reported $t" )
1194
+
1195
+ locally {
1196
+ // using the `global` implicit context
1197
+ import ExecutionContext .Implicits ._
1198
+ // a successful Future
1199
+ check(Future (42 )) // completed Success(42)
1200
+ // a Future that completes with an application exception
1201
+ check(Future (failing())) // completed Failure(java.lang.NumberFormatException: test)
1202
+ // same, but the exception is thrown somewhere in the chain of combinators
1203
+ check(testFails()) // completed Failure(java.lang.NumberFormatException: test)
1204
+ // a Future that does not complete because of a linkage error;
1205
+ // the trace is printed to stderr by default
1206
+ check(testCrashes()) // did not complete
1207
+ // a Future that completes with an operational exception that is wrapped
1208
+ check(testInterrupted()) // completed Failure(java.util.concurrent.ExecutionException: Boxed Exception)
1209
+ // caused by java.lang.InterruptedException: test
1210
+ // a Future that completes due to a failed assert, which is bad for the app,
1211
+ // but is handled the same as interruption
1212
+ check(testError()) // completed Failure(java.util.concurrent.ExecutionException: Boxed Exception)
1213
+ // caused by java.lang.AssertionError: test
1214
+ }
1215
+ locally {
1216
+ // same as `global`, but adds a custom reporter that will handle uncaught
1217
+ // exceptions and errors reported to the context
1218
+ implicit val ec : ExecutionContext = ExecutionContext .fromExecutor(null , reporter)
1219
+ check(testCrashes()) // reported java.lang.NoSuchMethodError: test
1220
+ // did not complete
1221
+ }
1222
+ locally {
1223
+ // does not handle uncaught exceptions; the executor would have to be
1224
+ // configured separately
1225
+ val executor = ForkJoinPool .commonPool()
1226
+ implicit val ec : ExecutionContext = ExecutionContext .fromExecutor(executor, reporter)
1227
+ // the reporter is not invoked and the Future does not complete
1228
+ check(testCrashes()) // did not complete
1229
+ }
1230
+ locally {
1231
+ // sample minimal configuration for a context and underlying pool that
1232
+ // use the reporter
1233
+ val handler : Thread .UncaughtExceptionHandler =
1234
+ (_ : Thread , t : Throwable ) => reporter(t)
1235
+ val executor = new ForkJoinPool (
1236
+ Runtime .getRuntime.availableProcessors,
1237
+ ForkJoinPool .defaultForkJoinWorkerThreadFactory, // threads use the pool's handler
1238
+ handler,
1239
+ /* asyncMode=*/ false
1240
+ )
1241
+ implicit val ec : ExecutionContext = ExecutionContext .fromExecutor(executor, reporter)
1242
+ check(testCrashes()) // reported java.lang.NoSuchMethodError: test
1243
+ // did not complete
1244
+ }
1245
+ }
1246
+ ~~~
1247
+ {% endtab %}
1248
+
1249
+ {% tab 'Scala 3' for=exceptions %}
1250
+ ~~~ scala
1251
+ import java .util .concurrent .{ForkJoinPool , TimeoutException }
1252
+ import scala .concurrent .{Await , ExecutionContext , Future }
1253
+ import scala .concurrent .duration .DurationInt
1254
+ import scala .util .{Failure , Success }
1255
+
1256
+ def crashing (): Int = throw new NoSuchMethodError (" test" )
1257
+ def failing (): Int = throw new NumberFormatException (" test" )
1258
+ def interrupt (): Int = throw new InterruptedException (" test" )
1259
+ def erroring (): Int = throw new AssertionError (" test" )
1260
+
1261
+ // computations can fail in the middle of a chain of combinators,
1262
+ // after the initial Future job has completed
1263
+ def testCrashes ()(using ExecutionContext ): Future [Int ] =
1264
+ Future .unit.map(_ => crashing())
1265
+ def testFails ()(using ExecutionContext ): Future [Int ] =
1266
+ Future .unit.map(_ => failing())
1267
+ def testInterrupted ()(using ExecutionContext ): Future [Int ] =
1268
+ Future .unit.map(_ => interrupt())
1269
+ def testError ()(using ExecutionContext ): Future [Int ] =
1270
+ Future .unit.map(_ => erroring())
1271
+
1272
+ // Wait for 1 second for the the completion of the passed `future` value and print it
1273
+ def check (future : Future [Int ]): Unit =
1274
+ try
1275
+ Await .ready(future, 1 .second)
1276
+ for completion <- future.value do
1277
+ println(s " completed $completion" )
1278
+ // In case of failure, also print the cause of the exception, when defined
1279
+ completion match
1280
+ case Failure (exception) if exception.getCause != null =>
1281
+ println(s " caused by ${exception.getCause}" )
1282
+ case _ => ()
1283
+ catch
1284
+ // If the future value did not complete within 1 second, the call
1285
+ // to `Await.ready` throws a TimeoutException
1286
+ case _ : TimeoutException => println(s " did not complete " )
1287
+
1288
+ def reporter (t : Throwable ) = println(s " reported $t" )
1289
+
1290
+ @ main def test (): Unit =
1291
+ locally :
1292
+ // using the `global` implicit context
1293
+ import ExecutionContext .Implicits .given
1294
+ // a successful Future
1295
+ check(Future (42 )) // completed Success(42)
1296
+ // a Future that completes with an application exception
1297
+ check(Future (failing())) // completed Failure(java.lang.NumberFormatException: test)
1298
+ // same, but the exception is thrown somewhere in the chain of combinators
1299
+ check(testFails()) // completed Failure(java.lang.NumberFormatException: test)
1300
+ // a Future that does not complete because of a linkage error;
1301
+ // the trace is printed to stderr by default
1302
+ check(testCrashes()) // did not complete
1303
+ // a Future that completes with an operational exception that is wrapped
1304
+ check(testInterrupted()) // completed Failure(java.util.concurrent.ExecutionException: Boxed Exception)
1305
+ // caused by java.lang.InterruptedException: test
1306
+ // a Future that completes due to a failed assert, which is bad for the app,
1307
+ // but is handled the same as interruption
1308
+ check(testError()) // completed Failure(java.util.concurrent.ExecutionException: Boxed Exception)
1309
+ // caused by java.lang.AssertionError: test
1310
+
1311
+ locally :
1312
+ // same as `global`, but adds a custom reporter that will handle uncaught
1313
+ // exceptions and errors reported to the context
1314
+ given ExecutionContext = ExecutionContext .fromExecutor(null , reporter)
1315
+ check(testCrashes()) // reported java.lang.NoSuchMethodError: test
1316
+ // did not complete
1317
+
1318
+ locally :
1319
+ // does not handle uncaught exceptions; the executor would have to be
1320
+ // configured separately
1321
+ val executor = ForkJoinPool .commonPool()
1322
+ given ExecutionContext = ExecutionContext .fromExecutor(executor, reporter)
1323
+ // the reporter is not invoked and the Future does not complete
1324
+ check(testCrashes()) // did not complete
1325
+
1326
+ locally :
1327
+ // sample minimal configuration for a context and underlying pool that
1328
+ // use the reporter
1329
+ val handler : Thread .UncaughtExceptionHandler =
1330
+ (_ : Thread , t : Throwable ) => reporter(t)
1331
+ val executor = new ForkJoinPool (
1332
+ Runtime .getRuntime.availableProcessors,
1333
+ ForkJoinPool .defaultForkJoinWorkerThreadFactory, // threads use the pool's handler
1334
+ handler,
1335
+ /* asyncMode=*/ false
1336
+ )
1337
+ given ExecutionContext = ExecutionContext .fromExecutor(executor, reporter)
1338
+ check(testCrashes()) // reported java.lang.NoSuchMethodError: test
1339
+ // did not complete
1340
+ end test
1341
+ ~~~
1342
+ {% endtab %}
1343
+ {% endtabs %}
1129
1344
1130
1345
## Promises
1131
1346
@@ -1226,7 +1441,7 @@ continues its computation, and finally completes the future `f` with a
1226
1441
valid result, by completing promise ` p ` .
1227
1442
1228
1443
Promises can also be completed with a ` complete ` method which takes
1229
- a potential value ` Try[T] ` -- either a failed result of type ` Failure[Throwable] ` or a
1444
+ a potential value ` Try[T] ` -- either a failed result of type ` Failure[Throwable] ` or a
1230
1445
successful result of type ` Success[T] ` .
1231
1446
1232
1447
Analogous to ` success ` , calling ` failure ` and ` complete ` on a promise that has already
0 commit comments