Web API 呼出しと Callback (J)

Web API 呼出し

こちら で使い方を紹介したRetrofit は、 同期/非同期呼び出しの両方に対応しています。 非同期呼び出しは、Callback という仕掛けを使っています。

asyncCall.png

Call<List<Repo>> repos2 = service.listRepos("icpc2019-konan");
repos2.enqueue(new Callback<List<Repo>>() {
    @Override
    public void onResponse(Call<List<Repo>> call, Response<List<Repo>> response) {
        if (response.isSuccessful()) {
            List<Repo> result = response.body();
            System.out.println("Success (Async): " + result);
        } else {
            System.err.println("Failed (with Response, Async): " + response.message());
        }
    }
    @Override
    public void onFailure(Call<List<Repo>> call, Throwable t) {
        System.err.println("Failed (without Response, Async.): " + t.getMessage());
        // throw t; で例外発生させても良い
    }
});
System.out.println("Don't wait the completion!");
  • enqueue()の段階でリクエストの発行(依頼)をおこなうが、結果は待たないで、代わりに結果が返ってきた際の処理をおこなう Callback を渡す。
    • Callback はインタフェイスで、結果が返ってきたときに呼ばれる onResponse と、返ってこなかったときに呼ばれる onFailureを提供
    • 結果が返ってきたといっても、「失敗(例えば、対応するアカウントないよとか)」を意味する返事が返ることもある
  • enqueue()の段階では結果を待たないので、callback の実行より、次の行のprintln("Dont..."の方が(多分)先に実行される。
  • onResponse() が終わった段階でプログラムが完了なので、System.exit(0);で終了させています。

プログラムの制御の流れが一見して見にくくなってしまいましたね。 まあでも、イベント駆動的にプログラムを処理している場合はあまり問題ないかと思います。

一方で、一連の外部問い合わせを処理を記述している場合は、callback の中で別のリクエストを発行して、その処理の callback を書いてと、結構なスパゲティになることもあります。

さて、callback ですが、誰が実行しているかというと、callback などの処理用のスレッドが起動され、Executor として稼働しています。 一方で、このスレッドはデーモン指定されていないため、プログラム終了時はSystem.exit(0);でプログラムを終了させて下さい。

Future

呼出し側のスレッドで、Web API の結果をつかって処理をしたい場合、で説明した Future を利用しても構いません。Future の中で Retrofit の同期呼出しをした例は、こちらに載せておきます。 例えば、複数の Web API の結果を集めてなにかしたい場合、Callback だと、「結果を集める」のが大変なので。こんな感じ。

Future<Result> result1 = executor.submit(()->...);
Future<Result> result2 = executor.submit(()->...);
sum = result1.get() + result2.get()

ってことで、Web API を呼ぶ場合のコードサンプルです。

System.out.println("Start Future");
ExecutorService executor = Executors.newSingleThreadExecutor();
Call<List<Repo>> repos3 = service.listRepos("icpc2019-konan");
Future<List<Repo>> future = executor.submit(()-> {
    // ここは同期問合せと一緒
    Response<List<Repo>> response2 = repos3.execute();
    if (response2.isSuccessful()) {
        return response2.body();
    } else {
        System.err.println("Failed (in Future).");
        return null;
    }
});
// 仕事を待つ間別の仕事をしてもよい。
List<Repo> result = future.get();
if(result != null) {
    System.out.println("Success (Future): " + result);
}