All-knowing about error handling in the ZIO library

Reading Time: 2 minutes

Introduction

Errors are unwanted or unexpected events that occur during the execution of a program or during runtime. During the execution of programs, these errors affect the flow control of the program. There are a few situations that can be managed by the program that is not too dangerous. Error handling in ZIO library helps to safely the execution of a program without any interruptions.

In this section, we will examine some of the typical methods for detecting and handling errors in the zio library in Scala.

Either

ZIO#either surfaces failures through the creation of ZIO[R, E, A] while returning a ZIO[R, Nothing, Either[E, A]].

val zeither: UIO[Either[String, Int]] = 
  IO.fail("Error occured!").either

Using ZIO.absolve, you can submerge failures and convert a ZIO[R, Nothing, Either[E, A]] into a ZIO[R, A, E]:

def sqrt(io: UIO[Double]): IO[String, Double] =
  ZIO.absolve(
    io.map(value =>
      if (value < 0.0) Left("Value should be >= 0.0")
      else Right(Math.sqrt(value))
    )
  )

Catching All Errors

You can use the catchAll method to catch and attempt recovery of all types of errors:

val z: IO[IOException, Array[Byte]] = 
  openFile("main.json").catchAll(_ => 
    openFile("backup.json"))

It is possible to pass a callback to catchAll that returns an effect with a different error type (or perhaps Nothing), which will be reflected in the type of effect returned by catchAll.

Catching Some Errors

You can use the catchSome method if you want to catch and recover from only some types of exceptions and effectively attempt recovery.

val data: IO[IOException, Array[Byte]] = 
  openFile("primary.data").catchSome {
    case _ : FileNotFoundException => 
      openFile("backup.data")
  }

Unlike catchAll, catchSome cannot remove or reduce the error type, although it can band things together to create a broader set of errors.

Fallback

With the orElse combination, you can either try one effect or another, if the first fails:

val primaryOrBackupData: IO[IOException, Array[Byte]] = 
  openFile("primary.data").orElse(openFile("backup.data"))

Folding

Scala’s Option and Either data types have fold, a feature that allows you to handle both failures and successes simultaneously. Similar to ZIO effects, ZIO effects also have several methods for handling both failure and success.

You can handle failure and success non-effectively by using the fold method, which provides a non-effective handler in each case:

lazy val DefaultData: Array[Byte] = Array(0, 0)

val primaryOrDefaultData: UIO[Array[Byte]] = 
  openFile("primary.data").fold(
    _    => DefaultData,
    data => data)

By providing an effective (but still pure) handler for both failure and success, foldM allows you to effectively handle both failure and success.

val primaryOrSecondaryData: IO[IOException, Array[Byte]] = 
  openFile("primary.data").foldM(
    _    => openFile("secondary.data"),
    data => ZIO.succeed(data))

FoldM is used for both failure and success in the readUrls method in the following example:

val urls: UIO[Content] =
  readUrls("urls.json").foldM(
    error   => IO.succeed(NoContent(error)), 
    success => fetchContent(success)
  )

Retrying

For retrying failed effects, the ZIO data type has several useful methods.

There are several ways to do this, though the basic one is ZIO#retry, which takes a Schedule and returns a new effect that retries the first if it fails, according to the specified policy:

import zio.clock._

val retriedOpenFile: ZIO[Clock, IOException, Array[Byte]] = 
  openFile("primary.data").retry(Schedule.recurs(5))

ZIO#retryOrElse is another powerful function, which enables the specification of a fallback if the specified policy fails:

  openFile("primary.data").retryOrElse(
    Schedule.recurs(5), 
    (_, _) => ZIO.succeed(DefaultData))

Lastly, ZIO#retryOrElseEither provides the option to return a different type as a fallback.

Conclusion

In this blog, we have learnt how to handle errors and exceptions in the ZIO library. By using ZIO, you can break the problem into smaller parts, then solving them individually, which gives it the ability to solve the problem.

If you are comfortable with basic error handling, then the next step is to learn about safe resource handling.

knoldus

Written by 

Pappu Bhardwaj is a Software Intern at Knoldus Inc. in Noida. He has completed his B.Tech from the KIET Group of Institution, Ghaziabad. He is recognized as a good team player, a dedicated and responsible professional, and a technology enthusiast. He is a quick learner & curious to learn new technologies. He is passionate about Competetive Programming. He loves to play cricket and wildlife photography.