Introduction à l’avenir dans Vavr

Introduction au futur chez Vavr

1. introduction

Core Java fournit une API de base pour les calculs asynchrones -Future.CompletableFuture est l'une de ses dernières implémentations.

Vavr propose sa nouvelle alternative fonctionnelle à l'APIFuture. Dans cet article, nous aborderons la nouvelle API et vous montrerons comment utiliser certaines de ses nouvelles fonctionnalités.

Vous trouverez plus d'articles sur Vavrhere.

2. Dépendance Maven

L'APIFuture est incluse dans la dépendance Vavr Maven.

Alors, ajoutons-le à nospom.xml:


    io.vavr
    vavr
    0.9.2

Nous pouvons trouver la dernière version de la dépendance surMaven Central.

3. Future de Vavr

LesFuture peuvent être dans l'un des deux états suivants:

  • En attente - le calcul est en cours

  • Completed - le calcul s'est terminé avec succès avec un résultat, a échoué avec une exception ou a été annulé

Le principal avantage par rapport au noyau JavaFuture est que nous pouvons facilement enregistrer des callbacks et composer des opérations de manière non bloquante.

4. Opérations de base deFuture

4.1. Démarrage des calculs asynchrones

Voyons maintenant comment nous pouvons démarrer des calculs asynchrones à l'aide de Vavr:

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

4.2. Récupération des valeurs d'unFuture

Nous pouvons extraire des valeurs d'unFuture en appelant simplement l'une des méthodesget() ougetOrElse():

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

La différence entreget() etgetOrElse() est queget() est la solution la plus simple, tandis quegetOrElse() nous permet de renvoyer une valeur de n'importe quel type au cas où nous ne serions pas en mesure de récupérer la valeur à l'intérieur desFuture.

Il est recommandé d'utilisergetOrElse() afin que nous puissions gérer toutes les erreurs qui se produisent lors de la tentative de récupération de la valeur d'unFuture. For the sake of simplicity, we’ll just use get() in the next few examples.

Notez que la méthodeget() bloque le thread actuel s’il est nécessaire d’attendre le résultat.

Une approche différente consiste à appeler la méthode non bloquantegetValue(), qui renvoie unOption<Try<T>> quiwill be empty as long as computation is pending.

On peut alors extraire le résultat du calcul qui se trouve à l'intérieur de l'objetTry:

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

Parfois, nous devons vérifier si leFuture contient une valeur avant d'en récupérer les valeurs.

Nous pouvons simplement le faire en utilisant:

resultFuture.isEmpty();

Il est important de noter que la méthodeisEmpty() est bloquante - elle bloquera le thread jusqu'à ce que son opération soit terminée.

4.3. Modification duExecutorService par défaut

Futures utilise unExecutorService pour exécuter leurs calculs de manière asynchrone. LeExecutorService par défaut estExecutors.newCachedThreadPool().

Nous pouvons utiliser un autreExecutorService en passant une implémentation de notre choix:

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

    assertThat(result)
      .isEqualTo(HELLO);
}

5. Exécution d'actions à la fin

L'API fournit la méthodeonSuccess() qui exécute une action dès que leFuture se termine avec succès.

De même, la méthodeonFailure() est exécutée en cas d'échec desFuture.

Voyons un exemple rapide:

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

La méthodeonComplete() accepte une action à exécuter dès que leFuture a terminé son exécution, que leFuture ait réussi ou non. La méthodeandThen() est similaire àonComplete() - elle garantit simplement que les callbacks sont exécutés dans un ordre spécifique:

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

6. Opérations utiles surFutures

6.1. Bloquer le fil de discussion actuel

La méthodeawait() a deux cas:

  • si leFuture est en attente, il bloque le thread actuel jusqu'à ce que le futur soit terminé

  • si leFuture est terminé, il se termine immédiatement

Utiliser cette méthode est simple:

resultFuture.await();

6.2. Annulation d'un calcul

On peut toujours annuler le calcul:

resultFuture.cancel();

6.3. Récupération desExecutorService sous-jacents

Pour obtenir leExecutorService utilisé par unFuture, nous pouvons simplement appelerexecutorService():

resultFuture.executorService();

6.4. Obtention d'unThrowable à partir d'unFuture en échec

Nous pouvons le faire en utilisant la méthodegetCause() qui retourne lesThrowable enveloppés dans un objetio.vavr.control.Option.

Nous pouvons ensuite extraire lesThrowable de l'objetOption:

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

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

De plus, nous pouvons convertir notre instance en une instanceFuture contenant une instanceThrowable en utilisant la méthodefailed():

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

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

6.5. isCompleted(), isSuccess(), etisFailure()

Ces méthodes sont assez explicites. Ils vérifient si unFuture est terminé, s'il s'est terminé avec succès ou avec un échec. Tous renvoient des valeursboolean, bien sûr.

Nous allons utiliser ces méthodes avec l'exemple précédent:

@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. Appliquer des calculs au sommet d'un avenir

La méthodemap() nous permet d'appliquer un calcul au dessus d'unFuture: en attente

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

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

Si nous passons une fonction qui renvoie unFuture à la méthodemap(), nous pouvons nous retrouver avec une structureFuture imbriquée. Pour éviter cela, nous pouvons utiliser la méthodeflatMap():

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

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



6.7. TransformerFutures

La méthodetransformValue() peut être utilisée pour appliquer un calcul au-dessus d'unFuture et changer la valeur qu'il contient en une autre valeur du même type ou d'un type différent:

@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. ZipperFutures

L'API fournit la méthodezip() qui zips lesFutures ensemble en tuples - un tuple est une collection de plusieurs éléments qui peuvent ou non être liés les uns aux autres. Ils peuvent aussi être de types différents. Voyons un exemple rapide:

@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"));
}

Le point à noter ici est que lesFuture résultants seront en attente tant qu'au moins l'un desFutures de base est toujours en attente.

6.9. Conversion entreFutures etCompletableFutures

L'API prend en charge l'intégration avecjava.util.CompletableFuture. Ainsi, nous pouvons facilement convertir unFuture en unCompletableFuture si nous voulons effectuer des opérations que seule l'API Java principale prend en charge.

Voyons comment nous pouvons y parvenir:

@Test
public void whenConvertToCompletableFuture_thenCorrect()
  throws Exception {

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

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

Nous pouvons également convertir unCompletableFuture en unFuture en utilisant la méthodefromCompletableFuture().

6.10. Gestion des exceptions

En cas d'échec d'unFuture, nous pouvons gérer l'erreur de plusieurs manières.

Par exemple, nous pouvons utiliser la méthoderecover() pour renvoyer un autre résultat, tel qu'un message d'erreur:

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

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

Ou, nous pouvons retourner le résultat d'un autre calcul deFuture en utilisantrecoverWith():

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

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

La méthodefallbackTo() est une autre façon de gérer les erreurs. Il est appelé sur unFuture et accepte un autreFuture comme paramètre.

Si le premierFuture réussit, il renvoie son résultat. Sinon, si le deuxièmeFuture réussit, il renvoie son résultat. Si les deuxFutures échouent, alors la méthodefailed() renvoie unFuture d'unThrowable, qui contient l'erreur des premiersFuture:

@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. Conclusion

Dans cet article, nous avons vu ce qu'est unFuture et appris certains de ses concepts importants. Nous avons également décrit certaines des fonctionnalités de l'API à l'aide de quelques exemples pratiques.

La version complète du code est disponibleover on GitHub.