Search code examples
scalafutureakka-httpspray-client

Why is my map() not working?


I have a Akka HTTP service which returns a string, as shown below:

val route1: Route = {
    path("hello") {
      get{
        complete{
          println("Inside r1")
          "You just accessed hello"
        }
      }
   }
}

I am trying to understand the difference between map and flatMap

For example, the below code gives me the result as expected:

val future1: Future[String] = 
  Http()
   .singleRequest(
      HttpRequest(method = HttpMethods.GET,
                  uri = "http://localhost:8187/hello"))
   .flatMap(testFlatFunc)

def testFlatFunc(x: HttpResponse): Future[String] = {
  Unmarshal(x).to[String]
}

But, if I try to replace it with a map, as below I get the output as FulfilledFuture(You just accessed hello)

val future1: Future[String] = Http()
    .singleRequest(
      HttpRequest(method = HttpMethods.GET,
                  uri = "http://localhost:8187/hello"))
    .map(testFunc)

def testFunc(x: HttpResponse): String={
  Unmarshal(x).to[String].toString
}

Why is my map() not working as intended?


Solution

  • It's about what you do in testFunc:

    def testFunc(x: HttpResponse): String = {
      Unmarshal(x).to[String].toString
    }
    

    here types are ok, but what you do inside is not.

    Unmarshal(x).to[String]
    

    returns Future[String] - it means it is asynchronous result, and its value will appear there in time. It is ok, to use such result type in flatMap.

    val sqrts: Double => List[Double] = x => List(Math.sqrt(x), -Math.sqrt(x))
    val numbers = List(4.0, 9.0, 16.0)
    numbers.flatMap(sqrts) // List(2.0, -2.0, 3.0, -3.0, 4.0, -4.0)
    // results equal to
    numbers
      .map(sqrts) // List(List(2.0, -2.0), List(3.0, -3.0), List(4.0, -4.0))
      .flatten    // List(2.0, -2.0, 3.0, -3.0, 4.0, -4.0)
    

    Here you can see that flatMap works like map + flatten (except that some containers don't even have to have flatten implemented, e.g. Future ;) ).

    But why your testFunc fails? Basically you take the asynchronous result (Future[String]) and you don't wait for the result - you call toString instead which only prints some useless information (FulfilledFuture(You just accessed hello)) but not the result itself.

    To work around this you would have to do something like:

    def testFunc(x: HttpResponse): String = {
      Await.result(Unmarshal(x).to[String], 10.seconds)
    }
    

    this would block and wait at most for 10 seconds for the result to make it synchronous. (That kind of beats the purpose in this case and map would be more useful if the result you calculate there was synchronous in the first place.)