Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

throw exception does not work inside future.map scala?

Tags:

scala

future

def testThrowException(number: Int): Future[Int] = {
 if (number == 0) {
     throw new Exception("number is 0")
 else {
     Future{1}
 }

for the above function, if i call it with testThrowException(0), i can see the exception error message printed in console but if i do something like

def testThrowException(number: Int): Future[Int] = {
anotherFuture.map {
    if (number == 0) {
        throw new Exception("number is 0")
    } else {
        1
    }
}

i am not able to see the exception printed in console but if i do testThrowException.onFailure, i can see the failure message, is there anything i did wrong here? why the exception is not printed out

like image 409
heheda Avatar asked May 12 '19 21:05

heheda


3 Answers

Future runs in a separate thread so throwing inside a Future just crashes that thread whilst leaving the main thread running. For example,

object Hello extends App {
  println(s"Starting in main thread called ${Thread.currentThread.getName}...")
  Future(
    throw new RuntimeException(s"I crashed separate thread called ${Thread.currentThread.getName}")
  ).andThen { case Failure(e) => println(e.getMessage) }
  println("I made it!")
}

should output

Starting in main thread called run-main-e...
I made it!
I crashed separate thread called scala-execution-context-global-253

where we see it crashed separate thread named scala-execution-context-global-253 whilst the main thread run-main-e kept running so I made it! got printed just fine. On the other hand, the following example throws outside a Future

object Hello extends App {
  println(s"Starting in main thread called ${Thread.currentThread.getName}...")
  throw new RuntimeException(s"I crashed the main thread ${Thread.currentThread.getName}")
  println("I made it!")
}

which outputs

Starting in main thread called run-main-d...
[error] (run-main-d) java.lang.RuntimeException: I crashed the main thread run-main-d

where we see main thread run-main-d crashed before I made it! could be printed.

like image 130
Mario Galic Avatar answered Oct 21 '22 07:10

Mario Galic


The first function testThrowException does not return a Future in the case that it gets a 0 as input. Therefore, the program keeps running until the exception appears.

However, as seen in the source code pasted below, the Future.map always returns another future:

  def map[S](f: T => S)(implicit executor: ExecutionContext): Future[S] = { // transform(f, identity)
    val p = Promise[S]()
    onComplete { v => p complete (v map f) }
    p.future
  }

Just defining a Future does not print out its results and it also does not print exceptions thrown. You would need to define the onSuccess, onFailure, or onComplete to do so. However, if a print statement exists in the body of the Future, then it will execute:

def addOne(number: Int): Int = {
    if (number == 0) {
        // you can also print the exception instead of just throwing it.
        throw new Exception("number is 0")
    } else {
        println("success")
        1 + number
    }
}
Future { addOne(1) } // scala.concurrent.Future[Int] = Future(Success(2))
// the above also prints "success"
Future { addOne(0) } // scala.concurrent.Future[Int] = Future(Failure(java.lang.Exception: number is 0))
// the above does not print anything unless the exception is printed before thrown in `addOne`

You can also use onComplete, to handle both success and/or failure:

// prints "got error" plus the stack trace

 - List item

Future {0}.map(addOne).onComplete {
       case Success(value) => println(s"got $value")
       case Failure(t) => println("got error: " + t.getStackTrace.mkString("\n"))
     }

Note that the imports used were:

import scala.concurrent.Future
import scala.concurrent.ExecutionContext.Implicits.global
import scala.util.{Failure, Success}
like image 27
ELinda Avatar answered Oct 21 '22 08:10

ELinda


In the 1st example the exception is just that, a naked exception thrown when encountered. It's not significantly different from something like this.

def testThrowException(number: Int): Future[Int] = {
  throw new Exception("BOOM!")
  . . . //code to create a Future[Int]

The 2nd example throws an exception inside a Fututre. The exception is wrapped in a Future, causing it to fail. You won't see anything sent to the console but if you inspect the result value you should see what you're looking for.

res0: scala.concurrent.Future[Int] = Future(Failure(java.lang.Exception: number is 0))
like image 37
jwvh Avatar answered Oct 21 '22 07:10

jwvh