CompletableFuture.runAsyn c Исключение исключений - PullRequest
0 голосов
/ 22 января 2020

Доброе утро,

Я не совсем знаком с CompletableFutures (я опытный разработчик, но я не нахожу их особенно интуитивными!).

Учитывая следующий фрагмент :

public CompletionStage<Void> leaveGame(GameService gameService)
{
  return gameService.deregister(playerName)
                    .exceptionally(t -> {
                      LOGGER.info("Could not deregister: {}", t.getMessage());
                      throw new CompletionException(t);
                    });
}

, который вызывается модульным тестом:

@Test
public void shouldCompleteExceptionallyForFailedLeave()
{
  var failFlow = new CompletableFuture<Void>();
  failFlow.completeExceptionally(new Exception("TestNonExistentPlayer"));
  when(mockedGameService.deregister(any(String.class))).thenReturn(failFlow);

  try
  {
    player.leaveGame(mockedGameService).toCompletableFuture().get();
    fail("Exception should have been thrown");
  }
  catch (Exception e)
  {
    assertEquals(Exception.class, e.getCause().getClass());
  }
  verify(mockedGameService, times(1)).deregister(any(String.class));
}

, который высмеивает gameService.deregister(...) для завершения исключения и возврата Exception.

В вышеупомянутом случае как и ожидалось, запускается исключительно ветвь, сообщение регистрируется, и исключение в модульном тесте перехватывается, т. е. утверждение fail(...) не срабатывает.

Однако, когда я хочу запустить CompletionStage перед выходом из игры, например:

public CompletionStage<Void> leaveGame(GameService gameService)
{
  return CompletableFuture.runAsync(() -> System.out.println("BLAH"))
                          .thenRun(() -> gameService.deregister(playerName)
                                                    .exceptionally(t -> {
                                                      LOGGER.info("Could not deregister: {}", t.getMessage());
                                                      throw new CompletionException(t);
                                                    }));
}

Исключительно ветвь является все еще сработала, но исключение теперь не перехватывается тестовым методом, то есть утверждение fail(...) является сработало.

Что я делаю не так?

Заранее большое спасибо!

1 Ответ

1 голос
/ 22 января 2020

Исходное определение

public CompletionStage<Void> leaveGame(GameService gameService)
{
  return gameService.deregister(playerName)
                    .exceptionally(t -> {
                      LOGGER.info("Could not deregister: {}", t.getMessage());
                      throw new CompletionException(t);
                    });
}

Метод leaveGame не выдает исключение, но всегда возвращает будущее. Вызывающий должен изучить будущее, чтобы выяснить, не завершилась ли инкапсулированная операция.

Аналогично, когда вы перемещаете тот же код в Runnable как

public CompletionStage<Void> leaveGame(GameService gameService)
{
    return CompletableFuture.runAsync(() -> System.out.println("BLAH"))
        .thenRun(() -> gameService.deregister(playerName)
                                  .exceptionally(t -> {
                                    LOGGER.info("Could not deregister: {}", t.getMessage());
                                    throw new CompletionException(t);
                                  }));
}

Runnable будет не исключение. По-прежнему необходимо изучить будущее, возвращаемое gameService.deregister(…).exceptionally(…), чтобы выяснить, не удалось ли это, но теперь вы не возвращаете его, а просто отбрасываете ссылку.

Чтобы создать будущее, завершение которого зависит от возвращенного будущего при оценке функции вам нужно thenCompose:

public CompletionStage<Void> leaveGame(GameService gameService)
{
    return CompletableFuture.runAsync(() -> System.out.println("BLAH"))
        .thenCompose(voidArg -> gameService.deregister(playerName)
                                  .exceptionally(t -> {
                                    LOGGER.info("Could not deregister: {}", t.getMessage());
                                    throw new CompletionException(t);
                                  }));
}

Так что теперь вы реализуете Function<Void,CompletionStage<Void>> вместо Runnable, и этап, возвращаемый функцией, будет используйте для завершения будущего, возвращаемого leaveGame.

...