Nested futures in Play for Scala does not compile - scala

This Action.async method in Play for Scala should execute the second future only if the first future returns 1, that's why they are nested. If the first future returns a result different than 1 then the second future should never be executed. But I'm getting the following compilation error in f2.map. Why is this error and how to fix it?
type mismatch; found :
scala.concurrent.Future[scala.concurrent.Future[play.api.mvc.Result]]
required: play.api.mvc.Result
def index = Action.async { request =>
val f1 = Future {1}
f1.map {
access => if (access==1) {
val f2 = Future {2}
f2.map { // <-- compilation error
result => {
val json = JsObject(Seq(
"acc" -> JsNumber(access),
"ret" -> JsString("0")
))
Ok(json)
}
}
}
else {
val json = JsObject(Seq(
"acc" -> JsNumber(0),
"ret" -> JsString("0")
))
Future.successful(Ok(json))
}
}
}

You're basically there - just flatMap on f1 instead of map since you're creating another future.

Related

Play framework - make form validation async - compilation error

Trying to use map (line 6) instead of using Await (lines 3-4) results in compilation saying Cannot resolve overloaded method 'Action' marking the first line.
I think the problem is in the last line of the function using fold which now has another value type returned from errorFunction.
can someone help understanding whats happening and how to make create user fully async?
def createUser = Action { implicit request: MessagesRequest[AnyContent] =>
val errorFunction = { formWithErrors: Form[Data] =>
val readAllResponse = Await.result(usersDao.readAll(), Duration.Inf)
BadRequest(views.html.listUsers(readAllResponse, formWithErrors, postUrl))
// TODO - try to make it async.
// usersDao.readAll().map(allUsersList => BadRequest(views.html.listUsers(allUsersList, formWithErrors, postUrl)))
}
val successFunction = { data: Data =>
val user = User(id = data.id, firstName = data.firstName, lastName = data.lastName)
// call to DAO
val createUsers: Future[User] = usersDao.create(user)
Redirect(routes.UserController.listUsers()).flashing("Info" -> "user added!")
}
val formValidationResult = form.bindFromRequest
formValidationResult.fold(errorFunction, successFunction)
}
code for readAll():
def readAll()(implicit transaction: Transaction[User]): Future[List[User]] = Future {
println(s"Reading all users...")
Thread.sleep(500)
transaction.modelObjectsList.toList
}
Error (when uncommenting line 6):
Compilation error[type mismatch;
found : play.api.data.Form[controllers.UserForm.Data] => scala.concurrent.Future[play.api.mvc.Result]
required: play.api.data.Form[controllers.UserForm.Data] => play.api.mvc.Result]
That fails in compilation because you are using the sync Action, and pass a Future. For that line to work you need to change your Action to an async Action, and it should be as simple as this:
Action.async {/*your code here*/}

ReactiveMongo conditional update

