Sep 18, 2013 16:32
Обработка асинхронных результатов
Почему асинхронные результаты?
До сих пор мы могли создавать результат и отправлять его напрямую web-клиенту. Однако, это не всегда то что нам надо: результат может зависит от "дорогих" вычислений или долгого вызова web-сервиса.
Из-за того как Play работает, код действия должен быть быстрый, насколько это возможно (другими словами неблокирующий). Что должны мы возвратить в качестве результата если мы еще не можем его создать? Правильны ответ - будущий результат (или промис)!
В конечном итоге будет возвращен Future[Result] со значением типа Result. Передавая Future[Result] вместо обычного Result мы можем быстро создавать в ответ без блокировок. Затем, Play обработает этот результат как только будет получен промис.
Web-клиент будет заблокирован в ожидании ответа, но сам сервер не будет заблокирован и серверные ресурсы могут быть использованы другими клиентами.
Как создать Future[Result]
Для того чтобы создать Future[Result] сначала нам понадобится другой промис, который выдаст необходимое нам действительное значение для вычисления результата:
import play.api.libs.concurrent.Execution.Implicits.defaultContext
val futurePIValue: Future[Double] = computePIAsynchronously()
val futureResult: Future[SimpleResult] = futurePIValue.map { pi =>
Ok("PI value computed: " + pi)
}
Все асинхронные API-вызовы возвращают вам Future. При всех случаях когда вы вызываете внешние web-сервисы при помощи play.api.libs.WS или используя Akka для создания асинхронных задач или для взаимодействия с акторами при помощи play.api.libs.Akka.
Самый простой способ асинхронно выполнить кусок кода и получить Future:
val futureInt: Future[Int] = scala.concurrent.Future {
intensiveComputation()
}
Важно понимать какой код исполняется в треде с промисами. В двух примерах кода выше показан имопорт в контексте исполнения Play по умолчанию. Это неявный параметр который передается всем методам в API промисов, которые принимаю обратные вызовы. Контекст выполнения часто совпадает с пулом тредов, но не обязательно.
Возврат промисов
До сих пор мы использовали конструктор методов Action.apply для построения действий, для отправки асинхронного результата нам необходимо использовать конструктор Action.async:
import play.api.libs.concurrent.Execution.Implicits.defaultContext
def index = Action.async {
val futureInt = scala.concurrent.Future { intensiveComputation() }
futureInt.map(i => Ok("Got result: " + i))
}
Обработка таймаутов
Всегда полезно правильно обрабатывать таймауты для избежания блокировок и ожидания web-баузера в том случае если что-то пошло не так. Вы можете легко создавать промис с таймаутом для обработки подобных случаев:
import play.api.libs.concurrent.Execution.Implicits.defaultContext
import scala.concurrent.duration._
def index = Action.async {
val futureInt = scala.concurrent.Future { intensiveComputation() }
val timeoutFuture = play.api.libs.concurrent.Promise.timeout("Oops", 1.second)
Future.firstCompletedOf(Seq(futureInt, timeoutFuture)).map {
case i: Int => Ok("Got result: " + i)
case t: String => InternalServerError(t)
}
}
async,
scala,
play