Как провести рефакторинг цепочки асинхронных вызовов в vertx, чтобы избежать ада обратных вызовов

У меня есть следующий код с несколькими асинхронными вызовами, зависящими друг от друга (например, вызовы могут быть apis REST) ​​и, в конце концов, обработать все результаты. Это мой пример кода:

private void foo1(String uuid, Handler<AsyncResult<JsonObject>> aHandler) {
    //call json api, for example
    JsonObject foo1 = new JsonObject();
    foo1.put("uuid", "foo1");
    aHandler.handle(Future.succeededFuture(foo1));
 }

private void foo2(String uuid, Handler<AsyncResult<JsonObject>> aHandler) {

    //call json api, for example
    JsonObject foo2 = new JsonObject();
    foo2.put("uuid", "foo2");
    aHandler.handle(Future.succeededFuture(foo2));
 }

private void foo3(String uuid, Handler<AsyncResult<JsonObject>> aHandler) {

    //call json api, for example
    JsonObject foo3 = new JsonObject();
    foo3.put("uuid", "foo3");
    aHandler.handle(Future.succeededFuture(foo3));
 }

private void doSomething(JsonObject result1, JsonObject result2, JsonObject result3, Handler<AsyncResult<JsonObject>> aHandler) {
    JsonObject finalResult =new JsonObject();
    aHandler.handle(Future.succeededFuture(finalResult));
}

private void processToRefactor (String uuid, Handler<AsyncResult<JsonObject>> aHandler) {

    foo1(uuid, ar -> {
        if (ar.succeeded()) {
            JsonObject foo1 = ar.result();
            foo2(foo1.getString("uuid"), ar2 ->{
                if (ar2.succeeded()) {
                    JsonObject foo2 = ar2.result();
                    foo3(foo2.getString("uuid"), ar3 -> {
                        if (ar3.succeeded()) {
                            JsonObject foo3 = ar3.result();
                            doSomething(foo1, foo2, foo3, aHandler);
                        } else {
                            ar3.cause().printStackTrace();
                        }
                    });
                } else {
                    ar2.cause().printStackTrace();
                }
            });
        } else {
            ar.cause().printStackTrace();
        }

    });
}

В предыдущем коде у меня есть все результаты, доступные для использования в методе «doSomething», если все вызовы были успешными. Я попытался реорганизовать этот код, используя простой пример «HelloWord» по следующей ссылке https://streamdata.io/blog/vert-x-and-the-async-calls-chain/

Это мой результат:

    private void process(String uuid, Handler<AsyncResult<JsonObject>> aHandler) {

        Future<JsonObject> future = Future.future();
        future.setHandler(aHandler);

        Future<JsonObject> futureFoo1 = Future.future();

        foo1(uuid, futureFoo1);

        futureFoo1.compose(resultFoo1 -> {
            Future<JsonObject> futureFoo2 = Future.future();
            foo2(resultFoo1.getString("uuid"), futureFoo2);

            return futureFoo2; 
        }).compose(resultFoo2 ->{
            Future<JsonObject> futureFoo3 = Future.future();
            foo3(resultFoo2.getString("uuid"), futureFoo3);

            return futureFoo3;

        }).compose(resultFoo3 -> {

            // How to get result1, result2 and result3?
//            doSomething(resultFoo1, resultFoo2, resultFoo3, aHandler);

        }, future);
    }

Новый код чище и понятнее, но при использовании compose в момент вызова функции «doSomething» у меня нет всех доступных результатов вызовов. Как мне получить все результаты в конце цепочки?

С другой стороны, как поступить, если один из методов вызова API возвращает массив? То есть для каждого элемента массива применяется цепочка функций, независимо от того, что у одних есть результаты, а у других нет. Например:

private void foo1Array(String uuid, Handler<AsyncResult<JsonArray>> aHandler) {

    //call json api that return array, for example
    JsonArray result = new JsonArray();
    JsonObject foo1 = new JsonObject();
    foo1.put("uuid", "foo1");

    JsonObject foo2 = new JsonObject();
    foo1.put("uuid", "foo2");

    JsonObject foo3 = new JsonObject();
    foo1.put("uuid", "foo3");

    result.add(foo1);
    result.add(foo2);
    result.add(foo3);

    aHandler.handle(Future.succeededFuture(result));
 }   
