Scala使用Cats IO/Either替代Future/异常的链式调用问题求助
问题解答
修正后可运行代码
import cats.data.EitherT import cats.effect.{IO, IOApp} import scala.util.control.NonFatal // 第三方Java库方法 def dbLoad(id: Int): Int = { throw new Exception("db exception") } sealed trait DbError extends Exception with Product object DbError { case object SomeError extends DbError } // 优化后的load方法:去掉不必要的Future封装,直接用IO处理阻塞调用 val load: Int => IO[Either[DbError, Int]] = id => IO.blocking(dbLoad(id)) .attempt .map(_.leftMap(_ => DbError.SomeError)) // 用EitherT简化短路逻辑的loadAll实现 val loadAll: IO[Either[DbError, (Int, Int, Int)]] = (for { i1 <- EitherT(load(1)) i2 <- EitherT(load(i1)) i3 <- EitherT(load(i2)) } yield (i1, i2, i3)).value
1. 单次load返回Left时终止后续执行
你当前的IO[Either[DbError, Int]]是两层结构,原生for推导只能处理外层IO的短路,无法感知内层Either的状态。有两种实现方案:
- 方案1(推荐):用
cats.data.EitherT这个monad转换器对嵌套结构做包装。EitherT的flatMap逻辑天然支持短路:只要某一步返回Left,后续所有调用都不会执行,直接返回第一个出现的Left。最后调用.value即可把EitherT[IO, DbError, (Int,Int,Int)]转回你需要的IO[Either[DbError, (Int,Int,Int)]]类型 - 方案2(无依赖):手动写flatMap+模式匹配逻辑,每一步判断Either状态,是Left就直接返回,是Right才继续执行后续调用,示例如下:
val loadAllManual: IO[Either[DbError, (Int, Int, Int)]] = load(1).flatMap { case Left(e) => IO.pure(Left(e)) case Right(i1) => load(i1).flatMap { case Left(e) => IO.pure(Left(e)) case Right(i2) => load(i2).map { case Left(e) => Left(e) case Right(i3) => Right((i1, i2, i3)) } } }
2. 传递Right侧值到下一次调用
用EitherT包装后,for推导中绑定的变量直接就是Either的Right侧值,不需要手动做模式匹配:
- 上面示例中
i1 <- EitherT(load(1))拿到的i1就是Int类型的Right值,可以直接作为参数传给下一次的load(i1) - 如果某一步返回Left,for推导会直接短路,不会走到后续的参数传递逻辑
如果用手动实现的方案,在模式匹配拿到Right(i1)之后,直接把i1作为参数传入下一次load调用即可。
3. 现有实现的问题与优化方案
现有思路的不合理处:
- 不必要的Future封装:IO本身完全可以处理阻塞的第三方Java调用,用
IO.blocking包裹阻塞操作即可,还能自动绑定到Cats IO专门的阻塞线程池,比自己创建Future更安全,也避免了多余的线程切换开销 - 嵌套结构手动处理太冗余:原生的
IO[Either]需要写大量的模式匹配代码处理短路逻辑,容易出错
更优方案就是上面示例的实现: - 阻塞调用直接用
IO.blocking包裹,用attempt捕获异常转成Either - 用EitherT做组合,代码简洁且符合函数式编程的惯用写法,短路逻辑和值传递都由框架原生保证
内容的提问来源于stack exchange,提问作者Andres
相关产品推荐
相关产品推荐

