The problem is, once that you start talking in Futures, you need to keep talking in Futures, the idea is that the same server will handle all this waits and context changes, so play it self can handle your promise that at some piont, you will return a result.
So, instead of calling functions in your controllers that return an Any, play can handle Future[Any].
val weight: Future[String] = getInfo() map { response =>
if (response.toString().length > 0) (response \ "weight").as[String])
else throw new Exception("didn't get response")
}
If you have several future calls, where each one needs the result from other Future, you can use For comprehension to avoid a map hell. For example, here is a little more complex example:
def processSync(databaseServer: DatabaseServer, databaseIdentity: DatabaseIdentity): Future[String] = {
val info = for {
catalogs <- databaseSyncService.getCatalogs(databaseServer.address, databaseServer.port, databaseIdentity.login, databaseIdentity.password)
clubbers <- getClubbers(databaseServer.id)
ignoredCatalogs <- ignoredCatalogService.get(databaseServer.id)
} yield (catalogs, clubbers, ignoredCatalogs)
val result = info.map{
case(catalogs, clubbers, ignoredCatalogs) => {
val currentCatalogs = (clubbers.map(clubber =>
Seq(Some(clubber.mainSchema), clubber.archiveSchema, clubber.blacklistSchema, clubber.logsSchema).flatten
).flatten ++ ignoredCatalogs).toSet
val serverCatalogs = catalogs.toSet
if(currentCatalogs == serverCatalogs) {
"synchronized"
} else {
"outOfSync"
}
}
}.recover{
case sqlE: SQLServerException =>{
logger.error(s"Conection error with ${databaseServer.name}", sqlE)
"connectionError"
}
}
for{
realResult <- result
_ <- databaseServerRepo.updateSync(databaseServer.id, realResult)
} yield realResult
}
Each value in the for is a future, but bellow we can access their value, at the end of the for, you use yield to mark what need to be returned.
Other example could be this controller call (ignore the shilouete part, is an authentication library, simply think of the return type as a Future[Result]):
def sync(id: Int) = silhouette.SecuredAction.async { implicit request: SecuredRequest[DefaultEnv, AnyContent] =>
val actions = for {
(server, identity) <- databaseServerService.getWithIdentity(id)
databaseCatalogs <- databaseSyncService.getCatalogs(server.address, server.port, identity.login, identity.password)
ignoredCatalogs <- ignoredCatalogService.get(id)
clubberIntegrations <- clubberIntegrationService.getClubbersListOrAutoFill(id, databaseCatalogs, ignoredCatalogs.map(_.name))
} yield Future.successful {
val catalogs = databaseCatalogs.map { x =>
val ignoredCatalogNotes = ignoredCatalogs.filter(_.name == x).map(_.note).headOption
DatabaseCatalog(x, ignoredCatalogNotes.getOrElse(""), ignoredCatalogNotes.isDefined)
}
Ok(web.databaseserver.views.html.databaseServerSync(request.identity, server, DatabaseServerForSync(clubberIntegrations, catalogs)))
}
actions.recover {
case _: SQLServerException => {
databaseServerService.getName(id).map(x => {
Redirect(web.databaseserver.controllers.routes.DatabaseServerController.list())
.flashing("error" -> Messages("databaseSync.couldNotConnect", x))
})
}
}.flatMap(x => x)
}
Also, i forgot to mention, if you need to handle a function that uses a Future and other does not, you can transform the latter with Future.successful, there is also Future.sequence to convert a Seq[Future[Any]] into Future[Seq[Any]] and other functions to help you handle the futures in different ways.