5

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?

ches
  • 6,382
  • 2
  • 35
  • 32
windweller
  • 2,365
  • 5
  • 33
  • 56
  • `map` and `flatmap` are usually the way to go. The `for`-comprehension only returns `Unit` if you use it without `yield`. When you use `yield` it is converted into a expression using `flatMap` and `map`. – jrudolph Jul 14 '14 at 07:48

2 Answers2

15

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)
    }
  }
Mario Camou
  • 2,303
  • 16
  • 28
  • 1
    Note: You'll need to be sure to have an ExecutionContext in scope or else you get a TypeError when trying to call onSuccess(...). – Toby Sullivan Nov 29 '15 at 05:52
  • Finally, it works! Strangely, it only works for me with `onSuccess`. When I try to add `onFailure`, it breaks. :/ – Brian Apr 13 '16 at 03:56
1

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 => 
    // ...
  }
}
ches
  • 6,382
  • 2
  • 35
  • 32
Abhijit
  • 93
  • 2