Error Handling in Cats Effect [Part-9]
1. Introduction
This is another part of the Cats Effect 3 series. In this short part, let's look at some of the ways to handle errors in Cats Effect 3.
2. Raising an Error
Firstly, let's see how we can raise an error in CE. We can create a failed IO instance using the raiseError method:
val failedIO: IO[Int] = IO.raiseError(new RuntimeException("Boom!"))
3. Handling Errors
Now, let's see different ways to handle error values in CE.
We can handle errors on IO by using handleError
and handleErrorWith
methods:
val handledError = failedIO.handleError(ex => 500)
val handledErrorWith = failedIO.handleErrorWith(ex => IO(-2))
In case of a failure, the handleError block is executed and that value is returned instead of the input value. We need to return an IO from handleErrorWith
block, where as a plain value is returned from the handleError
block.
We can also use recover and recoverWith methods to transform a failed IO effect:
val recoveredFailedIO: IO[Int] = failedIO.recover {
case ex => 0
}
The method recover
takes a partial function and we can apply pattern matching on the exceptions to return a desired value. In the previous example, we can return a value of 0 in case of a failure. Similarly, we can use recoverWith
to return an effectful value inside the partial function instead of a plain value:
val recoveredWithFailedIO: IO[Int] = failedIO.recoverWith {
case ex => IO(0)
}
In this case, we have to return an IO within the pattern matching.
This is similar to the recover and recoverWith methods available on Future.
The main difference between handleError
and recover
method is that the recover
method takes a partial function, which enables us to easily handle different types of errors in different ways. Let's look at an example:
val errorIO = IO(100/0)
val recoveredFailedIO: IO[Int] = errorIO.recover {
case ex: ArithmeticException => 0
}
In the above case, the errorIO
value will be transformed into IO(0)
in case of ArithmeticException
. For any other errors, the same failure value will be returned since we don't have a matching case
statement for them.
If we want to merely perform a side effect on failure, such as logging the exception, we can simply use the method onError:
val loggedFailedError: IO[Int] = failedIO.onError(ex => IO.println("It failed with message: "+ex.getMessage))
Using onError(), we are logging the error message to the console and returning the same IO effect back. In Scala Future, we need to do failedFuture.failed.foreach
method to perform an action on a failed future.
We can use the method attempt
to lift the value of an IO into an Either. If the IO failed, it lifts the value as Right, otherwise into a Left:
val attemptedIO: IO[Either[Throwable, Int]] = loggedFailedError.attempt
There is a method rethrow()
that is the inverse of the attempt
method. It converts an IO[Either[Throwable, A]]
to IO[A]
. If the either is a Right, the value will be lifted into IO, otherwise it will be a failed IO:
val eitherValue: IO[Either[Throwable, String]] = IO.pure(Right("Hello World"))
val rethrownValue: IO[String] = eitherValue.rethrow
Another way to operate on a failed IO is by using orElse()
:
val orElseResult1: IO[Int] = IO(100).orElse(IO(-1)) //returns IO(100)
val orElseResult2: IO[Int] = failedIO.orElse(IO(-1)) //returns IO(-1)
4. Conclusion
In this short part, we discussed various options available in Cats Effect to handle errors. The sample code is available here on GitHub.
Subscribe to my newsletter
Read articles from Yadukrishnan directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
Yadukrishnan
Yadukrishnan
Travel | Movies | History | Nature I am a software developer. Started my career as a Java developer, but later switched to Scala. I wish to write Scala articles which are easier for newbies to follow. I love to travel, prefer to be alone or in very small group. Love to watch movies/series.