Перейти к основному содержанию

Promise

Обзор

Обещания - это первичные строительные блоки в модели программирования ActiveJ async, которые можно сравнить с Java Futures. Обещание представляет собой результат операции, которая еще не завершена,

Характеристики

  • В отличие от Java Futures, Promises были изначально разработаны для работы в рамках одного потока eventloop.
  • Обещания чрезвычайно легки.
  • Отсутствие многопоточности
  • Может обрабатывать миллионы звонков в секунду
  • Мощный API для объединения нескольких обещаний

Создание Promises

Мы можем создавать Promises в основном с помощью основных статических методов:

  • of(T value) - создает успешно завершенное обещание, как CompletableFuture.completedFuture().
  • ofException(Exception e) - создает исключительно завершенное обещание.
  • complete() - создает успешно завершенный Promise<Void>, ярлык Promise.of(null).
Promise<Integer> firstNumber = Promise.of(10);Promise.of("Hello World");Promise.ofException(new Exception("Something went wrong"));

Цепочка Promises

Promise будет успешным или неудачным в некоторое неопределенное время, и вам нужно составить цепочку методов, которые будут выполняться в обоих случаях:

  • then(FunctionEx<T, Promise<U>>) - возвращает новое обещание, которое получается путем отображения результата исходного обещания на некоторое другое обещание. Если исходное обещание выполнено в порядке исключения, функция отображения не будет применена. Аналогично Java CompletionStage.thenCompose(). Используется для отображения результата обещания на другое обещание.
  • then(SupplierEx<>) - возвращает новое обещание, которое получается путем вызова предоставленного поставщика нового обещания. Если первоначальное обещание выполнено в порядке исключения, поставщик не будет вызван. Используйте этот метод, если вы можете игнорировать результат исходного обещания.
  • map(FunctionEx<T, U>) - возвращает новое обещание, которое получается путем отображения результата исходного обещания на некоторое другое значение. Если исходное обещание выполнено в порядке исключения, функция отображения не будет применена. Аналогично Java CompletionStage.thenApply(). Используется для сопоставления результата обещания с каким-либо другим значением.
  • whenResult(ConsumerEx<T>) - подписывает данного потребителя на выполнение после успешного завершения исходного Promise. Аналогично Java CompletionStage.thenAccept().

Кроме того, для обработки ошибок предусмотрены следующие методы:

  • then(BiFunctionEx<T, Exception, Promise<U>>) - возвращает новое обещание, которое получается путем отображения результата и исключения исходного обещания на некоторое другое обещание. Если исходное обещание выполнено в порядке исключения, исключение, переданное в функцию mapping bi, гарантированно будет не нулевым. Принимает 2 аргумента: результат исходного обещания и исключение, представляющее неудачное обещание.
  • map(BiFunctionEx<T, Exception, U>) - возвращает новое обещание, которое получается путем отображения результата и исключения исходного обещания на некоторое другое значение. Если это обещание выполнено в порядке исключения, исключение, переданное в функцию mapping bi, гарантированно не будет нулевым. Принимает 2 аргумента: результат исходного обещания и исключение, представляющее неудачное обещание.
  • whenException(ConsumerEx<Exception>) - подписывает данного потребителя на исключение, которое будет выполнено после исключительного завершения исходного Promise.
  • whenException(RunnableEx) - подписывает заданный runnable на выполнение после исключительного завершения исходного Promise.
  • whenComplete(BiConsumerEx<T, Exception>) - подписывает данного би-потребителя на выполнение после завершения исходного Promise (успешного или исключительного). Принимает 2 аргумента: результат исходного обещания и исключение, представляющее неудачное обещание.
  • whenComplete(RunnableEx) - подписывает заданную runnable на выполнение после завершения исходного Promise (успешного или исключительного).
note

Каждый метод цепочки обещаний принимает в качестве аргумента функцию 'Ex'. Это функциональные интерфейсы, аналогичные своим аналогам из пакета java.util.function . Главное отличие заключается в том, что каждая функция 'Ex' может выбросить исключение ``.

Если функция Ex выбрасывает проверенное исключение, результирующее обещание также будет завершено в исключительном порядке. Если вместо этого будет выброшено непроверенное исключение , обещание не будет выполнено, а непроверенное исключение будет повторно выброшено как есть.

Если есть несколько асинхронных вызовов, нам нужно выполнить их по порядку. В этом случае вы можете просто создать цепочку методов , чтобы создать последовательность.

