Reputation: 838
I have a function that must return a Future[Unit].
In my function I'm writing a file which returns a Future[IOResult]. Both the Future and the IOResult can have a failure status.
I'd like to check for success and failure of both the Future and the IOResult in my function, but just return a Failure[Unit] from this function, is this possible?
The code below is reporting errors:
discarded non-Unit value
[error] Future.successful(Unit)
discarded non-Unit value
[error] Future.failed(e)
Here is my function:
def create(filePath: String, fileStream: Source[ByteString, Any]): Future[Unit] = {
val writeResultFuture: Future[IOResult] = fileStream.runWith(FileIO.toPath(filePath))
writeResultFuture map { writeResult =>
writeResult.status match {
case Success(_) =>
logger.info(s"Successfully uploaded: ${fileInfo.fileName} to: $filePath")
Future.successful(Unit)
case Failure(e) =>
logger.error(s"Failed to upload: ${fileInfo.fileName} to: $filePath", e)
Future.failed(e)
}
} recover {
case e =>
logger.error(s"Failed to upload: ${fileInfo.fileName} to: $filePath", e)
Future.failed(e)
}
}
Upvotes: 1
Views: 1905
Reputation: 40510
Three things.
Unit
is not a value of type Unit
(it's a reference to an object scala.Unit
defined in scala library). You want ()
to denote a value of Unit
.
The function you give to recover
is supposed to return the actual result, not a Future
(recoverWith
works with futures).
The function you give to map
returns the actual result too. You'd want flatMap
if you actually needed to return a Future
So, something like this should work:
writeResultFuture.map { writeResult =>
writeResult.status match {
case Success(_) =>
logger.info(s"Successfully uploaded: ${fileInfo.fileName} to: $filePath")
case Failure(e) =>
logger.error(s"Failed to upload: ${fileInfo.fileName} to: $filePath", e)
throw e
}
}.recover { case e =>
logger.error(s"Failed to upload: ${fileInfo.fileName} to: $filePath", e)
throw e
}
Better yet, use onFailure
instead of recover
- that way you don't need to rethrow. Just do .onFailure { e => logger.error(...) }
Also, note that you are logging errors twice this way (once inside map
, and then again in recover
/onFailure
) ... Consider removing the recover
part alltogether.
writeResultFuture.map(_.status).map {
case Success(_) => logger.info(s"Successfully uploaded: ${fileInfo.fileName} to: $filePath")
case Failure(e) => logger.error(s"Failed to upload: ${fileInfo.fileName} to: $filePath", e)
throw e
}
Upvotes: 4
Reputation: 26597
You are returning Future's so your end result will be of type Future[Future[…]], in your case we do not need to nest at all, so we can change the code to:
DISCLAIMER: I only typed the code in here, it has not been typechecked, YMMV. No warranties. etc etc
fileStream.runWith(FileIO.toPath(filePath)) transform {
case Failure(e) => Failure(e)
case Success(i: IOResult) => i.status
} andThen {
case Success(_) =>
logger.info(s"Successfully uploaded: ${fileInfo.fileName} to: $filePath")
case Failure(e) =>
logger.error(s"Failed to upload: ${fileInfo.fileName} to: $filePath", e)
}
} map { _ => () }
Upvotes: 0
Reputation: 27421
This might work:
writeResultFuture flatMap { writeResult =>
writeResult.status match {
case Success(_) =>
logger.info(s"Successfully uploaded: ${fileInfo.fileName} to: $filePath")
Future.successful()
case Failure(e) =>
Future.failed(e)
}
} recover {
case e =>
logger.error(s"Failed to upload: ${fileInfo.fileName} to: $filePath", e)
}
Using flatMap
will flatten the nested Future
returned by your match
statement, and recover
now returns Unit
so that result is Future[Unit]
. recover
will catch all errors that are generated so you don't need to print anything in the inner Failure
case.
Upvotes: 0