Search code examples
scalaplayframeworkplayframework-2.0enumeratorloops

Best way to make a periodic WS call to feed an Enumerator with Play2/Scala?


I use the Enumerator pattern to retrieve some tweets every second with WS.url

Enumerator.fromCallback[String](() => 
        Promise.timeout(WS.url("http://search.twitter.com/search.json?q="+query+"&rpp=1").get().map { response =>
            val tweets = response.json \\ "text"
            tweets match {
                case Nil => "Nothing found!"
                case head :: tail => query + " : " + head.as[String]
            }
        }.map(Some.apply).value.get, 1000 milliseconds)
  )

My is problem is that

Enumerator.fromCallback[String]() 

is waiting for a

Promise[Option[String]]

As WS.url(...).get returns a Promise, and as I use Promise.timeout to re-launch the call every second,

I have a

Promise[Promise[Option[String]]] 

So I have to use value.get to have the good type, so it does not seems very clean for the asynchronous aspect.

This code works but my question is : Is there a better way, more elegant, to achieve this? Can I get easily a Promise from another Promise and a Promise.timeout?

Thanks :)


Solution

  • Promise is a monad, and in general when you find yourself with a nested monad you want to stick a flatMap in there somewhere. In your case something like this should work:

    import akka.util.duration._
    import play.api.libs.concurrent._
    import play.api.libs.iteratee._
    import play.api.libs.ws._
    
    val query = "test"
    val url = WS.url("http://search.twitter.com/search.json?q=" + query + "&rpp=1")
    
    val tweets = Enumerator.fromCallback[String](() => 
      Promise.timeout(url.get, 1000 milliseconds).flatMap(_.map { response =>
        (response.json \\ "text") match {
          case Nil => "Nothing found!"
          case head :: _ => query + " : " + head.as[String]
        }
      }.map(Some.apply))
    )
    

    I'd personally write it more like this:

    val tweets = Enumerator.fromCallback[String](() => 
      Promise.timeout(url.get, 1000 milliseconds).flatMap(_.map(
        _.json.\\("text").headOption.map(query + " " + _.as[String])
      ))
    )
    

    And not fuss with the "Nothing found!" message, but depending on what exactly you're doing that might not be appropriate.