播放Scala:在不使用Await.result的情况下调用递归Web服务

时间:2015-02-28 02:21:15

标签: web-services scala recursion playframework-2.2

我使用Play! Scala 2.2并且我需要进行递归Web服务调用(直到我得到所有结果)。 目前我设法使用Await.result进行以下操作:

def getTitleSetFromJson( jsValue: JsValue ): Set[ String ] = {
  val titleReads: Reads[Option[String]] = (__ \\ "title").readNullable[String]
  (jsValue \ "response" \ "songs")
    .asOpt[Set[Option[String]]](Reads.set(titleReads))
    .getOrElse(Set.empty)
    .flatten    
}

def findEchonestSongs(start: Long, echonestId: String): Future[Set[String]] = {
  val titleReads: Reads[Option[String]] = (__ \\ "title").readNullable[String]
  val endpoint = s"$baseUrl/artist/songs"
  val assembledUrl = s"$endpoint?api_key=$echonestApiKey&id=$artistId&format=json&start=$start&results=20"

  WS.url(assembledUrl).get().map { songs =>
    if ((songs.json \ "response" \ "total").asOpt[Int].getOrElse(0) > start + 20) {
      getTitleSetFromJson(songs.json) ++
        Await.result( findEchonestSongs(start + 20, echonestId), 3.seconds )
    } else {
      getTitleSetFromJson(songs.json) 
    }
  }
}

现在我想做同样的事情,但是使用非阻塞方式,即不使用Await.result但是我尝试的所有内容都有错误:由于嵌套的未来,类型不匹配。

你能告诉我这样做的方法吗?

2 个答案:

答案 0 :(得分:1)

有许多不同的方法可以满足您的要求。但也许最简单的方法是使用flatMap

这是你的功能的改进版本(为了使它更简单,我冒昧地让它返回Future[Set[JsValue]])

import play.api.libs.json.JsValue
import play.api.libs.ws.WS
import scala.concurrent.Future
import play.api.Play.current
import scala.concurrent.ExecutionContext.Implicits.global

object Test {

  val apiKey = "//YOUR_ECHONEST_API_KEY//"
  val baseUrl = "http://developer.echonest.com/api/v4"

  def findSongs(start: Long, artistId: String): Future[Set[JsValue]] = {

    def endpoint = s"$baseUrl/artist/songs"
    def assembledUrl = s"$endpoint?api_key=$apiKey&id=$artistId&format=json&start=$start&results=20"
    def response = WS.url(assembledUrl).get()
    def futureJson = response map (_.json)
    futureJson flatMap { result =>
      def total = (result \ "response" \ "total").asOpt[Int]
      def songs = (result \ "response" \ "songs").as[Set[JsValue]]
      total exists (_ > start + 20) match {
        case false => Future.successful(songs)
        case true => findSongs(start + 20, artistId) map (songs ++ _)
      }
    }

  }

}

处理结果

如果您真的想要Set[String],可以在结果上轻松使用map

val sample = findSongs(0, "someone")
sample.map(_.map(_ \ "title").map(_.as[String]))

改进

这里有很大的改进空间。例如,每次递归调用函数时都不需要传递artistId。所以我们可以定义一个递归的内部函数。

使用累加器使尾部递归并不困难。

最后,使用Seq[Future]并使用fold代替递归可能是有意义的,这也是函数式编程中的常见模式。但Stream api已经很好地提供了所有这些东西。您可以参考Stream API了解更多信息。

答案 1 :(得分:0)

您可以将其他(来自嵌套调用)的结果映射到当前Set。

另外......一件非常重要的事情。 Readability Counts ...特别是在StackOverFLow上。

def findEchonestSongs(start: Long, echonestId: String): Future[Set[String]] = {

  val titleReads: Reads[Option[String]] = (__ \\ "title").readNullable[String]

  val requestUrl = "http://developer.echonest.com/api/v4/artist/songs?api_key=" + echonestApiKey + "&id=" + echonestId +
  "&format=json&start=" + start + "&results=20"

  WS.url( requestUrl ).get().flatMap { songs =>

    var nTotal = (songs.json \ "response" \ "total").asOpt[Int].getOrElse(0)

    if( nTotal > start + 20 ) {

      val titleSet = getTitleSetFromJson( songs.json )

      val moreTitleSetFuture = findEchonestSongs( start + 20, echonestId )

      moreTitleSetFuture.map( moreTitleSet =>  titleSet ++ moreTitleSet )

    }  
    else {
      Future.successful( getTitleSetFromJson( songs.json ) )
    }
  }
}

def getTitleSetFromJson( jsValue: JsValue ): Set[ String ] = {
   (songs.json \ "response" \ "songs")
    .asOpt[Set[Option[String]]](Reads.set(titleReads))
    .getOrElse(Set.empty)
    .flatten
}