I am confused on how one would conditionally update a document based on a previous query using only futures.
Let say I want to push to some value into an array in a document only if that array has a size less than a given Integer.
I am using this function to get the document, after getting the document I am pushing values - what I am unable to do is do that conditionally.
def joinGroup(actionRequest: GroupRequests.GroupActionRequest): Future[GroupResponse.GroupActionCompleted] = {
//groupisNotFull() is a boolean future
groupIsNotFull(actionRequest.groupId).map(
shouldUpdate => {
if(shouldUpdate){
Logger.info(actionRequest.initiator + " Joining Group: " + actionRequest.groupId)
val selector = BSONDocument("group.groupid" -> BSONDocument("$eq" -> actionRequest.groupId))
val modifier = BSONDocument("$push" -> BSONDocument("group.users" -> "test-user"))
val updateResult = activeGroups.flatMap(_.update(selector, modifier))
.map(res => {
GroupActionCompleted(
actionRequest.groupId,
actionRequest.initiator,
GroupConstants.Actions.JOIN,
res.ok,
GroupConstants.Messages.JOIN_SUCCESS
)
})
.recover {
case e: Throwable => GroupActionCompleted(
actionRequest.groupId,
actionRequest.initiator, GroupConstants.Actions.JOIN,
success = false,
GroupConstants.Messages.JOIN_FAIL
)
}
updateResult
}
else {
val updateResult = Future.successful(
GroupActionCompleted(
actionRequest.groupId,
actionRequest.initiator,
GroupConstants.Actions.JOIN,
success = false,
GroupConstants.Messages.JOIN_FAIL
))
updateResult
}
}
)
}
//returns a Future[Boolean] based on if there is room for another user.
private def groupIsNotFull(groupid: String): Future[Boolean] = {
findGroupByGroupId(groupid)
.map(group => {
if (group.isDefined) {
val fGroup = group.get
fGroup.group.users.size < fGroup.group.groupInformation.maxUsers
} else {
false
}
})
}
I am confused on why I cannot do this. The compilation error is:
Error:type mismatch;
found : scala.concurrent.Future[response.group.GroupResponse.GroupActionCompleted]
required: response.group.GroupResponse.GroupActionCompleted
for both the if and else branch 'updateResult'.
As a side question.. is this the proper way of updating documents conditionally - that is querying for it, doing some logic then executing another query?
Ok got it - you need to flatMap the first Future[Boolean] like this:
groupIsNotFull(actionRequest.groupId).flatMap( ...
Using flatMap, the result will be a Future[T] with map you would get a Future[Future[T]]. The compiler knows you want to return a Future[T] so its expecting the map to return a T and you are trying to return a Future[T] - so it throws the error. Using flatMap will fix this.
Some further clarity on map vs flatmap here: In Scala Akka futures, what is the difference between map and flatMap?
I believe the problem is because the joinGroup2 function return type is Future[Response], yet you are returning just a Response in the else block. If you look at the signature of the mapTo[T] function, it returns a Future[T].
I think you need to wrap the Response object in a Future. Something like this:
else {
Future { Response(false, ERROR_REASON) }
}
Btw you have a typo: Respose -> Response

Async Action and Future

I'm using Play 2.5 and Scala 2.11. I'm trying to use async Action with Future to return json string but can't get it to work.
Here is my action :
def getData = Action.async { implicit request =>
val futureData = Future { daoObj.fetchData }
futureData onComplete {
case Success(data) =>
Ok(Json.prettyPrint(Json.obj("data" -> data.groupBy(_.name))))
case Failure(t) =>
BadRequest(Json.prettyPrint(Json.obj(
"status" -> "400",
"message" -> s"$t"
)))
}
}
The daoObj.fetchData returns scala.concurrent.Future[List[models.Data]]
Here is the models.Data :
case class Data(name: String, details: String)
I can have data like this
Data (name = "data1", details = "details1")
Data (name = "data1", details = "details1a")
Data (name = "data2", details = "details2")
that I can join on name to return a structure of the form
Map(data1 -> List("details1", "details11"),
data2 -> List("details2"))
I have a compilation error on groupBy :
value groupBy is not a member of scala.concurrent.Future[List[models.Data]]
1) How to get the value (List[models.Data]) from the Future in my action ?
2) It's my first Play Scala app so if you have any comment to improve my code, it's welcome.
You should not use Future.onComplete, which is a callback (with side-effect), but .map and .recover to turn your Future[T] into a Future[Result] which is what's expected by Action.async.
def getData = Action.async { implicit request =>
Future { daoObj.fetchData }.map { data =>
Json.obj("data" -> data.groupBy(_.name))
}.recover {
case error => BadRequest(Json.obj(
"status" -> "400",
"message" -> error.getMessage
))
}
}
There is no need to pretty-print the JsObject which can be directly written as result (except for debugging purposes maybe).

Extracting and accumulate result from multiple HTTP request

