user1189332
user1189332

Reputation: 1941

Scala Futures and Promises Callback basics

I'm a beginner to Scala Futures/Promises.

I'm trying to do this using Futures (using callbacks):

Here is the code:

// Final write container to hold the result.
val promise = Promise[List[GitData]]()

val repositoriesF = Future {
  GitDriver.repositoriesOf(request.user)
}

val gitRepositories = Promise[List[GitRepository]]()

repositoriesF onSuccess {
  case repositories => {
    val contributors = Promise[List[Contributor]]()
    val contributorsF = Future(repositories.map(GitDriver.contributors))
    val readMe = Promise[List[String]]
    val readMeF = Future(repositories.map(GitDriver.readme))

    contributorsF.onSuccess {
      case contrib => contributors.success(contrib.flatten)
    }

    readMeF.onSuccess {
      case r => readMe.success(r)
    }

    val extractedContributors = contributors.future.value
    val extractedReadme = readMe.future.value
    println(extractedContributors.size)
    println(extractedReadme.size)
  }
}

Await.ready(repositoriesF, Duration.Inf)

The sizes of the contributors and readme are always zero.

My approach to this problem is, I can parallalize the extraction of contributors and README through Futures as they don't depend upon each other. All they need is just a repository object.

What am I missing here? I'm sure that there are more elegant solutions using for comprehensions, maps, dsls etc. But just curious to get to the bottom of this one! Thanks.

Upvotes: 1

Views: 512

Answers (3)

Peter Neyens
Peter Neyens

Reputation: 9820

Your future.value and println lines are executed before the Futures (the explicit with Future(...) and the Promise.future set in onSuccess) are completed.

The value of the Future is still None, compare with :

val p = Promise[Int]()
p.future.value
// Option[scala.util.Try[Int]] = None
p.future.value.size
// Int = 0

Since you want to continue with your solution, you could do something like (without the superfluous Promises):

repositoriesF onSuccess {
  case repositories =>
    val contributorsF = Future(repositories.flatMap(GitDriver.contributors))
    val readMeF = Future(repositories.map(GitDriver.readme))

    contributorsF zip readMeF onSuccess {
      case (contrib, readme) => 
        println(contrib.size)
        println(readme.size)
    }
}

Upvotes: 0

Daniel Langdon
Daniel Langdon

Reputation: 5999

You are doing a few weird things. The main issue as per your problem is that you are printing the values inside this block:

repositoriesF onSuccess {
   case repositories => {
      ...

So in a sense, you are only waiting for the FIRST async computation, and have only set callbacks for the other information you need (readme and contributors). Right after setting the callbacks, those are not completed and you have no results.

The second weird thing is that you are doing Await() on the first future and not on the other computations. Most likely you want to wait on the entire information to be available.

The third weird thing you are doing is using too many promises. You use a Promise not when you want to get some result, but when you have to return a Future to someone else and need a way to complete that future later on, when you no longer have access to the future itself.

As for how to solve your problem, I would chain futures using for comprehensions:

val futureGitData = for {
  repos <- Future { GitDriver.repositoriesOf(request.user) }
  repo <- repos
  readme <- Future { GitDriver.readme(repo) } 
  contributors <- Future { GitDriver.contributors(repo) } 
} yield (repo, readme, contributors)

Note that this has much better parallelism, as I'm creating different futures for each repository. What you where doing, Future(repositories.map(GitDriver.readme)) would do the map synchronously and return a single future.

Note that I don't have Scala in this device, so that code might not be perfect, but should give you an idea.

Upvotes: 2

izikovic
izikovic

Reputation: 133

At the moment you print the size of the contributors and readme the future is probably not yet completed so the contributors.future.value returns None.

You could try something like this:

val repositoriesF = Future {
  GitDriver.repositoriesOf(request.user)
}

val contributorsAndReadmeFuture = for {
  repositories <- repositoriesF
  contributors <- Future(repositories.map(GitDriver.contributors))
  readme <- Future(repositories.map(GitDriver.readme))
} yield (contributors, readme)

contributorsAndReadmeFuture onSuccess {
  case (constributors, readme) => 
    println(constributors.size)
    println(readme.size)
}

Upvotes: 0

Related Questions