Play Scala 2.4 linking asynchronous calls

885 Views Asked by At

I'm trying to figure out how to link multiple asynchronous calls and return a result. I am currently trying to asynchronously user data first, and update user data asynchronously and return result, but it seems like it is not working :( i used map { result => Ok(result)}, but play still thinks that I am returning an object. any help?

def updateUserData() = Action.async { implicit request =>
updateUserForm.bindFromRequest.fold(
errors => Future.successful(BadRequest(views.html.authenticated.settings.settings_hero(errors, Option(""), Option("")))),
{
  case (userData) =>
    request.session.get("email") match {
      case Some(email) =>
        getUser(email, userData.curent_password) map { userCheck =>
          if (userCheck) {
            updateUserOnService(email, userData.f_name, userData.l_name, userData.new_password) map { result =>
              Ok("please")
            }
            //val e = updateUserOnService(email, userData.f_name, userData.l_name, userData.new_password) map {result => Ok("")}

            // user is valid now update the user data

            // call removeAuth to log out

            // redirect to home
            ///Ok (updateUserOnService(email, userData.f_name, userData.l_name, userData.new_password) map { result => result})
            //Redirect(routes.settings.index()).addingToSession("email" -> email)
          } else {
            BadRequest(views.html.authenticated.settings.settings_hero(updateUserForm.bindFromRequest.withGlobalError(Messages("error.login", email)), Option(""), Option("")))
          }
        }
      }
  })
}

The main part that i am having issue is this part. I think it is matter of some syntax. Could someone help? Thanks

updateUserOnService(email, userData.f_name, userData.l_name, userData.new_password) map { result =>
  Ok("please")
}
1

There are 1 best solutions below

2
Akos Krivachy On BEST ANSWER

The issue is with your types and that they don't match up with the required ones.

.fold has to result in Future[Result] in both branches (the error and the success ones).

In the successful form bind branch you have this:

case (userData) => ... // The ... must evaluate to Future[Result]

Looking at your first operation we see:

request.session.get("email") match {
  case Some(email) => ...
}

One big issue here is that the None case is not handled! (but this is does not cause the types not matching up). Having something like the following will solve this: case None => Future.successful(BadRequest(...))

So moving on: in the Some you have the following:

getUser(email, userData.curent_password) map { userCheck =>
  if (userCheck) {
    updateUserOnService(email, userData.f_name, userData.l_name, userData.new_password) map { result =>
       Ok("please")
    }
  } else {
    BadRequest(views.html.authenticated.settings.settings_hero(updateUserForm.bindFromRequest.withGlobalError(Messages("error.login", email)), Option(""), Option("")))
  }
}

This is where the issue is:

getUser will return with a Future[X] and when you map over it you will have Future[Y] where Y will be what userCheck => ... evaluates to.

In this case the types are totally mixed up, since when you do if(usercheck) on the true branch you have Future[Result] on the false branch you have Result. So the types don't align on both branches which is a big issue and the compiler will infer Any from this.

To fix this, in the false branch create a future: Future.successful(BadRequest(....))

Ok, now that we fixed the most inner type issues, let's start going backwards. Inside we have Future[Result], if we go back one level (before the getUser()) then we will have Future[Future[Result]]. Again this is not what we want, because we need Future[Result].

The solution to this is to flatMap instead of map, because with flatMap when you need to return with the same container type and it flattens it. A quick example to understand this:

Seq(1, 2, 3).flatMap(i => Seq(i, i))
// res0: Seq[Int] = List(1, 1, 2, 2, 3, 3)

In the case of Futures:

import scala.concurrent.Future   
import scala.concurrent.ExecutionContext.Implicits.global
Future(1).flatMap(i => Future(i*2))
// res1: scala.concurrent.Future[Int] = [....]

So we see that we don't have double nesting, but just a single Future.

Going back to your example this would be my updated code that would work better:

def updateUserData() = Action.async { implicit request =>
  updateUserForm.bindFromRequest.fold(
    errors => Future.successful(BadRequest(views.html.authenticated.settings.settings_hero(errors, Option(""), Option("")))),
    {
      case (userData) =>
        request.session.get("email") match {
          case Some(email) =>
            getUser(email, userData.curent_password).flatMap { userCheck =>
              if (userCheck) {
                updateUserOnService(email, userData.f_name, userData.l_name, userData.new_password) map { result =>
                  Ok("please")
                }
              } else {
                Future.successful(BadRequest(views.html.authenticated.settings.settings_hero(updateUserForm.bindFromRequest.withGlobalError(Messages("error.login", email)), Option(""), Option(""))))
              }
            }
        case None => Future.successful(BadRequest) // FIXME: Implement as you wish
        }
    })
}