doSomeProcess()    .whenResult(result -> System.out.printf("Result of some process is '%s'%n", result))    .whenException(e -> System.out.printf("Exception after some process is '%s'%n", e.getMessage()))    .map(s -> s.toLowerCase())    .map(result -> String.format("The mapped result is '%s'", result), e -> e.getMessage())    .whenResult(s -> System.out.println(s));

Полный текст примера смотрите на GitHub.

Объединение Promises

Бывают случаи, когда необходимо выполнить несколько Promises и объединить их результаты. Для этого рассмотрим следующие статические методы из раздела Обещания класс:

  • combine() - возвращает новый Promise , который, когда оба Promiseзавершены, выполняется с двумя результатами в качестве аргументов.
  • all() - возвращает обещание , которое завершается, когда все предоставленные обещания завершены.
  • any() - возвращает одно из первых выполненных обещаний.
Promise<Integer> firstNumber = Promise.of(10);Promise<Integer> secondNumber = Promises.delay(2000, 100);
Promise<Integer> result = firstNumber.combine(secondNumber, Integer::sum);result.whenResult(res -> System.out.println("The first result is " + res));
  • delay() - задерживает завершение работы Promise на определенный период времени.
Promise<String> strPromise = Promises.delay("result", Duration.seconds(10))

Особенности оптимизации

ActiveJ Promise в значительной степени оптимизирован под GC:

  • Внутреннее представление типичного Promise состоит из 1-2 объектов с минимальным количеством полей внутри.
  • После выполнения обещания , он передает результат своим подписчикам, а затем отбрасывает результат.

Чтобы оптимизировать Promises, существует несколько реализаций интерфейса Promise :

graph TD Promise --> AbstractPromise Promise --> CompleteExceptionallyPromise Promise --> CompletePromise AbstractPromise --> NextPromise AbstractPromise --> SettablePromise CompletePromise --> CompleteResultPromise CompletePromise --> CompleteNullPromise
  • Promise - корневой интерфейс, который представляет обещания поведения.
  • SettablePromise - может использоваться как корень для цепочки Promises. Позволяет обернуть операции в Promises, могут быть завершены вручную даже до фактического завершения.
  • AbstractPromise, NextPromise - вспомогательные классы для создания цепочек нестационарных обещаний. Вы можете рассматривать эти цепочки как трубы, по которым передаются значения, но не хранятся.
  • CompletePromise - абстрактный класс, представляющий успешно завершенное обещание.
  • CompleteExceptionallyPromise, CompleteResultPromise, CompleteNullPromise - вспомогательные классы.

Бенчмарки

Мы сравнили ActiveJ Promise с Java CompletableFuture в различных сценариях:

  1. ActiveJ Promise/Java CompletableFuture выполняет операции с одним обещанием/будущим.
  2. ActiveJ Promise/Java CompletableFuture объединяет несколько обещаний/фьючерсов.

Мы использовали JMH в качестве эталонного инструмента и выполняли тесты в режиме AverageTime . Все измерения представлены в наносекундах.

ActiveJ Promise oneCallMeasureCnt: 10; Score: 12.952; Error: ± 0.693; Units: ns/op;
ActiveJ Promise combineMeasureCnt: 10; Score: 34.112; Error: ± 1.869; Units: ns/op;
Java CompletableFuture oneCallMeasureCnt: 10; Score: 85.151; Error: ± 1.781; Units: ns/op;
Java CompletableFuture combineMeasureCnt: 10; Score: 153.645; Error: ± 4.491; Units: ns/op;

Вы можете найти эталонные источники на GitHub

Примеры

note

Чтобы запустить примеры, необходимо клонировать ActiveJ с GitHub

git clone https://github.com/activej/activej

И импортируйте его как проект Maven. Посмотрите тег v5.0. Перед запуском примеров выполните сборку проекта. Эти примеры расположены по адресу activej/examples/core/promise.

PromiseChainExample

Вы можете создавать цепочки Обещаниеs еще до их завершения, и вы еще не знаете, завершатся ли они успешно или с исключением. В этом примере у нас есть doSomeProcess , который возвращает Promise , имеющий равные шансы завершиться либо успешно, либо с исключением. Поэтому мы создаем цепочку, которая будет обрабатывать оба случая:

@SuppressWarnings("Convert2MethodRef")public class PromiseChainExample {  private static final Eventloop eventloop = Eventloop.create().withCurrentThread();
  public static void main(String[] args) {    //[START REGION_1]    doSomeProcess()        .whenResult(result -> System.out.printf("Result of some process is '%s'%n", result))        .whenException(e -> System.out.printf("Exception after some process is '%s'%n", e.getMessage()))        .map(s -> s.toLowerCase())        .map(result -> String.format("The mapped result is '%s'", result), e -> e.getMessage())        .whenResult(s -> System.out.println(s));    //[END REGION_1]    Promise.complete()        .then(PromiseChainExample::loadData)        .whenResult(result -> System.out.printf("Loaded data is '%s'%n", result));    eventloop.run();  }
  private static Promise<String> loadData() {    return Promise.of("Hello World");  }
  public static Promise<String> doSomeProcess() {    return Promises.delay(1000, Math.random() > 0.5 ?        Promise.of("Hello World") :        Promise.ofException(new RuntimeException("Something went wrong")));  }}

Если вы запустите пример, вы получите либо такой вывод (если doSomeProcess завершится успешно):

Загруженные данные - 'Hello World'Результат некоторого процесса - 'Hello World'Сопоставленный результат - 'hello world'.

Или это, если он завершается с исключением:

Загруженные данные - 'Hello World'Исключение после некоторого процесса - 'Something went wrong'Something went wrong

Обратите внимание, что первая строка

Загруженные данные - 'Hello World'

Это связано с 1-секундной задержкой, которую мы установили в doSomeProcess.

Полный текст примера смотрите на GitHub.

PromiseAdvancedExample

Вы можете объединить несколько Promises, например:

Promise<Integer> firstNumber = Promise.of(10);Promise<Integer> secondNumber = Promises.delay(2000, 100);
Promise<Integer> result = firstNumber.combine(secondNumber, Integer::sum);result.whenResult(res -> System.out.println("The first result is " + res));

Существует также несколько способов отсрочки Promise:

int someValue = 1000;int delay = 1000;     // in millisecondsint interval = 2000;  // also in millisecondsPromise<Integer> intervalPromise = Promises.interval(interval, Promise.of(someValue));Promise<Integer> schedulePromise = Promises.schedule(someValue * 2, Instant.now());Promise<Integer> delayPromise = Promises.delay(delay, someValue);
Promise<Integer> result = intervalPromise    .combine(schedulePromise, (first, second) -> first - second)    .combine(delayPromise, Integer::sum);
result.whenResult(res -> System.out.println("The second result is " + res));

Полный текст примера смотрите на GitHub.

Примеры Promises

Promises - это вспомогательный класс, который позволяет эффективно управлять несколькими Promises. В данном примере будут продемонстрированы три варианта использования.

  • В следующем примере мы используем Promises loop, который напоминает Java для loop, но имеет асинхронные возможности, которые обеспечиваются Promise:
Promises.loop(0,    i -> i < 5,    i -> {      System.out.println("This is iteration #" + i);      return Promise.of(i + 1);    });

Вывод:

Цикл с условием:Это итерация #1Это итерация #2Это итерация #3Это итерация #4Это итерация #5

2.Другой пример создает список Promises результатов, используя метод Promises toList :

Promises.toList(Promise.of(1), Promise.of(2), Promise.of(3), Promise.of(4), Promise.of(5), Promise.of(6))    .whenResult(list -> System.out.println("Size of collected list: " + list.size() + "\nList: " + list));

Вывод:

Сбор группы **Победы** в список **Победы**' результаты:Размер собранного списка: 6Список: [1, 2, 3, 4, 5, 6]

3.В последнем примере Promises toArray используется метод, который уменьшает promises до массива данных заданного типа (в данном случае Integers):

Promises.toArray(Integer.class, Promise.of(1), Promise.of(2), Promise.of(3), Promise.of(4), Promise.of(5), Promise.of(6))    .whenResult(array -> System.out.println("Size of collected array: " + array.length + "\nArray: " + Arrays.toString(array)));

И конечный результат таков:

Сбор группы **Promises** в массив результатов **Promises**':Размер собранного массива: 6Массив: [1, 2, 3, 4, 5, 6]

Полный текст примера смотрите на GitHub.

AsyncFileServiceExample

Также вы можете использовать Promises для работы с файловой системой. Когда вы запустите этот пример:

``java url=/examples/core/promise/src/main/java/AsyncFileServiceExample.java tag=REGION_1

... вы получите следующий вывод, который представляет собой содержимое созданного файла:
```textЗдравствуйтеЭто тестовый файлЭто 3-я строка в файле```
**<Githublink url='/examples/core/promise/src/main/java/AsyncFileServiceExample.java'>Полный текст примера смотрите на GitHub.</Githublink>**