Promise
Ratpack est une bibliothèque événementielle non bloquante, il est donc supposé que chaque processus est également écrit de manière asynchrone. Si vous êtes un utilisateur Java expérimenté, vous savez qu'il peut être difficile d'écrire un traitement Java asynchrone. Ratpack fournit une classe «Promise» pour décrire brièvement le traitement asynchrone. L'image est similaire à la Promise
JavaScript, et vous pouvez écrire un rappel lorsque le processus est terminé avec then ()
.
Je pense que le traitement d'E / S est l'opération de blocage la plus typique. Vous pouvez facilement créer une «Promise» en utilisant la classe utilitaire «Blocage».
chain.all( ctx -> {
String query = ctx.getRequest().getQueryParams().get( "id" );
Promise<String> result = Blocking.get( () -> {
return Database.find( query );
} );
ctx.render( result );
} );
Considérez Database.find ()
comme le processus de recherche de données à partir d'une base de données fictive. Blocking.get ()
exécute la fermeture de l'argument de manière asynchrone et encapsule sa valeur de retour dans Promise
. Vous pouvez également passer une Promise
à Context.render ()
.
Utilisez ʻop () pour les opérations sans valeur de retour. La classe ʻOperation
est une Promise
sans valeur de retour dans Ratpack.
Blocking.op( () -> {
String data = ctx.getRequest().getQueryParams().get( "data" );
Database.persist( data );
} ).then( () -> {
ctx.render( "OK" );
} );
Tout d'abord, enregistrez les informations dans une base de données fictive dans Blocking.op ()
. La méthode ʻop () renvoie ʻOperation
pour cette opération. Ensuite, décrivez le traitement après avoir sauvegardé les données dans la base de données avec then ()
. J'appelle Context.render ()
pour créer une réponse appelée ʻOK`.
Promise.sync()
Créez une promesse depuis l'usine.
Promise<String> result = Promise.sync( () -> "data" );
result.then( data -> {
ctx.render( "OK" );
} );
Promise.async()
Une fabrique statique Promise.async ()
est fournie lorsque vous travaillez avec d'autres bibliothèques asynchrones.
Promise<String> result = Promise.async( downstream -> {
downstream.success( "data" );
} );
result.then( data -> {
ctx.render( "OK" );
} );
Appelez la méthode success ()
pour vous dire que le processus est terminé. Notez que Promise.async ()
lui-même n'effectue pas de traitement des arguments de manière asynchrone. Il vous suffit d'écrire le processus asynchrone vous-même (ou dans la bibliothèque) (donc l'exemple officiel crée un Thread
et appelle success ()
).
then
Spécifie le rappel qui sera appelé lorsque le traitement de «Promise» est terminé. Je pense que le processus le plus courant consiste à appeler Context.render ()
dans le rappel et à créer une réponse.
Il convient de noter que «then ()» enregistre le rappel auprès de l'application et l'exécute séquentiellement. Considérez le code suivant.
@Data class Obj {
public int a;
public int b;
public int c;
}
Obj o = new Obj();
Promise.value( 1 ).then( o::setA );
Promise.value( 2 ).then( o::setB );
Promise.value( 3 ).then( o::setC );
Operation.of( () -> ctx.render( o.toString() ) ).then();
Puisque Promise
représente un traitement asynchrone, à première vue, il peut sembler que le champ ʻo lors de l'appel de ʻo.toString ()
dépend du timing. Cependant, l'appel à then ()
garantit que Ratpack s'exécutera séquentiellement dans l'ordre d'enregistrement, donc la valeur de ʻo.toString () `sera toujours ʻObj (a = 1, b = 2, c = 3). ) ». Cependant, ce comportement est non intuitif et déroutant, donc je pense que vous ne devriez pas trop l'utiliser.
map
Crée une «Promise» qui adapte la fonction spécifiée au résultat de la «Promise». C'est la même chose que "map" comme stream.
String result = ExecHarness.yieldSingle( e -> {
return Promise.value( "hoge" )
.map( String::toUpperCase );
} ).getValue();
assertThat( result ).isEqualTo( "HOGE" );
blockingMap
C'est presque la même chose que map
, mais il est exécuté par le thread pour bloquer le traitement. C'est une image qui encapsule le traitement dans map
avec` Blocking.get () ʻetc. Il existe une méthode dérivée «blockingOp».
flatMap
Remplace le résultat de «Promise» par «Promise» renvoyé par la fonction spécifiée. Ratpack a beaucoup de traitement qui retourne Promise
par défaut, il est donc utilisé plus fréquemment que prévu.
String result = ExecHarness.yieldSingle( e -> {
return Promise.value( "hoge" )
.flatMap( v -> {
assertThat( v ).isEqualTo( "hoge" );
return Promise.value( "piyo" );
} );
} ).getValue();
assertThat( result ).isEqualTo( "piyo" );
mapIf
Applique la fonction de carte uniquement si le Predicate
spécifié est positif.
mapError
flatMapError
Si une exception se produit, le résultat de l'application de la fonction de mappage qui prend l'exception comme argument est renvoyé. Vous pouvez facilement écrire des branches lorsqu'elle se termine normalement et lorsqu'une erreur se produit.
String result = ExecHarness.yieldSingle( e -> {
return Promise.value( "hoge" )
.mapIf( s -> true, s -> { throw new RuntimeException();} )
.mapError( t -> "piyo" );
} ).getValue();
assertThat( result ).isEqualTo( "piyo" );
apply
Prend une fonction qui prend l'appelant «Promise» lui-même et renvoie «Promise». Je ne sais pas comment m'en servir, mais il semble que le but soit de simplifier la description lorsque le processus est divisé en méthodes.
String result = ExecHarness.yieldSingle( e -> {
return Promise.value( "hoge" ).apply( p -> {
assertThat( p == Promise.value( "hoge" ) ).isTrue();
return p.map( String::toUpperCase );
} );
} ).getValue();
assertThat( result ).isEqualTo( "HOGE" );
around
Insérer le traitement avant et après le calcul de «Promise». Cela semble utile en soi, mais c'est une méthode honteuse qui rend le code inutile car vous devez envelopper le résultat après dans ʻExecResult`.
String result = ExecHarness.yieldSingle( e -> {
return Promise.value( "hoge" )
.around(
() -> "before",
( before, r ) -> {
assertThat( before ).isEqualTo( "before" );
assertThat( r.getValue() ).isEqualTo( "hoge" );
return ExecResult.of( Result.success( "piyo" ) );
}
);
} ).getValue();
assertThat( result ).isEqualTo( "piyo" );
replace
Remplacez «Promise» par une autre «Promise». Bref, c'est une version qui ne prend pas l'argument de flatMap ()
. C'est aussi une méthode dont la nécessité n'est pas bien comprise.
String result = ExecHarness.yieldSingle( e -> {
return Promise.value( "hoge" )
.replace( Promise.value( "piyo" ) );
} ).getValue();
assertThat( result ).isEqualTo( "piyo" );
route
Si «Predicate» est «true», exécutez le consommateur spécifié. En regardant JavaDoc, il semble qu'il soit destiné à être utilisé pour la validation de données etc ..., mais je pense que ce n'est pas très facile à utiliser.
ExecResult<String> result = ExecHarness.yieldSingle( e -> {
return Promise.value( "hoge" )
.route( s -> false, System.out::println );
} );
assertThat( result.getValue() ).isEqualTo( "hoge" );
assertThat( result.isComplete() ).isFalse();
boolean completed = ExecHarness.yieldSingle( e -> {
return Promise.value( "hoge" )
.route( s -> true, System.out::println );
} ).isComplete();
assertThat( completed ).isTrue();
to
Convertit «Promise» en un autre type. Cela peut sembler difficile à utiliser, mais il est utilisé pour l'intégration de bibliothèques externes. Voici un exemple de RxRatpack.
List<String> resultHolder = new ArrayList<>();
ExecHarness.runSingle( e -> {
Promise.value( "hoge" )
.to( RxRatpack::observe )
.subscribe( s -> resultHolder.add( s ) );
} );
assertThat( resultHolder ).containsExactly( "hoge" );
next
Il a un consommateur dont l'argument est le résultat de "Promise". La valeur de retour «Promise» renvoie le même résultat que la «Promise» d'origine. Il existe des méthodes dérivées telles que «nextOp».
String result = ExecHarness.yieldSingle( e -> {
return Promise.value( "hoge" )
.next( System.out::println );
} ).getValue();
assertThat( result ).isEqualTo( "hoge" );
right
left
Combine «Promise» avec une autre «Promise» et la renvoie comme «Promise» de «Pair».
Pair<String, String> result = ExecHarness.yieldSingle( e -> {
return Promise.value( "hoge" )
.right( Promise.value( "piyo" ) );
} ).getValue();
assertThat( result.getLeft() ).isEqualTo( "hoge" );
assertThat( result.getRight() ).isEqualTo( "piyo" );
cache
Mettez en cache le résultat de «Promise». Si une exception se produit, cette exception est également mise en cache.
Il existe des méthodes dérivées telles que cacheResultIf
.
onError
Décrit le traitement lorsqu'une erreur se produit. Je pense que l'utilisation principale est d'écrire Context.render ()
au moment de l'erreur. Vous pouvez avoir plusieurs modèles d'arguments, comme celui qui prend une classe d'exception comme argument, celui qui reçoit un consommateur d'exception et celui qui sélectionne une exception avec Predicate
.
close
Lorsque la Promise
se termine ou qu'une exception se produit, le ʻAutoCloseable` spécifié dans l'argument est fermé. Je ne sais pas où l’utiliser.
retry
Si le processus échoue, il réessayera après un délai spécifié. Ceci est pratique lors de l'appel d'une API externe.
String result = ExecHarness.yieldSingle( e -> {
return Promise.value( "hoge" )
.retry( 3, Duration.ofSeconds( 1 ), ( i, t ) -> System.out.printf( "retry: %d%n", i ) );
} ).getValue();
assertThat( result ).isEqualTo( "hoge" );
time
Il prend un consommateur comme argument qui renvoie le temps nécessaire pour exécuter la Promise
. La mesure du rendement est-elle la principale utilisation?
fourchette
Ratpack exécute «Promise» dans l'unité représentée par la classe «Execution» pour un traitement asynchrone. Normalement, vous n'êtes pas au courant de cette «exécution», mais pour exécuter plusieurs «promesses» en parallèle, vous devez «fork ()» «exécution». Promise.fork ()
est fourni comme méthode pratique, et vous pouvez facilement exécuter Promise
dans un autre thread.
Le code ci-dessous est une légère modification de l'exemple dans le JavaDoc fork
.
CyclicBarrier b = new CyclicBarrier( 2 );
Pair<String, String> result = ExecHarness.yieldSingle( e -> {
Promise<String> p1 = Promise.sync( () -> {
b.await();
return "hoge";
} ).fork();
Promise<String> p2 = Promise.sync( () -> {
b.await();
return "piyo";
} ).fork();
return p1.right( p2 );
} ).getValue();
assertThat( result.getLeft() ).isEqualTo( "hoge" );
assertThat( result.getRight() ).isEqualTo( "piyo" );
Maintenant, si vous supprimez l'appel à fork ()
, p1
et p2
seront exécutés en séquence dans le même thread, ce qui entraînera un blocage. Si vous avez créé un autre thread avec fork ()
, cela fonctionnera normalement.
La «Promise» de Ratpack prend en charge le traitement asynchrone, ce que Java n'est pas bon. Cependant, certaines pièces ont de fortes habitudes et il existe de nombreuses méthodes, il est donc difficile de les utiliser correctement. L'astuce peut être de ne pas essayer d'être très intelligent. Ratpack a un module RxRatpack pour prendre en charge RxJava. Si vous avez des bibliothèques asynchrones familières, vous pouvez également en tirer parti.
map
et flatMap
, et toutes les méthodes ne sont pas pratiques.Blocking
ou la méthode blocking ...
pour bloquer les opérations.Recommended Posts