Extract value from Future using Cats

Viewed 251

I am trying to write a small library for GitHub's API. And I am trying to simulate the behavior of Github4s's library, here is an example of this library:

val user1 = Github(accessToken).users.get("rafaparadela")
object ProgramEval {
    val u1 = user1.exec[Eval, HttpResponse[String]]().value
 }

import cats.implicits._
import github4s.GithubResponses.GHResult

ProgramEval.u1 match {
  // Here the actual value of the request is returned, 
  // not the same as Future's onComplete, where the return type is Unit
  case Right(GHResult(result, status, headers)) => result.login
  case Left(e) => e.getMessage
}

I am quoting the docs:

Every Github4s API call returns a GHIO[GHResponse[A]] which is an alias for Free[Github4s, GHResponse[A]].

GHResponse[A] is, in turn, a type alias for Either[GHException, GHResult[A]].

GHResult contains the result A given by Github as well as the status code and headers of the response:

At some point, they are making an HttpRequest using HttpClient.scala

How could I replicate this behavior myself? I have tried using Cats.Eval as in the example, but I end up having the same Future[String].

Also, I an facing with some nesting problems as I make request, for example, to get a list of contributors of an organization I need to make two HttpRequest:

  • One for getting the organization repos
  • One for each repo in order to get a list of contributors

This result on a Future[List[Future[Users]]], and I face the same problem as above, in order to obtain the results, I have to do:

(result:Future[List[Future[Users]]]) onComplete { users =>
    users.foreach {
        _  onComplete {
            // Process result
        }
    }
}

But I would like to return the value, as github4s. I've been reading about Cats's Applicative and Traversable Functors without luck.

2 Answers

You can convert List[Future] to Future[List] using Future.sequence and then squash nested futures.

The resulting code:

val input: Future[List[Future[Users]]] = ???
implicit  val ec: ExecutionContext = ExecutionContext.global
val result: Future[List[Users]] = input.flatMap(list => Future.sequence(list))

Not sure why you think this problem will be solved by cats. It seems what you want in the end is the String (body value), not Future[String].

The only way you can extract a value from a Future is to block your main thread waiting on it using Await. As that is not very efficient you can create a callback using onComplete as you did.

The important thing to notice is that once you wrap something in Future, most of the time you don't want to get it back. When you do it is usually on edge of the program (for example your main thread).

Let's take your problem: val f = Future[List[Future[Users]]]. To get this unnested you need to:

f.flatMap(Future.sequence) gives you Future[List[Users]].

Future.sequence will make Future[List[Future[Users]]] into Future[Future[List[Users]]]. It merges all the Futures in the list into a single one. Any failed future will make the outer future be a failure. If you don't want that, you can .recover each inner Future.

f.flatMapwill make Future[Future[List[Users]] into Future[List[Users]]. It runs the inner Future after the outer one finishes. Same for error handling as above here.

Related