Introdução ao futuro no Vavr
1. Introdução
Core Java fornece uma API básica para cálculos assíncronos -Future.CompletableFuture é uma de suas implementações mais recentes.
Vavr fornece sua nova alternativa funcional para a APIFuture. Neste artigo, discutiremos a nova API e mostraremos como usar alguns de seus novos recursos.
Mais artigos sobre o Vavr podem ser encontradoshere.
2. Dependência do Maven
A APIFuture está incluída na dependência do Vavr Maven.
Então, vamos adicioná-lo ao nossopom.xml:
io.vavr
vavr
0.9.2
Podemos encontrar a versão mais recente da dependência emMaven Central.
3. Future de Vavr
OFuture pode estar em um de dois estados:
-
Pendente - o cálculo está em andamento
-
Concluído - o cálculo foi concluído com êxito com um resultado, falhou com uma exceção ou foi cancelado
A principal vantagem sobre o JavaFuture principal é que podemos facilmente registrar retornos de chamada e compor operações de uma maneira sem bloqueio.
4. Operações básicasFuture
4.1. Iniciando Computações Assíncronas
Agora, vamos ver como podemos iniciar cálculos assíncronos usando Vavr:
String initialValue = "Welcome to ";
Future resultFuture = Future.of(() -> someComputation());
4.2. Recuperando Valores de aFuture
Podemos extrair valores deFuture simplesmente chamando um dos métodosget() ougetOrElse():
String result = resultFuture.getOrElse("Failed to get underlying value.");
A diferença entreget()egetOrElse() é queget() é a solução mais simples, enquantogetOrElse() nos permite retornar um valor de qualquer tipo, caso não possamos recuperar o valor dentro deFuture.
É recomendado usargetOrElse() para que possamos lidar com quaisquer erros que ocorram ao tentar recuperar o valor de umFuture. For the sake of simplicity, we’ll just use get() in the next few examples.
Observe que o métodoget() bloqueia o thread atual se for necessário esperar pelo resultado.
Uma abordagem diferente é chamar o métodogetValue() não bloqueante, que retorna umOption<Try<T>> quewill be empty as long as computation is pending.
Podemos então extrair o resultado do cálculo que está dentro do objetoTry:
Option> futureOption = resultFuture.getValue();
Try futureTry = futureOption.get();
String result = futureTry.get();
Às vezes, precisamos verificar seFuture contém um valor antes de recuperar valores dele.
Podemos simplesmente fazer isso usando:
resultFuture.isEmpty();
É importante observar que o métodoisEmpty() está bloqueando - ele bloqueará o thread até que sua operação seja concluída.
4.3. Alterando o padrãoExecutorService
Futures usa umExecutorService para executar seus cálculos de forma assíncrona. OExecutorService padrão éExecutors.newCachedThreadPool().
Podemos usar outroExecutorService passando uma implementação de nossa escolha:
@Test
public void whenChangeExecutorService_thenCorrect() {
String result = Future.of(newSingleThreadExecutor(), () -> HELLO)
.getOrElse(error);
assertThat(result)
.isEqualTo(HELLO);
}
5. Execução de ações após a conclusão
A API fornece o métodoonSuccess() que executa uma ação assim queFuture é concluído com êxito.
Da mesma forma, o métodoonFailure() é executado na falha deFuture.
Vamos ver um exemplo rápido:
Future resultFuture = Future.of(() -> appendData(initialValue))
.onSuccess(v -> System.out.println("Successfully Completed - Result: " + v))
.onFailure(v -> System.out.println("Failed - Result: " + v));
O métodoonComplete() aceita que uma ação seja executada assim queFuture tiver concluído sua execução, independentemente deFuture ter sido bem-sucedido ou não. O métodoandThen() é semelhante aonComplete() - ele apenas garante que os callbacks sejam executados em uma ordem específica:
Future resultFuture = Future.of(() -> appendData(initialValue))
.andThen(finalResult -> System.out.println("Completed - 1: " + finalResult))
.andThen(finalResult -> System.out.println("Completed - 2: " + finalResult));
6. Operações úteis emFutures
6.1. Bloqueando o Tópico Atual
O métodoawait() tem dois casos:
-
seFuture estiver pendente, ele bloqueia o thread atual até que o futuro seja concluído
-
se oFuture for concluído, ele termina imediatamente
O uso desse método é direto:
resultFuture.await();
6.2. Cancelando um cálculo
Sempre podemos cancelar o cálculo:
resultFuture.cancel();
6.3. Recuperando oExecutorService subjacente
Para obter oExecutorService que é usado porFuture, podemos simplesmente chamarexecutorService():
resultFuture.executorService();
6.4. Obtendo umThrowable de umFuture com falha
Podemos fazer isso usando o métodogetCause() que retornaThrowable embrulhado em um objetoio.vavr.control.Option.
Podemos posteriormente extrairThrowable do objetoOption:
@Test
public void whenDivideByZero_thenGetThrowable2() {
Future resultFuture = Future.of(() -> 10 / 0)
.await();
assertThat(resultFuture.getCause().get().getMessage())
.isEqualTo("/ by zero");
}
Além disso, podemos converter nossa instância emFuture contendo uma instânciaThrowable usando o métodofailed():
@Test
public void whenDivideByZero_thenGetThrowable1() {
Future resultFuture = Future.of(() -> 10 / 0);
assertThatThrownBy(resultFuture::get)
.isInstanceOf(ArithmeticException.class);
}
6.5. isCompleted(), isSuccess(), eisFailure()
Esses métodos são praticamente auto-explicativos. Eles verificam se umFuture foi concluído, se foi concluído com sucesso ou com falha. Todos eles retornam valoresboolean, é claro.
Usaremos esses métodos com o exemplo anterior:
@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. Aplicando Computações em um Futuro
O métodomap() nos permite aplicar um cálculo em cima de umFuture: pendente
@Test
public void whenCallMap_thenCorrect() {
Future futureResult = Future.of(() -> "from example")
.map(a -> "Hello " + a)
.await();
assertThat(futureResult.get())
.isEqualTo("Hello from example");
}
Se passarmos uma função que retornaFuture para o métodomap(), podemos terminar com uma estruturaFuture aninhada. Para evitar isso, podemos aproveitar o métodoflatMap():
@Test
public void whenCallFlatMap_thenCorrect() {
Future
6.7. TransformandoFutures
O métodotransformValue() pode ser usado para aplicar um cálculo sobreFuturee alterar o valor dentro dele para outro valor do mesmo tipo ou um tipo diferente:
@Test
public void whenTransform_thenCorrect() {
Future
6.8. CompactandoFutures
A API fornece o métodozip() que compactaFutures em tuplas - uma tupla é uma coleção de vários elementos que podem ou não estar relacionados entre si. Eles também podem ser de tipos diferentes. Vamos ver um exemplo rápido:
@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"));
}
O ponto a ser observado aqui é que oFuture resultante estará pendente enquanto pelo menos um dosFutures base ainda estiver pendente.
6.9. Conversão entreFutures eCompletableFutures
A API oferece suporte à integração comjava.util.CompletableFuture. Portanto, podemos facilmente converterFuture emCompletableFuture se quisermos realizar operações que apenas a API Java principal suporta.
Vamos ver como podemos fazer isso:
@Test
public void whenConvertToCompletableFuture_thenCorrect()
throws Exception {
CompletableFuture convertedFuture = Future.of(() -> HELLO)
.toCompletableFuture();
assertThat(convertedFuture.get())
.isEqualTo(HELLO);
}
Também podemos converter aCompletableFuture emFuture usando o métodofromCompletableFuture().
6.10. Manipulação de exceção
Em caso de falha de aFuture, podemos lidar com o erro de algumas maneiras.
Por exemplo, podemos usar o métodorecover() para retornar outro resultado, como uma mensagem de erro:
@Test
public void whenFutureFails_thenGetErrorMessage() {
Future future = Future.of(() -> "Hello".substring(-1))
.recover(x -> "fallback value");
assertThat(future.get())
.isEqualTo("fallback value");
}
Ou podemos retornar o resultado de outro cálculo deFuture usandorecoverWith():
@Test
public void whenFutureFails_thenGetAnotherFuture() {
Future future = Future.of(() -> "Hello".substring(-1))
.recoverWith(x -> Future.of(() -> "fallback value"));
assertThat(future.get())
.isEqualTo("fallback value");
}
O métodofallbackTo() é outra maneira de lidar com erros. Ele é chamado emFuturee aceita outroFuture como parâmetro.
Se o primeiroFuture for bem-sucedido, ele retornará seu resultado. Caso contrário, se o segundoFuture for bem-sucedido, ele retornará seu resultado. Se ambosFutures falharem, então o métodofailed() retorna umFuture de umThrowable, que contém o erro do primeiroFuture:
@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. Conclusão
Neste artigo, vimos o que é aFuture e aprendemos alguns de seus conceitos importantes. Também examinamos alguns dos recursos da API usando alguns exemplos práticos.
A versão completa do código está disponívelover on GitHub.