1

我正在使用 Scala 向 API(确切地说是 Play Framework 的 WS)发出 HTTP GET 请求,该 API 以如下所示的 JSON 响应进行响应;

{
  data: [
    {text: "Hello there", id: 1},
    {text: "Hello there again", id: 2}
  ],
  next_url: 'http://request-this-for-more.com/api?page=2' //optional
}

因此,next_url返回的 JSON 中的字段可能存在也可能不存在。

我的方法需要做的是从调用第一个 URL 开始,检查响应是否有一个next_url,然后对其执行 GET。最后,我应该data将响应中的所有字段合并到所有数据字段的一个未来中。next_url当响应中不存在时,我终止。

现在,以阻塞方式执行此操作更容易,但我不想这样做。解决此类问题的最佳方法是什么?

4

2 回答 2

2

在 scalaz 的某个地方可能有一种方法可以做到这一点,但如果你不知道具体的解决方案,通常可以使用递归和flatMap. 就像是:

//Assume we have an async fetch method that returns Result and Option of next Url
def fetch(url: Url): Future[(Result, Option[Url])] = ...

//Then we can define fetchAll with recursion:
def fetchAll(url: Url): Future[Vector[Result]] =
  fetch(url) flatMap {
    case (result, None) => Future.successful(Vector(result))
    case (result, Some(nextUrl)) =>
      fetchAll(nextUrl) map {results => result +: results}
  }

(请注意,这对每个调用都使用了一个堆栈帧——如果你想进行数千次提取,那么我们需要更仔细地编写它,以便它是尾递归的)

于 2015-05-22T09:33:09.723 回答
1

Future.flatMap 方法在这种情况下完全存在

假设你有这样的东西:

case class Data(...)
def getContent(url:String):Future[String]
def parseJson(source:String):Try[JsValue]
def getData(value: JsValue):Seq[Data]

和type 有受play json 库JsValue启发的方法

def \ (fieldName: String): JsValue
def as[T](implicit ...):T //probably throwing exception

你可以组成最终结果,比如

def innerContent(url:String):Future[Seq[Data]] = for {
  first <- getContent(url)
  json <- Future.fromTry(parseJson(first))
  nextUrlAttempt = Try((json \ "next_url").as[String])
  dataAttempt = Try(getData(json \ "data"))
  data <- Future.fromTry(dataAttempt)
  result <- nextUrlAttempt match {
    case Success(nextUrl) => innerContent(nextUrl)
    case Failure(_) => Future.successful(Seq())
} yield data ++ result

另请查看针对复杂异步流的库,例如您的库:

  1. 玩迭代
  2. scalaz 迭代
  3. 斯卡拉兹流
于 2015-05-22T09:33:39.357 回答