JDeferredへのガイド

JDeferredのガイド

1. 概要

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

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

2. メーベン依存

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


    org.jdeferred
    jdeferred-core
    1.2.6

Central Maven RepositoryJDeferredプロジェクトの最新バージョンを確認できます。

3. 約束

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

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

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

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

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

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

Deferred deferred
  = new DeferredObject<>();
Promise 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()メソッドを使用してステータスを確認できます。

ここで注意すべき点は、once a deferred object’s status is changed to RESOLVED, we can’t perform reject operation on that object.

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

4. フィルター

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

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

private static String modifiedMsg;

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

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

    d.resolve(msg);
    return modifiedMsg;
}

5. パイプ

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

public enum Result {
    SUCCESS, FAILURE
};

private static Result status;

public static Result validate(int num) {
    Deferred d = new DeferredObject<>();
    Promise p = d.promise();

    p.then((DonePipe) result > {
        public Deferred pipeDone(Integer result) {
            if (result < 90) {
                return new DeferredObject()
                  .resolve(result);
            } else {
                return new DeferredObject()
                  .reject(new Exception("Unacceptable value"));
            }
    }).done(r > status = Result.SUCCESS )
      .fail(r > status = Result.FAILURE );

    d.resolve(num);
    return status;
}

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

6. Deferred Manager

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

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

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

カスタムスレッドプールを使用して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()メソッドをwait for all deferred tasks to finishにのみ使用できます。

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

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

deferred.resolve("Hello example");

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

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

上記の方法はどちらもほぼ同じように機能しますが、2番目の手順では同期が必要ないため、常に2番目の方法を使用することをお勧めします。

8. Androidの統合

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

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


    org.jdeferred
    jdeferred-android
    1.2.6
    apklib

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


    org.jdeferred
    jdeferred-android-aar
    1.2.6
    aar

9. 結論

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

いつものように、完全なソースコードはover on GitHubで利用できます。