2015-05-22 4 views
1

Я использую Scala для того, чтобы HTTP-запросы HTTP были переданы в API (точнее, в WS-версии Play Framework), который отвечает на ответ 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.

Теперь сделать это блокирующим способом проще, но я не хочу этого делать. Каков наилучший способ решить такую ​​проблему?

ответ

2

Возможно, есть способ сделать это где-то в скалясе, но если вы не знаете конкретного решения, как правило, можно построить его с рекурсией и 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} 
    } 

(Обратите внимание, что это использует кадр стека для каждого вызова - если вы хотите сделать тысячи выборок, то мы должны написать это немного более тщательно, так что это хвостовая рекурсию)

1

the Future.flatMap method существует exaclty для случаев, как этот

Предположим, у вас есть такие вещи:

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

и JsValue типа имеют метамфетамин в.п.с. вдохновленный play json library

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. play iteratees
  2. scalaz iteratees
  3. scalaz stream
+0

Будет ли это работать на неопределенное количество 'next_url'? – Ashesh

+0

@Ashesh Извините, я не понял вас сначала. Обновлен мой ответ – Odomontois

Смежные вопросы