По сути, в Scala асинхронность представлена Future
, который можно рассматривать как оболочку одного объекта, который:
- может в какой-то момент времени (возможно, в прошлое) будет завершено
- если оно завершено, оно либо завершено с помощью объекта, либо исключением
Можно зарегистрировать обратные вызовы, которые будут работать с Future
в какой-то момент после него завершает; можно также обращаться с Future
так же, как с коллекцией с map
, flatMap
, et c. чтобы преобразовать его (за кулисами эти операции регистрируют обратный вызов и возвращают новый Future
, который завершается обратным вызовом: использование map
, flatMap
, recover
и друзей обычно предпочтительнее ручной регистрации обратного вызова).
Это очень общий API, и есть много-много способов его реализовать. В общем, если библиотека Scala возвращает Future
с, она поддерживает асинхронные операции из коробки. Нет недостатка в библиотеках, которые дадут Future
HTTP-запроса, включая, но не ограничиваясь этим:
- Akka HTTP
- Dispatch
- Gigahorse
- Play WS
Выбор такой библиотеки в основном дело вкуса: другие используемые библиотеки могут направить вас к конкретной библиотеке (например, если вы используете другие библиотеки Akka, может иметь смысл использовать Akka HTTP), и вы можете обнаружить, что та или иная библиотека лучше поддерживает конкретный вариант использования.
Кроме того, если конкретная библиотека не дает вам Future
, вы можете довольно легко обернуть его:
import some.blocking.http.request.library
import scala.concurrent.ExecutionContext
def asyncRequestUrl(url: String)(implicit ectx: ExecutionContext): Future[Response] =
Future {
library.request(url)
}
scala.concurrent.ExecutionContext
представляет пул потоков с невыполненными задачами для выполнения в пуле. Future { code... }
(технически Future.apply
) добавляет задачу для выполнения code...
и завершения будущего (которое немедленно возвращается) с результатом выполнения. Обратите внимание: поскольку пул потоков почти всегда имеет верхний предел количества потоков в нем, задача будет оставаться в состоянии «запланировано, но не выполняется» до тех пор, пока все потоки в пуле заняты другими задачами.
Если вызывающий не предоставляет ExecutionContext
, компилятор пометит это как ошибку и может предложить использовать scala.concurrent.ExecutionContext.Implicits.global
. Использование этого ExecutionContext
, вероятно, нормально в средах разработки / тестирования, но почти наверняка захочется создать собственный ExecutionContext
с большим количеством потоков, чем глобальный контекст по умолчанию, по крайней мере, для выполнения HTTP-запросов. Использование библиотеки asyn c по индивидуальному проекту, подобной тем, которые я перечислил выше, вероятно, «просто сработает» с точки зрения выбора подходящего пула потоков (и, вероятно, будет настраиваемым, если обнаружится, что он «не просто работает»).
Если вы используете кодовую базу, ориентированную на FP, вы, вероятно, захотите использовать что-то вроде http4s , которое использует немного другую модель, чем «vanilla» Scala для асинхронности (например, ConcurrentEffect
от Cats Effect). Есть способы заставить его работать с ванилью Scala, если так хочется.