Play 2: Обработка асинхронных результатов

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

Previous post Next post
Up