Last active
September 11, 2021 02:28
-
-
Save ianlintner/a35e1842288aa7e4b2a811f625574b32 to your computer and use it in GitHub Desktop.
Retry a F (functor|future|cats) in Scala e.g. Cats F, EitherT
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import cats.data.EitherT | |
import cats.effect.{Async} | |
import cats.implicits._ | |
import kittys.{AsyncService} | |
val someAsyncService = AsyncService() | |
val key = "imma-key-1234" | |
case class SomeCaseClass(failure: String) | |
/** | |
* Recurse until condition is met could be used for async | |
* generating uniuqe values, performing deletes retrying flakey web API calls. | |
* | |
* This could be generalized and far more idomatic, but it taught me a lot about cats in scala. | |
* | |
* This should be a safe way to do sucessive retries the for makes it sequential but it should | |
* be non-blocking just not parallel as long as your service is non-blocking. | |
*/ | |
def recursiveRetry(key: String, attempts: Integer): F[Either[String,String]] = { | |
(for { | |
// This is getting our F[] that will become our test | |
existing <- EitherT(checkIfItemExists(key)) | |
// This is the conditional test for flow control | |
result <- EitherT( existing match { | |
// Base case / gate around recursion and decrement attempts each time. | |
case Some(_) if attempts > 0 => recursiveRetry(key, attempts-1) | |
// Failure Condition these have to be made into F[]'s | |
case Some(_) => Async[F].pure("Left failure".asLeft[String]) | |
// Return our value | |
case None => Async[F].pure(keye.asRight[String]) | |
}) | |
} yield result).value | |
} | |
/** | |
* Wrapper around F[either] e.g. from an async service to translate to "right" only either | |
* In a EitherT based for comprehensions a left ends the computation full stop. | |
* So to have failure condition everything must be Right so it can be EitherT | |
*/ | |
def checkIfItemExists(key: String): F[Either[String, Option[String]]] = { | |
someAsyncService.get(key) map { | |
case Right(None) => None.asRight[String] | |
case _ => Some(key).asRight[String] | |
} | |
} | |
// Run the retry recursion | |
recursiveRetry(key, 5) map { | |
case Left(fail) => SomeCaseClass(fail).asLeft[String] | |
case Right(pass) => pass.asRight[SomeCaseClass] | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment