[JAVA] Einführung in Ratpack (6) --Promise

Ratpack Einführungsserie

  1. Einführung in Ratpack (1) - Was ist Ratpack
  2. Einführung in Ratpack (2) --Architecture
  3. Einführung in Ratpack (3) - Hallo Welt, ausführliche Erklärung
  4. Einführung in Ratpack (4) - Routing & statischer Inhalt
  5. Einführung in Ratpack (5) --Json & Registry
  6. Einführung in Ratpack (6) --Promise
  7. Einführung in Ratpack (7) - Guice & Spring
  8. Einführung in Ratpack (8) - Sitzung
  9. Einführung in Ratpack (9) - Thymeleaf

Promise

Ratpack ist eine nicht blockierende ereignisgesteuerte Bibliothek, daher wird davon ausgegangen, dass jeder Prozess auch asynchron geschrieben wird. Wenn Sie ein erfahrener Java-Benutzer sind, wissen Sie, dass es schwierig sein kann, eine asynchrone Java-Verarbeitung schlecht zu schreiben. Ratpack bietet eine "Promise" -Klasse, um die asynchrone Verarbeitung kurz zu beschreiben. Das Bild ähnelt dem JavaScript-Versprechen "Versprechen", und Sie können einen Rückruf schreiben, wenn der Vorgang mit "dann ()" abgeschlossen ist.

Machen Sie ein "Versprechen"

Ich denke, die E / A-Verarbeitung ist die typischste Blockierungsoperation. Sie können ganz einfach ein Versprechen erstellen, indem Sie die Dienstprogrammklasse "Blockieren" verwenden.

chain.all( ctx -> {

    String query = ctx.getRequest().getQueryParams().get( "id" );

    Promise<String> result = Blocking.get( () -> {
        return Database.find( query );
    } );

    ctx.render( result );
} );

Stellen Sie sich "Database.find ()" als den Prozess des Findens von Daten aus einer fiktiven Datenbank vor. Blocking.get () führt den Argumentabschluss asynchron aus und schließt seinen Rückgabewert in Promise ein. Sie können auch ein "Versprechen" an "Context.render ()" übergeben.

Verwenden Sie op () für Operationen, die keinen Rückgabewert haben. Die "Operation" -Klasse ist ein "Versprechen" ohne Rückgabewert in Ratpack.

Blocking.op( () -> {
    String data = ctx.getRequest().getQueryParams().get( "data" );
    Database.persist( data );
} ).then( () -> {
    ctx.render( "OK" );
} );

Speichern Sie zunächst die Informationen in einer fiktiven Datenbank in "Blocking.op ()". Die Methode op () gibt die Operation für diese Operation zurück. Beschreiben Sie als Nächstes die Verarbeitung nach dem Speichern der Daten in der Datenbank mit then (). Ich rufe "Context.render ()" auf, um eine Antwort mit dem Namen "OK" zu erstellen.

Promise.sync()

Erstellen Sie ein "Versprechen" ab Werk.

Promise<String> result = Promise.sync( () -> "data" );
result.then( data -> {
    ctx.render( "OK" );
} );

Promise.async()

Bei der Arbeit mit anderen asynchronen Bibliotheken wird eine statische Factory "Promise.async ()" bereitgestellt.

Promise<String> result = Promise.async( downstream -> {
    downstream.success( "data" );
} );
result.then( data -> {
    ctx.render( "OK" );
} );

Rufen Sie die Methode success () auf, um zu erfahren, dass der Vorgang abgeschlossen ist. Beachten Sie, dass Promise.async () selbst die Argumentverarbeitung nicht asynchron durchführt. Sie müssen den asynchronen Prozess nur selbst (oder in der Bibliothek) schreiben (das offizielle Beispiel erstellt also einen "Thread" und ruft "success ()" auf).

Bedienung von "Versprechen"

then

Gibt den Rückruf an, der aufgerufen wird, wenn die Verarbeitung von "Versprechen" abgeschlossen ist. Ich denke, der häufigste Prozess ist, Context.render () im Callback aufzurufen und eine Antwort zu erstellen.

Es ist zu beachten, dass then () den Rückruf bei der Anwendung registriert und nacheinander ausführt. Betrachten Sie den folgenden Code.

@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();

