Scala Future 完成后进行清理的最佳实践

Best practice to make cleanup after Scala Future is complete

我想在 Future 完成后进行一些清理(例如关闭数据库连接)。
目前我是这样实现的:

Future { ... } onComplete {
  case Success(v) =>
    // ...
    conn.close()
  case Failure(ex) =>
    // ...
    conn.close()
}

有重复的代码,也很乏味。
对此有什么最佳实践吗?

我喜欢用map,你可以这样做:

val mapped: Future[String] = future.map(_ => "OK").recover{case _ => "KO"}

由于对成功和失败都执行相同的操作 conn.close(),因此考虑使用 andThen 将其作为副作用执行,例如

Future { ... } andThen { _ => conn.close() }

同样,使用 onComplete 我们可以做到

Future { ... } onComplete { _ => conn.close() }

andThenonComplete的区别在于后者会returnUnit,也就是丢弃returned的值Future.

可悲的是,Scala Futures 没有内置这个基本功能。因此,我强烈建议使用像 ZIO 或 cats-effect 这样的现代效果系统,它们都解决了这个问题和无数其他问题期货有。做你想做的最简单的方法是使用 bracket 方法:

https://zio.dev/docs/overview/overview_handling_resources

现在 bracket 效果很好,但通常有一种方法效果更好:Managed 类型。如果您在获取资源时始终使用 Managed,则几乎不可能编写泄漏资源的代码:

https://zio.dev/docs/datatypes/datatypes_managed

就是说,如果您绝对必须使用 Futures,则必须编写自己的 try-finally 等价物。或者你可以使用我的:

  def tryFinally[A](tryy: => Future[A])(finallyy: => Future[Any])(
  implicit ec: ExecutionContext): Future[A] =
    Future.fromTry(Try(tryy)).flatten.transformWith { t =>
      finallyy.flatMap((_: Any) => Future.fromTry(t))
    }