Введение в будущее в Вавре

Введение в будущее в Вавре

1. Вступление

Core Java предоставляет базовый API для асинхронных вычислений -Future.CompletableFuture - одна из его новейших реализаций.

Vavr предоставляет новую функциональную альтернативу APIFuture. В этой статье мы обсудим новый API и покажем, как использовать некоторые из его новых функций.

Больше статей о Вавре можно найтиhere.

2. Maven Dependency

APIFuture включен в зависимость Vavr Maven.

Итак, добавим это к нашемуpom.xml:


    io.vavr
    vavr
    0.9.2

Мы можем найти последнюю версию зависимости отMaven Central.

3. Future Вавра

Future может находиться в одном из двух состояний:

  • Ожидание - вычисление продолжается

  • Завершено - вычисление успешно завершено с результатом, не выполнено с исключением или было отменено

Основное преимущество над ядром JavaFuture заключается в том, что мы можем легко регистрировать обратные вызовы и составлять операции неблокирующим способом.

4. Основные операцииFuture

4.1. Запуск асинхронных вычислений

Теперь давайте посмотрим, как мы можем запустить асинхронные вычисления с помощью Vavr:

String initialValue = "Welcome to ";
Future resultFuture = Future.of(() -> someComputation());

4.2. Получение значений изFuture

Мы можем извлечь значения изFuture, просто вызвав один из методовget() илиgetOrElse():

String result = resultFuture.getOrElse("Failed to get underlying value.");

Разница междуget() иgetOrElse() заключается в том, чтоget() является самым простым решением, аgetOrElse() позволяет нам возвращать значение любого типа в случае, если мы не смогли получить значение внутриFuture.

Рекомендуется использоватьgetOrElse(), чтобы мы могли обрабатывать любые ошибки, возникающие при попытке получить значение изFuture. For the sake of simplicity, we’ll just use get() in the next few examples.

Обратите внимание, что методget() блокирует текущий поток, если необходимо дождаться результата.

Другой подход - вызвать неблокирующий методgetValue(), который возвращаетOption<Try<T>>, которыйwill be empty as long as computation is pending.

Затем мы можем извлечь результат вычисления, который находится внутри объектаTry:

Option> futureOption = resultFuture.getValue();
Try futureTry = futureOption.get();
String result = futureTry.get();

Иногда нам нужно проверить, содержит лиFuture значение, прежде чем извлекать из него значения.

Мы можем просто сделать это, используя:

resultFuture.isEmpty();

Важно отметить, что методisEmpty() является блокирующим - он блокирует поток, пока его работа не будет завершена.

4.3. ИзменениеExecutorService по умолчанию

Futures используютExecutorService для асинхронного выполнения своих вычислений. По умолчаниюExecutorService -Executors.newCachedThreadPool().

Мы можем использовать другойExecutorService, передав реализацию по нашему выбору:

@Test
public void whenChangeExecutorService_thenCorrect() {
    String result = Future.of(newSingleThreadExecutor(), () -> HELLO)
      .getOrElse(error);

    assertThat(result)
      .isEqualTo(HELLO);
}

5. Выполнение действий по завершении

API предоставляет методonSuccess(), который выполняет действие, как толькоFuture завершается успешно.

Точно так же методonFailure() выполняется при отказеFuture.

Давайте посмотрим на быстрый пример:

Future resultFuture = Future.of(() -> appendData(initialValue))
  .onSuccess(v -> System.out.println("Successfully Completed - Result: " + v))
  .onFailure(v -> System.out.println("Failed - Result: " + v));

МетодonComplete() принимает действие, которое будет запущено, как толькоFuture завершит свое выполнение, независимо от того, был лиFuture успешным. МетодandThen() похож наonComplete() - он просто гарантирует, что обратные вызовы выполняются в определенном порядке:

Future resultFuture = Future.of(() -> appendData(initialValue))
  .andThen(finalResult -> System.out.println("Completed - 1: " + finalResult))
  .andThen(finalResult -> System.out.println("Completed - 2: " + finalResult));

6. Полезные операции наFutures

6.1. Блокировка текущего потока

Методawait() имеет два случая:

  • еслиFuture ожидает обработки, он блокирует текущий поток до тех пор, пока Future не завершится

  • еслиFuture завершен, он немедленно завершается

Использовать этот метод просто:

resultFuture.await();

6.2. Отмена вычисления

Мы всегда можем отменить вычисление:

resultFuture.cancel();

6.3. Получение базовогоExecutorService

Чтобы получитьExecutorService, который используетсяFuture, мы можем просто вызватьexecutorService():

resultFuture.executorService();

6.4. ПолучениеThrowable из НеудачногоFuture

Мы можем сделать это с помощью методаgetCause(), который возвращаетThrowable, завернутый в объектio.vavr.control.Option.

Позже мы можем извлечьThrowable из объектаOption:

@Test
public void whenDivideByZero_thenGetThrowable2() {
    Future resultFuture = Future.of(() -> 10 / 0)
      .await();

    assertThat(resultFuture.getCause().get().getMessage())
      .isEqualTo("/ by zero");
}

Кроме того, мы можем преобразовать наш экземпляр вFuture, содержащий экземплярThrowable, используя методfailed():

