Несколько вызовов WS в одном действии, как обрабатывать объекты Promise? - PullRequest
11 голосов
/ 03 апреля 2012

Я разрабатываю небольшой сервер в PlayFramework2 / Scala, который должен извлекать данные из нескольких WS (REST / JSON), манипулировать данными из этих WS, затем составлять и возвращать результат.

Я знаю, каквызовите one WS, манипулируйте данными и верните асинхронный ответ.Но я не знаю, как вызвать последовательно нескольких веб-сервисов, обрабатывать данные между каждым вызовом и генерировать агрегированный ответ.

Пример:

  • Получить список моих любимых песен из WebService A
  • , а затем, для каждой песни, выбрать детали исполнителя из WS B (один вызов по песне)
  • затем сгенерируйте и верните что-то (например, агрегированный список), используя ответы A и B
  • , затем вернитерезультат

Я заблокирован асинхронной обработкой WS API (WS.url(url).get => Promise[Response]).Нужно ли опираться на Акку, чтобы решить эту проблему?

Спасибо.

1 Ответ

19 голосов
/ 03 апреля 2012

flatMap и map ваши друзья! Эти два метода типа Promise позволяют преобразовать результат Promise[A] в другой Promise[B].

Вот простой пример их работы (я намеренно написал явно больше аннотаций типов, чем нужно, просто чтобы понять, где происходят преобразования):

def preferredSongsAndArtist = Action {
  // Fetch the list of your preferred songs from Web Service “A”
  val songsP: Promise[Response] = WS.url(WS_A).get
  val resultP: Promise[List[Something]] = songsP.flatMap { respA =>
    val songs: List[Song] = Json.fromJson(respA.json)
    // Then, for each song, fetch the artist detail from Web Service “B”
    val result: List[Promise[Something]] = songs.map { song =>
      val artistP = WS.url(WS_B(song)).get
      artistP.map { respB =>
        val artist: Artist = Json.fromJson(respB.json)
        // Then, generate and return something using the song and artist
        val something: Something = generate(song, artist)
        something
      }
    }
    Promise.sequence(result) // Transform the List[Promise[Something]] into a Promise[List[Something]]
  }
  // Then return the result
  Async {
    resultP.map { things: List[Something] =>
      Ok(Json.toJson(things))
    }
  }
}

Без ненужных аннотаций типов и использования нотации «для понимания» вы можете написать следующий более выразительный код:

def preferredSongsAndArtist = Action {
  Async {
    for {
      // Fetch the list of your preferred songs from Web Service “A”
      respA <- WS.url(WS_A).get
      songs = Json.fromJson[List[Song]](respA.json)
      // Then, for each song, fetch the artist detail from Web Service “B”
      result <- Promise.sequence(songs.map { song =>
        for {
          respB <- WS.url(WS_B(song)).get
          artist = Json.fromJson[Artist](respB.json)
        } yield {
          // Then, generate and return something using the song and artist
          generate(song, artist)
        }
      })
    // Then return the result
    } yield {
      Ok(Json.toJson(result))
    }
  }
}
...