1. 概要

JDeferred は、ボイラープレートコードを記述せずに非同期トポロジを実装するために使用される小さな Java ライブラリ( Groovy もサポート)です。 このフレームワークは、JqueryのPromise/Ajax機能とAndroidのDeferredObjectパターンに触発されています。

このチュートリアルでは、JDeferredとそのさまざまなユーティリティの使用方法を示します。

2. Mavenの依存関係

pom.xmlに次の依存関係を追加することで、どのアプリケーションでも JDeferred の使用を開始できます:

<dependency>
    <groupId>org.jdeferred</groupId>
    <artifactId>jdeferred-core</artifactId>
    <version>1.2.6</version>
</dependency>

JDeferred プロジェクトの最新バージョンは、CentralMavenリポジトリで確認できます。

3. 約束

エラーが発生しやすい同期REST API 呼び出しを呼び出し、APIによって返されたデータに基づいていくつかのタスクを実行する簡単な使用例を見てみましょう。

単純なJQueryでは、上記のシナリオは次の方法で対処できます。

$.ajax("/GetEmployees")
    .done(
        function() {
            alert( "success" );
        }
     )
    .fail(
        function() {
            alert( "error" );
        }
     )
    .always(
        function() {
            alert( "complete" );
        }
    );

同様に、JDeferredにはPromiseおよびDeferredインターフェースが付属しており、対応するオブジェクトにスレッドに依存しないフックを登録し、そのオブジェクトのステータスに基づいてさまざまなカスタマイズ可能なアクションをトリガーします。

ここでは、 Deferred がトリガーとして機能し、Promiseがオブザーバーとして機能します。

このタイプの非同期ワークフローは簡単に作成できます。

Deferred<String, String, String> deferred
  = new DeferredObject<>();
Promise<String, String, String> promise = deferred.promise();

promise.done(result -> System.out.println("Job done"))
  .fail(rejection -> System.out.println("Job fail"))
  .progress(progress -> System.out.println("Job is in progress"))
  .always((state, result, rejection) -> 
    System.out.println("Job execution started"));

deferred.resolve("msg");
deferred.notify("notice");
deferred.reject("oops");

ここで、各メソッドには異なるセマンティクスがあります。

  • done() –遅延オブジェクトに対する保留中のアクションが正常に完了した場合にのみトリガーされます
  • fail() –延期されたオブジェクトに対して保留中のアクションを実行しているときに、何らかの例外が発生したときにトリガーされます
  • progress() –遅延オブジェクトに対する保留中のアクションの実行が開始されるとすぐにトリガーされます
  • always() –遅延オブジェクトの状態に関係なくトリガーします

デフォルトでは、遅延オブジェクトのステータスは PENDING / REJECTED /RESOLVEDになります。 deferred.state()メソッドを使用してステータスを確認できます。

ここで注意すべき点は、遅延オブジェクトのステータスがRESOLVEDに変更されると、そのオブジェクトに対して拒否操作を実行できないことです。

同様に、オブジェクトのステータスが REJECTED、に変更されると、そのオブジェクトに対してresolveまたはnotify操作を実行できなくなります。 違反があると、IllegalStateExeptionになります。

4. フィルタ

最終結果を取得する前に、DoneFilterを使用して遅延オブジェクトに対してフィルタリングを実行できます。

フィルタリングが完了すると、スレッドセーフな遅延オブジェクトが取得されます。

private static String modifiedMsg;

static String filter(String msg) {
    Deferred<String, ?, ?> d = new DeferredObject<>();
    Promise<String, ?, ?> p = d.promise();
    Promise<String, ?, ?> filtered = p.then((result) > {
        modifiedMsg = "Hello "  result;
    });

    filtered.done(r > System.out.println("filtering done"));

    d.resolve(msg);
    return modifiedMsg;
}

5. パイプ

フィルタと同様に、 JDeferred は、 DonePipe インターフェイスを提供して、遅延オブジェクトの保留中のアクションが解決されると、高度なフィルタリング後のアクションを実行します。

public enum Result { 
    SUCCESS, FAILURE 
}; 

private static Result status; 

