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 tasks 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