private void processArray(String uuid, Handler<AsyncResult<JsonObject>> aHandler) {

    Future<JsonObject> future = Future.future();
    future.setHandler(aHandler);

    Future<JsonArray> futureFoo1 = Future.future();

    foo1Array(uuid, futureFoo1);

    futureFoo1.compose(resultArray -> {
        List<Future> futures = new ArrayList<Future>();
        for (int i = 0; i < resultArray.size(); i ++) {
            JsonObject resultFoo1 = resultArray.getJsonObject(i);

            Future<JsonObject> futureFoo2 = Future.future();
            foo2(resultFoo1.getString("uuid"), aHandler);
            futures.add(futureFoo2);
        }

        CompositeFuture.any(futures).setHandler(ar -> {
            //What to do here?
        });

    }, future);
}

Как вызвать функции foo2, foo3, ... с результатом foo1Array, а затем использовать его в doSomething?

Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
6
0
3 233
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Если вы хотите более простой и без адского кода обратного вызова, переключитесь на rx, ваш приведенный выше код превратится во что-то вроде этого:

private Single<JsonObject> foo1(String uuid) {
    JsonObject foo1 = new JsonObject();
    foo1.put("uuid", "foo1");
    return Single.just(foo1);
 }

private Single<JsonObject> foo2(String uuid) {
    JsonObject foo2 = new JsonObject();
    foo2.put("uuid", "foo2");
    return Single.just(foo2);
 }

private Single<JsonObject> foo3(String uuid) {
    JsonObject foo3 = new JsonObject();
    foo3.put("uuid", "foo3");
    return Single.just(foo3);
 }

private Single<JsonObject> doSomething(String uuid) {
    JsonObject finalResult =new JsonObject();
    return Single.zip(foo1(uuid), foo2(uuid), foo3(uuid))
    .map(results -> {
        // Map zip results to finalResult
        return finalResult;
    });
}

private void processToRefactor (String uuid, Handler<AsyncResult<JsonObject>> aHandler) {
    doSomething(uuid).subscribe();
}
Ответ принят как подходящий

Ваш первоначальный подход на самом деле не так уж и плох.

Чтобы улучшить код для лучшей «компоновки», вы должны изменить входной аргумент обработчика каждого метода fooX на что-то, что расширяет Handler<AsyncResult<JsonObject>> (например, Future) и в результате возвращает тот же обработчик, чтобы его можно было лучше использовать в `Future. compose, потому что переданный обработчик может использоваться как возвращаемое значение для каждого compose:

 private <T extends Handler<AsyncResult<JsonObject>>> T foo1(String uuid, T aHandler) {
    JsonObject foo1 = new JsonObject().put("uuid", "foo1");
    aHandler.handle(Future.succeededFuture(foo1));
    return aHandler; //<-- return the handler here
}

Во-вторых, чтобы получить доступ ко всем трем результатам на заключительном этапе, вы должны объявить три фьючерса вне цепочки. Теперь вы можете легко связать фьючерсы, используя вывод каждого метода foo в качестве результата для каждой композиции.

Future<JsonObject> futureFoo1 = Future.future();
Future<JsonObject> futureFoo2 = Future.future();
Future<JsonObject> futureFoo3 = Future.future();


foo1(uuid, futureFoo1).compose(resultFoo1 -> foo2(resultFoo1.getString("uuid"), futureFoo2))
                      .compose(resultFoo2 -> foo3(resultFoo2.getString("uuid"), futureFoo3))
                      .compose(resultFoo3 -> doSomething(futureFoo1.result(), //access results from 1st call
                                                         futureFoo2.result(), //access results from 2nd call 
                                                         resultFoo3,
                                                         Future.<JsonObject>future().setHandler(aHandler))); //pass the final result to the original handler

Если вы не можете жить с «примесью» этого подхода (определение фьючерсов вне цепочки и изменение их внутри функции), вы должны передать исходные входные значения для каждого метода (= результат предыдущего вызова) вместе с результат, но я сомневаюсь, что это сделало бы код более читабельным.

Чтобы изменить тип в одном методе компоновки, метод fooX должен сделать преобразование, возвращающее не исходный обработчик, а новое будущее с другим типом

private Future<JsonArray> foo2(String uuid, Handler<AsyncResult<JsonObject>> aHandler) {
    JsonObject foo2 = new JsonObject();
    foo2.put("uuid", "foo2" + uuid);
    aHandler.handle(Future.succeededFuture(foo2));
    JsonArray arr = new JsonArray().add("123").add("456").add("789");
    return Future.succeededFuture(arr);
}

спасибо, первое решение кажется наиболее подходящим и работает для меня.

oscar 23.04.2018 12:20

Другие вопросы по теме