public static Result validate(int num) { 
    Deferred<Integer, ?, ?> d = new DeferredObject<>(); 
    Promise<Integer, ?, ?> p = d.promise(); 
    
    p.then((DonePipe<Integer, Integer, Exception, Void>) result > {
        public Deferred<Integer, Exception, Void> pipeDone(Integer result) {
            if (result < 90) {
                return new DeferredObject<Integer, Exception, Void>()
                  .resolve(result);
            } else {
                return new DeferredObject<Integer, Exception, Void>()
                  .reject(new Exception("Unacceptable value"));
            }
    }).done(r > status = Result.SUCCESS )
      .fail(r > status = Result.FAILURE );

    d.resolve(num);
    return status;
}

ここでは、実際の結果の値に基づいて、結果を拒否する例外を発生させました。

6. 延期されたマネージャー

リアルタイムのシナリオでは、複数のプロミスによって観察された複数の遅延オブジェクトを処理する必要があります。 このシナリオでは、複数のプロミスを個別に管理することは非常に困難です。

そのため、JDeferredにはDeferredManagerインターフェイスが付属しており、すべてのPromiseに共通のオブザーバーが作成されます。 したがって、この共通のオブザーバーを使用して、すべての約束に対して共通のアクションを作成できます。

Deferred<String, String, String> deferred = new DeferredObject<>();
DeferredManager dm = new DefaultDeferredManager();
Promise<String, String, String> p1 = deferred.promise(), 
  p2 = deferred.promise(), 
  p3 = deferred.promise();
dm.when(p1, p2, p3)
  .done(result -> ... )
  .fail(result -> ... );
deferred.resolve("Hello Baeldung");

カスタムスレッドプールを使用してExecutorServiceDeferredManagerに割り当てることもできます。

ExecutorService executor = Executors.newFixedThreadPool(10);
DeferredManager dm = new DefaultDeferredManager(executor);

実際、 Promise の使用を完全に無視して、Callableインターフェースを直接定義してタスクを完了することができます。

DeferredManager dm = new DefaultDeferredManager();
dm.when(() -> {
    // return something and raise an exception to interrupt the task
}).done(result -> ... )
  .fail(e -> ... );

7. スレッドセーフアクション

ほとんどの場合、非同期ワークフローを処理する必要がありますが、すべての並列タスクの結果を待つ必要がある場合もあります。

このタイプのシナリオでは、 Objectwait()メソッドを使用して、すべての遅延タスクが終了するのを待機することができます

DeferredManager dm = new DefaultDeferredManager();
Deferred<String, String, String> deferred = new DeferredObject<>();
Promise<String, String, String> p1 = deferred.promise();
Promise<String, String, String> p = dm
  .when(p1)
  .done(result -> ... )
  .fail(result -> ... );

synchronized (p) {
    while (p.isPending()) {
        try {
            p.wait();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

deferred.resolve("Hello Baeldung");

または、 PromiseインターフェイスのwaitSafely()メソッドを使用して同じことを実現できます。

try {
    p.waitSafely();
} catch (InterruptedException e) {
    e.printStackTrace();
}

上記の両方の方法はほとんど同じことを実行しますが、2番目の手順は同期を必要としないため、常に2番目の方法を使用することをお勧めします。

8. Androidの統合

JDeferred は、AndroidMavenプラグインを使用してAndroidアプリケーションと簡単に統合できます。

APKLIBビルドの場合、pom.xmlに次の依存関係を追加する必要があります。

<dependency>
    <groupId>org.jdeferred</groupId>
    <artifactId>jdeferred-android</artifactId>
    <version>1.2.6</version>
    <type>apklib</type>
</dependency>

AAR ビルドの場合、pom.xmlに次の依存関係を追加する必要があります。

<dependency>
    <groupId>org.jdeferred</groupId>
    <artifactId>jdeferred-android-aar</artifactId>
    <version>1.2.6</version>
    <type>aar</type>
</dependency>

9. 結論

このチュートリアルでは、 JDeferred について説明しましたが、これはさまざまなユーティリティです。

いつものように、完全なソースコードはGitHubから入手できます。