this blog で、コールバック地獄の this (次のコードをコピーして貼り付けた)例を示しています。ただし、Reactive Extensionsを使用して問題を解決する方法については言及されていません。
したがって、F3はF1の完了に依存し、F4とF5はF2の完了に依存します。
注:現在Rxに頭を抱えているので、この質問をする前にこの例を解こうとしませんでした。
import Java.util.concurrent.CountDownLatch;
import Java.util.concurrent.ExecutorService;
import Java.util.concurrent.LinkedBlockingQueue;
import Java.util.concurrent.ThreadPoolExecutor;
import Java.util.concurrent.TimeUnit;
import Java.util.concurrent.atomic.AtomicReference;
public class CallbackB {
/**
* Demonstration of nested callbacks which then need to composes their responses together.
* <p>
* Various different approaches for composition can be done but eventually they end up relying upon
* synchronization techniques such as the CountDownLatch used here or converge on callback design
* changes similar to <a href="https://github.com/Netflix/RxJava">Rx</a>.
*/
public static void run() throws Exception {
final ExecutorService executor = new ThreadPoolExecutor(4, 4, 1, TimeUnit.MINUTES, new LinkedBlockingQueue<Runnable>());
/* the following are used to synchronize and compose the asynchronous callbacks */
final CountDownLatch latch = new CountDownLatch(3);
final AtomicReference<String> f3Value = new AtomicReference<String>();
final AtomicReference<Integer> f4Value = new AtomicReference<Integer>();
final AtomicReference<Integer> f5Value = new AtomicReference<Integer>();
try {
// get f3 with dependent result from f1
executor.execute(new CallToRemoteServiceA(new Callback<String>() {
@Override
public void call(String f1) {
executor.execute(new CallToRemoteServiceC(new Callback<String>() {
@Override
public void call(String f3) {
// we have f1 and f3 now need to compose with others
System.out.println("intermediate callback: " + f3 + " => " + ("f4 * f5"));
// set to thread-safe variable accessible by external scope
f3Value.set(f3);
latch.countDown();
}
}, f1));
}
}));
// get f4/f5 after dependency f2 completes
executor.execute(new CallToRemoteServiceB(new Callback<Integer>() {
@Override
public void call(Integer f2) {
executor.execute(new CallToRemoteServiceD(new Callback<Integer>() {
@Override
public void call(Integer f4) {
// we have f2 and f4 now need to compose with others
System.out.println("intermediate callback: f3" + " => " + (f4 + " * f5"));
// set to thread-safe variable accessible by external scope
f4Value.set(f4);
latch.countDown();
}
}, f2));
executor.execute(new CallToRemoteServiceE(new Callback<Integer>() {
@Override
public void call(Integer f5) {
// we have f2 and f5 now need to compose with others
System.out.println("intermediate callback: f3" + " => " + ("f4 * " + f5));
// set to thread-safe variable accessible by external scope
f5Value.set(f5);
latch.countDown();
}
}, f2));
}
}));
/* we must wait for all callbacks to complete */
latch.await();
System.out.println(f3Value.get() + " => " + (f4Value.get() * f5Value.get()));
} finally {
executor.shutdownNow();
}
}
public static void main(String[] args) {
try {
run();
} catch (Exception e) {
e.printStackTrace();
}
}
private static final class CallToRemoteServiceA implements Runnable {
private final Callback<String> callback;
private CallToRemoteServiceA(Callback<String> callback) {
this.callback = callback;
}
@Override
public void run() {
// simulate fetching data from remote service
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
callback.call("responseA");
}
}
private static final class CallToRemoteServiceB implements Runnable {
private final Callback<Integer> callback;
private CallToRemoteServiceB(Callback<Integer> callback) {
this.callback = callback;
}
@Override
public void run() {
// simulate fetching data from remote service
try {
Thread.sleep(40);
} catch (InterruptedException e) {
e.printStackTrace();
}
callback.call(100);
}
}
private static final class CallToRemoteServiceC implements Runnable {
private final Callback<String> callback;
private final String dependencyFromA;
private CallToRemoteServiceC(Callback<String> callback, String dependencyFromA) {
this.callback = callback;
this.dependencyFromA = dependencyFromA;
}
@Override
public void run() {
// simulate fetching data from remote service
try {
Thread.sleep(60);
} catch (InterruptedException e) {
e.printStackTrace();
}
callback.call("responseB_" + dependencyFromA);
}
}
private static final class CallToRemoteServiceD implements Runnable {
private final Callback<Integer> callback;
private final Integer dependencyFromB;
private CallToRemoteServiceD(Callback<Integer> callback, Integer dependencyFromB) {
this.callback = callback;
this.dependencyFromB = dependencyFromB;
}
@Override
public void run() {
// simulate fetching data from remote service
try {
Thread.sleep(140);
} catch (InterruptedException e) {
e.printStackTrace();
}
callback.call(40 + dependencyFromB);
}
}
private static final class CallToRemoteServiceE implements Runnable {
private final Callback<Integer> callback;
private final Integer dependencyFromB;
private CallToRemoteServiceE(Callback<Integer> callback, Integer dependencyFromB) {
this.callback = callback;
this.dependencyFromB = dependencyFromB;
}
@Override
public void run() {
// simulate fetching data from remote service
try {
Thread.sleep(55);
} catch (InterruptedException e) {
e.printStackTrace();
}
callback.call(5000 + dependencyFromB);
}
}
private static interface Callback<T> {
public void call(T value);
}
}
私は、コールバックとJava Futuresについて言及されているブログ投稿の元の作者です。サービスの構成を非同期で行うためのflatMap、Zip、およびmergeの使用例を次に示します。
Userオブジェクトをフェッチし、SocialデータとPersonalizedCatalogデータを同時にフェッチします。次に、PersonalizedCatalogからの各ビデオについて、ブックマーク、評価、メタデータを同時にフェッチし、それらをまとめて圧縮し、すべての応答をサーバー送信イベントとしてプログレッシブストリーム出力にマージします。 。
return getUser(userId).flatMap(user -> {
Observable<Map<String, Object>> catalog = getPersonalizedCatalog(user)
.flatMap(catalogList -> catalogList.videos().<Map<String, Object>> flatMap(
video -> {
Observable<Bookmark> bookmark = getBookmark(video);
Observable<Rating> rating = getRatings(video);
Observable<VideoMetadata> metadata = getVideoMetadata(video);
return Observable.Zip(bookmark, rating, metadata, (b, r, m) -> combineVideoData(video, b, r, m));
}));
Observable<Map<String, Object>> social = getSocial(user).map(s -> {
return s.getDataAsMap();
});
return Observable.merge(catalog, social);
}).flatMap(data -> {
String json = SimpleJson.mapToJson(data);
return response.writeStringAndFlush("data: " + json + "\n");
});
ここにすべての情報を提供することはできないので、プレゼンテーションの説明(ビデオへのリンク付き)も https://speakerdeck.com/benjchristensen/reactive-streams-with-rx- at-javaone-2014?slide = 32 。
あなたのコードによると。リモート呼び出しがObservable
を使用して行われると仮定します。
Observable<Integer> callRemoveServiceA() { /* async call */ }
/* .... */
Observable<Integer> callRemoveServiceE(Integer f2) { /* async call */ }
あなたが欲しいもの :
serviceA
を呼び出し、次にserviceB
の結果でserviceA
を呼び出しますserviceC
を呼び出し、次にserviceD
とserviceE
を呼び出し、serviceC
の結果を返しますserviceE
とserviceD
の結果を使用して、新しい値を作成しますserviceB
の結果で新しい値を表示するRxJavaでは、次のコードでこれを実現します。
Observable<Integer> f3 = callRemoveServiceA() // call serviceA
// call serviceB with the result of serviceA
.flatMap((f1) -> callRemoveServiceB(f1));
Observable<Integer> f4Andf5 = callRemoveServiceC() // call serviceC
// call serviceD and serviceE then build a new value
.flatMap((f2) -> callRemoveServiceD(f2).zipWith(callRemoveServiceE(f2), (f4, f5) -> f4 * f5));
// compute the string to display from f3, and the f4, f5 pair
f3.zipWith(f4Andf5, (childF3, childF4Andf5) -> childF3 + " => " + childF4Andf5)
// display the value
.subscribe(System.out::println);
ここで重要なのは、flapMap
とZip
(またはzipWith
)の使用です。
flapMap
は、値を別のObservable
に変換します。このObservable
は、ここで新しい非同期呼び出しになります。 ( http://reactivex.io/documentation/operators/flatmap.html )Zip
は、2つの異なるObservable
から新しい値を作成します。したがって、2つ(またはそれ以上)の結果で新しい値を作成できますObsevable
( http://reactivex.io/documentation/operators/Zip.html )ここでflapMapに関する詳細情報を取得できます。 RxJavaでmapとflatMapをいつ使用しますか?