In my backend controller function I have the following code:
def getContentComponents = Action.async {
contentComponentDTO.list().map { contentComponentsFuture =>
contentComponentsFuture.foreach(contentComponentFuture =>
contentComponentFuture.typeOf match {
case 5 => contentComponentDTO.getContentComponentText(contentComponentFuture.id.get).map(text => {
contentComponentFuture.text = text.text
println(contentComponentFuture.text)
})
}
)
Ok(Json.toJson(contentComponentsFuture))
}
}
The problem is, that OK() is called before the stuff above is finished. Is there a smart way to wait until the foreach is finished?
thanks
That have been two differen questions with different problems! So thats the reason for two questions looking similar
There are two approaches that you can take, but before going to that let's recap what you are trying to do.
You have a list of items contentComponentsFuture, I am assuming retrieving from database, which is why you are getting a future.
Now your contentComponentsFuture is a mutable variable (which I strongly suggest not to use, stick to immutable data) has a text field which you need to update.
Now all that code block before the Ok() will return a future as it is working on a future list. So the easiest solution is to do a map over the future and return the result. The map over a future is just a onComplete function which triggers once the future is resolved. So the code will look like:
def getContentComponents = Action.async {
val futureResult = contentComponentDTO.list().map { contentComponentsFuture =>
contentComponentsFuture.map(contentComponentFuture =>
contentComponentFuture.typeOf match {
case 5 => contentComponentDTO.getContentComponentText(contentComponentFuture.id.get).map(text => {
contentComponentFuture.text = text.text
contentComponentFuture
})
}
)
}
futureResult.map(result => {
Future.sequence(result).map(t => Ok(Json.toJson(t))
}))
}
The other option will be to use scala asycn library: https://github.com/scala/scala-async
which gives a handy wrapper so that you do not need to map over future explicitly, the same code above with scala async library will look like below:
def getContentComponents = Action.async {
Async.async {
val result = Async.await(contentComponentDTO.list().map { contentComponentsFuture =>
contentComponentsFuture.map(contentComponentFuture =>
contentComponentFuture.typeOf match {
case 5 => contentComponentDTO.getContentComponentText(contentComponentFuture.id.get).map(text => {
contentComponentFuture.text = text.text
contentComponentFuture
})
}
)
})
Ok(Json.toJson(result))
}
}
Related
I have a method that returns a Future[Boolean] in a Play controller and i want to evaluate that using async but i can't seem to get it to compile.
The following will work:
def health = Action {
logger.info("Endpoint method: health")
val isHealthy = healthCheckService.checkDynamo()
val b: Boolean = Await.result(isHealthy, scala.concurrent.duration.Duration(5, "seconds"))
Ok(Json.toJson(HealthCheckResponse(b.toString)))
}
But i don't think i want that Await in there. So i'm trying things like this with no success:
def health =
Action.async {
Future {
logger.info("Endpoint method: health")
healthCheckService.checkDynamo() match {
case Future.successful(true) => Ok(Json.toJson("false"))
case false => Ok(Json.toJson("true"))
}
val r = healthCheckService.checkDynamo() match {
case true => Ok(Json.toJson("false"))
case false => Ok(Json.toJson("true"))
}
}
}
I can't even get those to compile to test them out.
Any suggestions?
Try this:
def health = Action.async {
healthCheckService.checkDynamo().map {
case true => Ok(Json.toJson("false"))
case false => Ok(Json.toJson("true"))
}
}
Let Play handle the awaiting for you under the hood. That is, Action.async accepts a Future, which checkDynamo() already returns. All you have to do is map it to the appropriate result.
With Futures you have to use combinators like map and flatMap to express the final value. For example:
Action.async {
healthCheckService.checkDynamo()
.map { result => // boolean
HealthCheckResponse(result.toString)
}
.map(Json.toJson(_))
.map(Ok(_))
}
(You can merge maps above to one map and construct the final Ok value there; it is more or less a matter of taste)
If you have, say, two async calls which you want to execute and return a result based on their results, you can use flatMap, which could be easily expressed using a for comprehension:
Action.async {
for {
result1 <- someService.someCall()
result2 <- anotherService.anotherCall(result1.someProperty)
finalResult = SomeFinalResultType(result1, result2)
} yield Ok(Json.toJson(finalResult))
}
If you are not familiar with futures, you might want to read some tutorial which explains their nature, how to combine them and how to get useful results from them, like this one: http://hello-scala.com/920-scala-futures.html
I have actually a problem while returning a Seq back to frontend.
My code looks like this:
def getContentComponentsForProcessSteps: Action[AnyContent] = Action.async { implicit request =>
println("-----------------------------------------------New Request--------------------------------------------------------")
println(request.body.asJson)
request.body.asJson.map(_.validate[ProcessStepIds] match {
case JsSuccess(steps, _) =>
val contentComponents: Seq[Future[Seq[Future[ContentComponentModel]]]] = steps.steps.map(stepId => { //foreach
// Fetching all ContentComponent Relations
contentComponentDTO.getContentComponentsByStepId(stepId).map(contentComponents => { // Future[Seq[ContentComponent_ProcessStepTemplateModel]]
// Iteration über die Gefundenen Relations
contentComponents.map(contentComponent => { // foreach
// Fetch Content Component
contentComponentDTO.getContentComponentById(contentComponent.contentComponent_id).flatMap(contentComponent => { // Future[Option[ContentComponentModel]]
// Fetch Content Component Data for the types
val res = getContentComponentDataforOneContentComponent(contentComponent.get)
res.map(con => con)
})
})
})
})
Future.sequence(contentComponents).map(eins => {
println(eins)
Ok(Json.obj("Content Components Return" -> "true", "result" -> eins))
})
case JsError(_) =>
Future.successful {
BadRequest("Can't fetch Content Components")
}
case _ => Future.successful {
BadRequest("Can't fetch Content Components")
}
}).getOrElse(Future.successful {
BadRequest("Can't fetch Content Components")
})
}
Error is the following.
Thanks for any hint
Look at the type of eins your error message is telling you that it is a Seq[Seq[Future[ContenetComponentModel]]] and not simply a Seq like you thought.
There are two problems with this:
You can't write a Future (or in your case, a sequence of futures) to Json.
You need to have an implicit function in scope to convert your ContenetComponentModel to a JSON value.
Depending on what you want your result to look like, you could try flattening eins and then using another Future.sequence, but I think what you really should be doing is changing a lot of your .map calls to .flatMap calls to avoid the nesting in the first place.
I need to write simple web service with akka-http and reactivemongo.
Function to save data looks like this
def saveRoute(route: Route):Future[WriteResult] = {
collection.insert(route)
}
a code that calls this function looks like this
val userRoutes = {
logRequestResult("akka-http-microservice") {
path("routes") {
(post & entity(as[Route])) { route =>
Database.saveRoute(route)
}
}
}
}
I need to return result with inserted ID of Route and do this without making the thread to wait.
if try
Database.saveRoute(route).onComplete{
case Success(r) => complete(r.toString)
case Failure(e) => complete(e.getMessage)
}
It cannot compile, because it doesn't return value.
I know how to make it in dirty way, but really want to make in appropriate manner.
What should be done in this case?
Seems like I've found most efficient way to do this. It's built in onComplete directive
(path("routes" / "add") & post & entity(as[Route])) {
route =>
onComplete(routesController.addRoute(route)) {
case Success(result) => complete(StatusCodes.Created, "OK")
case Failure(ex) => complete(new ErrorResponse(StatusCodes.InternalServerError.intValue, ErrorResponse.ERROR, ex.getMessage))
}
}
Use onSuccess to handle the valid response when the future finishes and handleExceptions to handle when the future does not succeed.
val userRoutes = {
handleExceptions(mongoDbExceptionHandler) {
logRequestResult("akka-http-microservice") {
path("routes") {
(post & entity(as[Route])) { route =>
onSuccess(Database.saveRoute(route)) { result =>
complete(result)
}
}
}
}
}
}
// Something like this for whatever the exceptions you expect are
val mongoDbExceptionHandler = ExceptionHandler {
case ex: MongoDbReadException => complete(HttpResponse(InternalServerError, "No database")))
}
onSuccess:
http://doc.akka.io/docs/akka/2.4.9/scala/http/routing-dsl/directives/future-directives/onSuccess.html
handleExceptions:
http://doc.akka.io/docs/akka/2.4.9/scala/http/routing-dsl/exception-handling.html
You can map over the future and then complete the request like below.
val future = Database.saveRoute(route)
val response = future.map(_.getId).recover(_.getMessage)
complete(response)
On a side note, for handling exceptions, it is a good practice to have a ExceptionHandler and wrap it with your route. You can find example here.
You have few option i will try to put the most commonly used ones for REST API based solutions:
OnSuccess use it when you want your expectations to be bubbled and handled by expectionHandler
concat(
path("success") {
onSuccess(Future { "Ok" }) { extraction =>
complete(extraction)
}
},
path("failure") {
onSuccess(Future.failed[String](TestException)) { extraction =>
complete(extraction)
}
}
)
https://doc.akka.io/docs/akka-http/current/routing-dsl/directives/future-directives/onSuccess.html
onComplete: When you want to manually handle the exception. Try Monad wrapped.
val route =
path("divide" / IntNumber / IntNumber) { (a, b) =>
onComplete(divide(a, b)) {
case Success(value) => complete(s"The result was $value")
case Failure(ex) => complete((InternalServerError, s"An error occurred: ${ex.getMessage}"))
}
}
https://doc.akka.io/docs/akka-http/current/routing-dsl/directives/future-directives/onComplete.html
How about this, replace:
Database.saveRoute(route)
with:
complete(Database.saveRoute(route).map(_.toString).recover(_.getMessage))
When you use RequestContext you should use something like this:
import akka.http.scaladsl.server.Directives._
import akka.http.scaladsl.server.RouteResult.{Complete, Rejected}
...
val myRoute: Route = (path("my-path") & get) { req: RequestContext =>
val futureResp: Future[HttpResponse] = ???
futureResp.map(resp => RouteResult.Complete(resp))
}
I'm new to asynchronous programming. I read this tutorial http://danielwestheide.com/blog/2013/01/09/the-neophytes-guide-to-scala-part-8-welcome-to-the-future.html and was surprised by how effortless I can incorporate Future into the program. However, when I was using Future with Routing, the return type is kind of wrong.
get {
optionalCookie("commToken") {
case Some(commCookie) =>
val response = (MTurkerProgressActor ? Register).mapTo[..].map({...})
val result = Await.result(response, 5 seconds)
setCookie(HttpCookie("commToken", content = result._2.mturker.get.commToken)) {
complete(result._1, result._2.mturker.get)
}
case None => // ...
}
}
I really don't want to use Await (what's the point of asynchronous if I just block the thread and wait for 5 seconds?). I tried to use for-comprehension or flatMap and place the setCookie and complete actions inside, but the return type is unacceptable to Spray. For-comprehension returns "Unit", and flatMap returns a Future.
Since I need to set up this cookie, I need the data inside. Is Await the solution? Or is there a smatter way?
You can use the onSuccess directive:
get {
optionalCookie("commToken") { cookie =>
//....
val response = (MTurkerProgressActor ? Register).mapTo[..].map({...})
onSuccess(response) {
case (result, mTurkerResponse) =>
setCookie(HttpCookie("commToken", content = mTurkerResponse.mturker.get.commToken)) {
complete(result, mturkerResponse.mturker.get)
}
}
}
There's also onFailure and onComplete (for which you have to match on Success and Failure) See http://spray.io/documentation/1.2.1/spray-routing/future-directives/onComplete/
Also, instead of using get directly it's much more idiomatic to use map (I assume the mturker is an Option or something similar):
case (result, mTurkerResponse) =>
mTurkerResponse.mturker.map { mt =>
setCookie(HttpCookie("commToken", content = mt.commToken)) {
complete(result, mt)
}
}
You can also make a custom directive using this code -
case class ExceptionRejection(ex: Throwable) extends Rejection
protected def futureDirective[T](x: Future[T],
exceptionHandler: (Throwable) => Rejection = ExceptionRejection(_)) =
new Directive1[T] {
override def happly(f: (::[T, HNil]) => Route): Route = { ctx =>
x
.map(t => f(t :: HNil)(ctx))
.onFailure { case ex: Exception =>
ctx.reject(exceptionHandler(ex))
}
}
}
Example usage -
protected def getLogin(account: Account) = futureDirective(
logins.findById(account.id)
)
getAccount(access_token) { account =>
getLogin(account) { login =>
// ...
}
}
I want to take a function that returns a Future[Option[String]] and use that in conjunction with spray routing's onComplete directive. But no matter what I do, I can't seem to get it work.
Let's say that I have the following function:
def expensiveOperation: Future[Option[String]] = { ... do stuff ... }
And then I want to define a portion of my Route as such:
onComplete(expensiveOperation) {
case Success(string) => complete(string)
case Failure(_) => complete("failure")
}
Is there a way to do this without writing a separate function to transform the Future[Option[String]] into a basic Future[String]?
onComplete(expensiveOperation) {
case Success(Some(string)) => complete(string)
case _ => complete("failure")
}
or:
onComplete(expensiveOperation.map(_.get)) {
case Success(string) => complete(string)
case Failure(_) => complete("failure")
}
A Late Answer. Found this to be working.
post{
entity(as[Project]) { project =>
complete {
(projectActor ? Update(project)).mapTo[Project]
}
}
}
Hope this Fixes the issue
https://groups.google.com/forum/#!topic/spray-user/FM6mF6JXuNM