@Test
public void whenDivideByZero_thenGetThrowable1() {
    Future resultFuture = Future.of(() -> 10 / 0);

    assertThatThrownBy(resultFuture::get)
      .isInstanceOf(ArithmeticException.class);
}

6.5. isCompleted(), isSuccess(), иisFailure()

Эти методы в значительной степени говорят сами за себя. Они проверяют, завершился лиFuture, был ли он завершен успешно или с ошибкой. Конечно, все они возвращают значенияboolean.

Мы собираемся использовать эти методы в предыдущем примере:

@Test
public void whenDivideByZero_thenCorrect() {
    Future resultFuture = Future.of(() -> 10 / 0)
      .await();

    assertThat(resultFuture.isCompleted()).isTrue();
    assertThat(resultFuture.isSuccess()).isFalse();
    assertThat(resultFuture.isFailure()).isTrue();
}

6.6. Применение вычислений на вершине будущего

Методmap() позволяет нам применить вычисление поверх ожидающегоFuture:

@Test
public void whenCallMap_thenCorrect() {
    Future futureResult = Future.of(() -> "from example")
      .map(a -> "Hello " + a)
      .await();

    assertThat(futureResult.get())
      .isEqualTo("Hello from example");
}

Если мы передадим функцию, которая возвращаетFuture, методуmap(), мы можем получить вложенную структуруFuture. Чтобы избежать этого, мы можем использовать методflatMap():

@Test
public void whenCallFlatMap_thenCorrect() {
    Future futureMap = Future.of(() -> 1)
      .flatMap((i) -> Future.of(() -> "Hello: " + i));

    assertThat(futureMap.get()).isEqualTo("Hello: 1");
}



6.7. ПреобразованиеFutures

МетодtransformValue() может использоваться для применения вычисления поверхFuture и изменения значения внутри него на другое значение того же типа или другого типа:

@Test
public void whenTransform_thenCorrect() {
    Future future = Future.of(() -> 5)
      .transformValue(result -> Try.of(() -> HELLO + result.get()));

    assertThat(future.get()).isEqualTo(HELLO + 5);
}



6.8. АрхивированиеFutures

API предоставляет методzip(), который объединяетFutures вместе в кортежи - кортеж - это набор из нескольких элементов, которые могут быть связаны друг с другом, а могут и не быть. Они также могут быть разных типов. Давайте посмотрим на быстрый пример:

@Test
public void whenCallZip_thenCorrect() {
    Future f1 = Future.of(() -> "hello1");
    Future f2 = Future.of(() -> "hello2");

    assertThat(f1.zip(f2).get())
      .isEqualTo(Tuple.of("hello1", "hello2"));
}

Здесь следует отметить, что результирующийFuture будет отложен до тех пор, пока хотя бы один из базовыхFutures все еще ожидает обработки.

6.9. Преобразование междуFutures иCompletableFutures

API поддерживает интеграцию сjava.util.CompletableFuture. Итак, мы можем легко преобразоватьFuture вCompletableFuture, если мы хотим выполнять операции, которые поддерживает только ядро ​​Java API.

Посмотрим, как мы можем это сделать:

@Test
public void whenConvertToCompletableFuture_thenCorrect()
  throws Exception {

    CompletableFuture convertedFuture = Future.of(() -> HELLO)
      .toCompletableFuture();

    assertThat(convertedFuture.get())
      .isEqualTo(HELLO);
}

Мы также можем преобразоватьCompletableFuture вFuture, используя методfromCompletableFuture().

6.10. Обработка исключений

В случае отказаFuture мы можем обработать ошибку несколькими способами.

Например, мы можем использовать методrecover() для возврата другого результата, такого как сообщение об ошибке:

@Test
public void whenFutureFails_thenGetErrorMessage() {
    Future future = Future.of(() -> "Hello".substring(-1))
      .recover(x -> "fallback value");

    assertThat(future.get())
      .isEqualTo("fallback value");
}

Или мы можем вернуть результат другого вычисленияFuture, используяrecoverWith():

@Test
public void whenFutureFails_thenGetAnotherFuture() {
    Future future = Future.of(() -> "Hello".substring(-1))
      .recoverWith(x -> Future.of(() -> "fallback value"));

    assertThat(future.get())
      .isEqualTo("fallback value");
}

МетодfallbackTo() - еще один способ обработки ошибок. Он вызываетсяFuture и принимает другойFuture в качестве параметра.

Если первыйFuture успешен, он возвращает свой результат. В противном случае, если второйFuture успешен, он возвращает свой результат. Если обаFutures не срабатывают, то методfailed() возвращаетFuture изThrowable, который содержит ошибку первогоFuture:

@Test
public void whenBothFuturesFail_thenGetErrorMessage() {
    Future f1 = Future.of(() -> "Hello".substring(-1));
    Future f2 = Future.of(() -> "Hello".substring(-2));

    Future errorMessageFuture = f1.fallbackTo(f2);
    Future errorMessage = errorMessageFuture.failed();

    assertThat(
      errorMessage.get().getMessage())
      .isEqualTo("String index out of range: -1");
}

7. Заключение

В этой статье мы узнали, что такоеFuture, и узнали некоторые из его важных концепций. Мы также рассмотрели некоторые функции API на нескольких практических примерах.

Доступна полная версия кодаover on GitHub.