【问题标题】:Play Scala : Recursive web services call without using Await.resultPlay Scala:不使用 Await.result 的递归 Web 服务调用
【发布时间】:2015-02-28 02:21:14
【问题描述】:

我与 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,但我尝试的所有内容都出现错误:由于嵌套的期货,类型不匹配。

你能给我一个方法吗?

【问题讨论】:

  • 我已经尝试过了,但是我得到了一个我无法解决的错误:类型不匹配;找到:scala.concurrent.Future[Set[String]] 需要:scala.collection.GenTraversableOnce[?] at findEchonestSongs(start + 20, echonestId)

标签: web-services scala recursion playframework-2.2


【解决方案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了解更多信息。

【讨论】:

  • 如果您对改进部分感兴趣,请告诉我(例如,使用Stream 会更容易)。顺便说一句,整个事情有点奇怪。如果您想获取所有歌曲,为什么不增加限制?他们的 API 有什么限制吗?
  • 非常感谢您完美详细的回复!对于您的问题,是的,他们的 API 存在限制(最多 100 个结果,但对于我的测试,我将 100 更改为 20)。我也很感兴趣,很想看到您使用 Stream API 的改进。
  • 没问题 :) 将尝试用Streams 编写一个版本并更新答案。
【解决方案2】:

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

还有...一件非常重要的事情。 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
}

【讨论】:

  • 感谢您的回复,我完全同意您的评论“可读性很重要”,我将编辑我的答案并在未来更加小心。顺便说一句,您的回复不起作用,我收到编译错误:type mismatch; scala.concurrent.Future[scala.collection.immutable.Set[String]], required: Set[String]
  • 您还必须使用 Future.successful,如 Nader Hadji Ghanbari 的回答中所示。
  • 是的...在这种情况下,您将需要 Future.successful。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2013-02-02
  • 2021-02-06
  • 1970-01-01
  • 2016-10-14
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多