I have a catch block that I end up repeating quite a bit, and found this SO question that confirmed that I can use a partial function as a catch block (What are the use cases for Scala 2.9's try…catch generalization? 1).
Currently, my code looks like this:
var task = newTask("update product", "update for customer " + customerId.toString)
try {
val vcdRouter = actorSystem.actorFor("user/supervisor/router-10.10.10.10:443")
val vdcId = new UUID("92ddba5e-8101-4580-b9a5-e3ee6ea5718f")
val vdcGet = sendExpect[AdminVdcType](vcdRouter, GetVdc(vdcId))
val vdcPut = VdcPutConfig(vdcGet, c)
val vdcPutTask = sendExpect[TaskType](vcdRouter, UpdateVdc(vdcId, vdcPut))
task = task.copy(Progress = 100, status = SuccessType)
} catch {
case failure: NoResponseBodyException =>
logger.debug("*** In putCustomerProduct, got a Left(VcdGatewayException)")
task = task.copy(Progress = 100, status = Error, Error = Option(exceptionToError(failure, BadGateway)))
case failure: VcdGatewayException ⇒
logger.debug("*** In putCustomerProduct, got a Left(VcdGatewayException)")
task = task.copy(Progress = 100, status = Error, Error = Option(exceptionToError(failure, GatewayTimeout)))
case failure: Exception ⇒
logger.debug("*** In putCustomerProduct, got a Left(Exception)")
task = task.copy(Progress = 100, status = Error, Error = Option(exceptionToError(failure)))
}
Since I have this task var that is changing inside the catch block, is there a nice way to make it accessible from within a partial function that holds the catch block? The task is a var since it sets some initialization data like a created timestamp upon entry into the system. I could work around this, but am interested in an answer to the original question anyway.
I'm assuming that you have several different functions with different var task
s that you want to use this with.
You can create a function that takes both task
and a task-setter as parameters, which returns a PartialFunction
that you can use as your catch handler.
def handler(task: Task, setTask: Task => Any): PartialFunction[Throwable, Any] = {
case failure: NoResponseBodyException =>
logger.debug("*** In putCustomerProduct, got a Left(NoResponseBodyException)")
setTask(task.copy(Progress = 100, status = Error, Error = Option(exceptionToError(failure, BadGateway))))
case failure: VcdGatewayException =>
logger.debug("*** In putCustomerProduct, got a Left(VcdGatewayException)")
setTask(task.copy(Progress = 100, status = Error, Error = Option(exceptionToError(failure, GatewayTimeout))))
case failure: Exception =>
logger.debug("*** In putCustomerProduct, got a Left(Exception)")
setTask(task.copy(Progress = 100, status = Error, Error = Option(exceptionToError(failure))))
}
// somewhere else in your code...
var task = newTask("update product", "update for customer " + customerId.toString)
try {
val vcdRouter = actorSystem.actorFor("user/supervisor/router-10.10.10.10:443")
val vdcId = new UUID("92ddba5e-8101-4580-b9a5-e3ee6ea5718f")
val vdcGet = sendExpect[AdminVdcType](vcdRouter, GetVdc(vdcId))
val vdcPut = VdcPutConfig(vdcGet, c)
val vdcPutTask = sendExpect[TaskType](vcdRouter, UpdateVdc(vdcId, vdcPut))
task = task.copy(Progress = 100, status = SuccessType)
} catch handler(task, task = _)
I also agree with user3001 that you should try to reduce the duplication in your catch handlers.
Here is an alternate way using scala.util.control.Exception
.
scala> import util.control.Exception._
import util.control.Exception._
First create the Catch[_]
objects to handle specific exceptions.
scala> val nfeh = handling(classOf[NumberFormatException]) by println
nfeh: util.control.Exception.Catch[Unit] = Catch()
scala> val aobeh = handling(classOf[ArrayIndexOutOfBoundsException]) by println
aobeh: util.control.Exception.Catch[Unit] = Catch()
Combine them together using .or
method. Note that just like in catch
block, order matters.
scala> val h = nfeh or aobeh
h: util.control.Exception.Catch[Unit] = Catch()
Apply the handlers to the code that may throw the exception.
scala> h apply {
| println("1o2".toInt)
| }
java.lang.NumberFormatException: For input string: "1o2"
scala> h apply {
| val x = Array(8)
| println(x(2))
| }
java.lang.ArrayIndexOutOfBoundsException: 2
As for the task
part, you could do something along the following lines:
scala> val nfeh = handling(classOf[NumberFormatException]) by { ex =>
| println(ex)
| -1
| }
nfeh: util.control.Exception.Catch[Int] = Catch()
scala> val aobeh = handling(classOf[ArrayIndexOutOfBoundsException]) by { ex =>
| println(ex)
| -2
| }
aobeh: util.control.Exception.Catch[Int] = Catch()
scala> val h = nfeh or aobeh
h: util.control.Exception.Catch[Int] = Catch()
scala> val task = h apply {
| "120".toInt
| }
task: Int = 120
scala> val task = h apply {
| "12o".toInt
| }
java.lang.NumberFormatException: For input string: "12o"
task: Int = -1
scala> val task = h apply {
| Array(12, 33, 22)(2)
| }
task: Int = 22
scala> val task = h apply {
| Array(12, 33, 22)(6)
| }
java.lang.ArrayIndexOutOfBoundsException: 6
task: Int = -2
I'm quite new to scala, so maybe my answer might not be that correct, but: In each "case" block, the only thing that changes is the error type. At first, I would create another match for the exception type. Something like
def exceptionToErrorReason(val failure:Exception) : ErrorReason = failure.getClass match {
case classOf[NoResponseBodyException] => BadGateway
case classOf[VcdGatewayException ] => GatewayTimeout
case _ => null
}
Now you can change your catch block like this
case failure: Exception ⇒
logger.debug("*** In putCustomerProduct, got a Left(" + failure.getClass().getSimpleName() + )")
task = task.copy(Progress = 100, status = Error, Error = Option(exceptionToError(exceptionToErrorReason(failure)))
and leave out the rest. No need to duplicate anything.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With