I met a problem that I cannot solve these days. I'm doing multiple http requests and in each response, it should have a Array[DTAnnotation]. I want to accumulate all the resulting list into one (this is not the problem here). My problem is that I cannot return the results from a WSResponse. What I try :
import mymodel.{DTFeatures, DTResponse, DTRequest, DTAnnotations}
def checkForSpike
(
awsKey : String,
collection : JSONCollection,
record : Record,
features : Array[DTFeatures]
) : Unit = {
val url = config.getString("url").getOrElse
val holder = WS.url(url)
val complexHolder =
holder.withHeaders(("Content-Type","application/json"))
// excepting result is List[Array[DTAnnotations]]
val test : List[Array[DTAnnotations]] =
for(feature <- features) yield {
val dtFeature = Json.stringify(Json.toJson(DTRequest(feature)))
val futureResponse = complexHolder.post(dtFeature)
Logger.info("Make the HTTP POST Request in " + (t1 - t0) + " msecs")
futureResponse.map { response =>
Logger.info("Get response in " + (System.currentTimeMillis - t1))
if(response.status == 200) {
response.json.validate[DTResponse].map { dtResponse =>
// I want to return this
dtResponse.annotations
}.recoverTotal { _ =>
Logger.debug("invalid json")
}
} else {
Logger.debug(Json.prettyPrint(Json.obj("status" -> response.status, "body" -> response.body)))
}
}
Await.result(futureResponse, 10.seconds)
}
}
Because the response is a Future, I try to add a Await to get the annotations but I have one error at the typing phase :
[error] found : Array[play.api.libs.ws.WSResponse]
[error] required: List[Array[DTAnnotation]]
How I can fix this ? Thank you !
There are a number of errors that avoid this to work. I add a version that compiles with your expected type and if you have questions I will answer in the comments.
def checkForSpike
(
awsKey: String,
collection: JSONCollection,
record: Record,
features: Array[DTFeatures]
): Unit = {
val url = config.getString("url").getOrElse
val holder = WS.url(url)
val complexHolder =
holder.withHeaders(("Content-Type", "application/json"))
// excepting result is List[Array[DTAnnotations]]
val test: List[Array[DTAnnotations]] =
for (feature <- features.toList) yield {
val dtFeature = Json.stringify(Json.toJson(DTRequest(feature)))
val futureResponse = complexHolder.post(dtFeature)
val futureAnnotations: Future[Array[DTAnnotations]] = futureResponse.map { response =>
if (response.status == 200) {
response.json.validate[DTResponse].map { dtResponse =>
// I want to return this
dtResponse.annotations
}.recoverTotal { _ =>
Logger.debug("invalid json")
??? // An Array response should be expected, maybe empty
}
} else {
Logger.debug(Json.prettyPrint(Json.obj("status" -> response.status, "body" -> response.body)))
??? // An Array response should be expected, maybe empty
}
}
Await.result(futureAnnotations, 10.seconds)
}
}
Issues:
Features must be converted to List if you expect a list to be
returned by the for comprehension
The map on future response returns
another future and this value should be used in the Await
To make sure the type of futureAnnotations is correct in all the branches the type should be valid

scala, play, futures: combining results from multiple futures

I am using:
Scala 2.10
Play 2.1
Currently, I am using the Future class from scala.concurrent._, but I'm open to trying another API.
I am having trouble combining the results of multiple futures into a single List[(String, String)].
The following Controller method successfully returns the results of a single Future to an HTML template:
def test = Action { implicit request =>
queryForm.bindFromRequest.fold(
formWithErrors => Ok("Error!"),
query => {
Async {
getSearchResponse(query, 0).map { response =>
Ok(views.html.form(queryForm,
getAuthors(response.body, List[(String, String)]())))
}
}
})
}
The method getSearchResult(String, Int) performs a web service API call and returns a Future[play.api.libs.ws.Response]. The method getAuthors(String, List[(String, String)]) returns a List[(String, String)] to the HTML template.
Now, I am trying to call getSearchResult(String, Int) in a for loop to get several Response bodies. The following should give an idea of what I'm trying to do, but I get a compile-time error:
def test = Action { implicit request =>
queryForm.bindFromRequest.fold(
formWithErrors => Ok("Error!"),
query => {
Async {
val authors = for (i <- 0 to 100; if i % 10 == 0) yield {
getSearchResponse(query, i)
}.map { response =>
getAuthors(response.body, List[(String, String)]())
}
Ok(views.html.form(queryForm, authors))
}
})
}
type mismatch; found : scala.collection.immutable.IndexedSeq[scala.concurrent.Future[List[(String, String)]]] required: List[(String, String)]
How can I map the responses of several Future objects to a single Result?
Create a Future parametrized by a List or other Collection of the Result type.
From here:
In Play 1 you can do this:
F.Promise<List<WS.HttpResponse>> promises = F.Promise.waitAll(remoteCall1, remoteCall2, remoteCall3);
// where remoteCall1..3 are promises
List<WS.HttpResponse> httpResponses = await(promises); // request gets suspended here
In Play 2 less direct:
val httpResponses = for {
result1 <- remoteCall1
result2 <- remoteCall2
} yield List(result1, result2)