Da "Promise" eine asynchrone Verarbeitung darstellt, scheint es auf den ersten Blick so, als ob das Feld "o" beim Aufruf von "o.toString ()" zeitabhängig ist. Der Aufruf von "then ()" garantiert jedoch, dass Ratpack nacheinander in der Reihenfolge der Registrierung ausgeführt wird, sodass der Wert von "o.toString ()" immer "Obj" ist (a = 1, b = 2, c = 3). ) `. Dieses Verhalten ist jedoch nicht intuitiv und verwirrend. Ich denke, Sie sollten es nicht zu oft verwenden.

map

Erstellt ein "Versprechen", das die angegebene Funktion an das Ergebnis des "Versprechens" anpasst. Es ist das gleiche wie "Karte" wie Stream.

String result = ExecHarness.yieldSingle( e -> {
    return Promise.value( "hoge" )
                  .map( String::toUpperCase );
} ).getValue();

assertThat( result ).isEqualTo( "HOGE" );

blockingMap

Es ist fast dasselbe wie "map", wird jedoch vom Thread ausgeführt, um die Verarbeitung zu blockieren. Dies ist ein Bild, das die Verarbeitung in "map" mit "Blocking.get ()" usw. umschließt. Es gibt eine abgeleitete Methode blockingOp.

flatMap

Ersetzt das Ergebnis von "Versprechen" durch "Versprechen", das von der angegebenen Funktion zurückgegeben wird. Ratpack hat eine Menge Verarbeitung, die standardmäßig "Versprechen" zurückgibt, daher wird es häufiger als erwartet verwendet.

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

Wendet die Kartenfunktion nur an, wenn das angegebene "Prädikat" positiv ist.

mapError flatMapError

Wenn eine Ausnahme auftritt, wird das Ergebnis der Anwendung der Zuordnungsfunktion zurückgegeben, die die Ausnahme als Argument verwendet. Sie können fließend Zweige schreiben, wenn diese normal enden und wenn ein Fehler auftritt.

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

Übernimmt eine Funktion, die den Aufrufer "Versprechen" selbst übernimmt und "Versprechen" zurückgibt. Ich weiß nicht, wie ich es verwenden soll, aber es scheint, dass der Zweck darin besteht, die Beschreibung zu vereinfachen, wenn der Prozess in Methoden unterteilt ist.

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

Fügen Sie die Verarbeitung vor und nach der Berechnung von "Versprechen" ein. Es sieht an sich nützlich aus, ist aber eine beschämende Methode, die den Code bedeutungslos überflüssig macht, da Sie das After-Ergebnis in ein "ExecResult" einschließen müssen.

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

Ersetzen Sie "Versprechen" durch ein anderes "Versprechen". Kurz gesagt, es ist eine Version, die das Argument "flatMap ()" nicht akzeptiert. Dies ist auch eine Methode, deren Notwendigkeit nicht gut verstanden wird.

String result = ExecHarness.yieldSingle( e -> {
    return Promise.value( "hoge" )
                  .replace( Promise.value( "piyo" ) );
} ).getValue();

assertThat( result ).isEqualTo( "piyo" );

route

Wenn "Prädikat" "wahr" ist, führen Sie den angegebenen Verbraucher aus. Wenn man sich JavaDoc ansieht, scheint es für die Datenvalidierung usw. gedacht zu sein, aber ich denke, dass es nicht sehr einfach zu bedienen ist.

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

Konvertiert "Versprechen" in einen anderen Typ. Die Verwendung mag umständlich erscheinen, wird jedoch für die Integration externer Bibliotheken verwendet. Das Folgende ist ein Beispiel für 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

Es hat einen Verbraucher, dessen Argument das Ergebnis von "Versprechen" ist. Der Rückgabewert "Versprechen" gibt das gleiche Ergebnis zurück wie das ursprüngliche "Versprechen". Es gibt abgeleitete Methoden wie "nextOp".

String result = ExecHarness.yieldSingle( e -> {
    return Promise.value( "hoge" )
                  .next( System.out::println );
} ).getValue();

assertThat( result ).isEqualTo( "hoge" );

right left

Kombiniert "Versprechen" mit einem anderen "Versprechen" und gibt es als "Versprechen" von "Paar" zurück.

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

Zwischenspeichern Sie das Ergebnis von "Versprechen". Wenn eine Ausnahme auftritt, wird diese Ausnahme ebenfalls zwischengespeichert. Es gibt abgeleitete Methoden wie "cacheResultIf".

onError

Beschreibt die Verarbeitung, wenn ein Fehler auftritt. Ich denke, die Hauptverwendung besteht darin, zum Zeitpunkt des Fehlers "Context.render ()" zu schreiben. Sie können mehrere Argumentmuster haben, z. B. eines, das eine Ausnahmeklasse als Argument verwendet, eines, das einen Ausnahmekonsumenten empfängt, und eines, das eine Ausnahme mit "Prädikat" auswählt.

close

Wenn das "Versprechen" abgeschlossen ist oder eine Ausnahme auftritt, wird das im Argument angegebene "AutoCloseable" geschlossen. Ich weiß nicht, wo ich es verwenden soll.

retry

Wenn der Vorgang fehlschlägt, wird er nach einer bestimmten Zeit erneut versucht. Dies ist praktisch, wenn Sie eine externe API aufrufen.

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

Als Argument wird ein Verbraucher herangezogen, der die Zeit zurückgibt, die für die Ausführung des Versprechens benötigt wird. Ist Leistungsmessung die Hauptanwendung?

Über Fäden und Gabel

Ratpack führt "Promise" in der Einheit aus, die durch die "Execution" -Klasse für die asynchrone Verarbeitung dargestellt wird. Normalerweise ist Ihnen diese "Ausführung" nicht bekannt, aber um mehrere "Versprechen" parallel auszuführen, müssen Sie "fork ()" Ausführung "ausführen. "Promise.fork ()" wird als praktische Methode bereitgestellt, und Sie können "Promise" problemlos in einem anderen Thread ausführen.

Der folgende Code ist eine geringfügige Änderung des Beispiels im 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" );

Wenn Sie nun den Aufruf von "fork ()" entfernen, werden "p1" und "p2" nacheinander im selben Thread ausgeführt, was zu einem Deadlock führt. Wenn Sie mit fork () einen anderen Thread erstellt haben, funktioniert dies normal.

Zusammenfassung

Ratpacks "Promise" bietet Unterstützung für die asynchrone Verarbeitung, in der Java nicht gut ist. Es gibt jedoch einige Teile, die starke Gewohnheiten haben, und es gibt viele Methoden, so dass es schwierig ist, sie richtig anzuwenden. Der Trick könnte sein, nicht zu versuchen, sehr klug zu sein. Ratpack verfügt über ein RxRatpack-Modul zur Unterstützung von RxJava. Wenn Sie mit vertrauten asynchronen Bibliotheken vertraut sind, können Sie diese auch nutzen.

Persönliche Empfehlung

Recommended Posts

Einführung in Ratpack (6) --Promise
Einführung in Ratpack (8) -Session
Einführung in Ratpack (9) - Thymeleaf
Einführung in Ratpack (2) -Architektur
Einführung in Ratpack (5) --Json & Registry
Einführung in Ratpack (7) - Guice & Spring
Einführung in Ratpack (1) - Was ist Ratpack?
Einführung in Ruby 2
Einführung in web3j
Einführung in Micronaut 1 ~ Einführung ~
[Java] Einführung in Java
Einführung in die Migration
Einführung in Java
Einführung in Doma
Einführung in Ratpack (Extra Edition) - Mit Sentry
Einführung in Ratpack (3) - Hallo Welt, detaillierte Erklärung
Einführung in JAR-Dateien
Einführung in die Bitarithmetik
Einführung in PlayFramework 2.7 ① Übersicht
Einführung in das Android-Layout
Einführung in Entwurfsmuster (Einführung)
Einführung in die praktische Programmierung
Einführung in den Befehl javadoc
Einführung in den Befehl jar
Einführung in den Lambda-Stil
Einführung in den Java-Befehl
Einführung in die Keycloak-Entwicklung
Einführung in den Befehl javac
Einführung in Entwurfsmuster (Builder)
Einführung in die Android App-Entwicklung
Einführung in Metabase ~ Umgebungskonstruktion ~
(Punktinstallation) Einführung in Java8_Impression
Einführung in Entwurfsmuster (Composite)
Einführung in Micronaut 2 ~ Unit Test ~
Einführung in JUnit (Studiennotiz)
Einführung in Spring Boot ~ ~ DI ~
Einführung in Designmuster (Fliegengewicht)
[Java] Einführung in den Lambda-Ausdruck
Einführung in Spring Boot ② ~ AOP ~
Einführung in Apache Beam (2) ~ ParDo ~
Einführung in die EHRbase 2-REST-API
Einführung in Entwurfsmuster Prototyp
[Java] Einführung in die Stream-API
Einführung in Entwurfsmuster (Iterator)
Einführung in Spring Boot Teil 1
Einführung in Entwurfsmuster (Strategie)
[Einführung in Janken (ähnliche) Spiele] Java
[Einführung in Java] Über Lambda-Ausdrücke
Einführung in Algorithmen mit Java-kumulativer Summe
Einführung in die funktionale Programmierung (Java, Javascript)
Einführung in Algorithmen mit der Java-Shakutori-Methode