Ядро Реактивного инструментария библиотека реализует Модель асинхронной обработки на основе обещания несколько похожа (но не идентична) на модель, реализованную ECMAScript 2015.
Вероятно, главное отличие заключается в том, что Обещание реализованное библиотекой, имеет только два состояния ( ожидание и разрешен ) в отличие от трех состояний в ECMAScript ( на рассмотрении , решено и отклонено ). Это различие вызвано необходимостью ECMAScript для реализации обработки ошибок (следовательно, третье отклоненное состояние), в то время как Ядро реактивного инструментария использует Модель обработки ошибок на основе результата и не требует отдельного состояния для ошибки.
Необходимость поддержки только двух государств значительно упрощает реализацию. Обещание , реализованное в библиотеке, намного проще, чем, например, Завершаемое будущее и гораздо более легкое. Этот подход также позволяет удобно обрабатывать сложные сценарии взаимодействия с помощью плавного и прозрачного синтаксиса.
Давайте рассмотрим эту реализацию немного глубже.
Обещания могут быть созданы как в состоянии ожидания, так и в состоянии разрешения:
final var pendigPromise = Promise.promise(); final var resolvedPromise = Promise. fulfilled(123);
Только первое разрешение принимается и используется в качестве результата экземпляра Обещание . Все последующие попытки разрешить promise будут проигнорированы.
Пользователь может прикреплять действия к обещанию. Эти действия будут выполнены, как только обещание будет выполнено:
final var holder = new AtomicInteger(-1); final var promise = Promise.promise() .onSuccess(holder::set);
Если затем мы вызовем promise.ok(1) экземпляр будет разрешен и присоединенное действие выполнено. Прикрепленные действия выполняются только один раз. Если экземпляр уже разрешен к моменту присоединения нового действия, то действие выполняется немедленно, в контексте вызывающего потока. Если экземпляр еще не разрешен, то прикрепленные действия будут выполняться в контексте потока, в котором разрешено обещание.
Обещание имеет встроенный планировщик, который настроен на большое количество небольших и коротких задач, которые не блокируются. Этот планировщик можно использовать для асинхронного выполнения некоторых действий с обещанием, асинхронного выполнения разрешения обещания или планирования действий с обещанием по истечении указанного времени ожидания. Тайм-ауты реализуются с очень небольшими накладными расходами и без каких-либо дополнительных потоков.
Больше примеров:
// create instance and immediately invoke code which uses this instance final var promise = Promise.promise(p -> p.resolve(service.createNewToken(user))) // Set promise processing timeout final var promise = Promise. promise() .when(Timeout.timeout(30).seconds(), Errors.TIMEOUT::asFailure); // Resolve promise asynchronously, so all attached actions will be run in other thread promise.asyncResolve(Result.success("ok"));
Иногда необходимо произвести преобразование полученного результата, прежде чем передавать его дальше. Для этой цели Обещание имеет метод карта() :
final Promisepromise = service.promiseRandomInteger() .map(Objects::toString);
Теперь стоит рассмотреть более сложные сценарии.
Обещание.любой() и Обещание.любой успех()
Иногда необходимо получить хотя бы один ответ от разных служб. Если какой-либо ответ подходит, то можно использовать статический Promise.any() :
final Promisepromise = Promise.any(service1.promiseInteger(), service2.promiseInteger());
Первое разрешенное обещание будет возвращено в качестве результата, независимо от того, было ли оно успешным или неудачным. Но в большинстве случаев важен только успешный результат, поможет следующий метод:
final Promisepromise = Promise.anySuccess(service1.promiseInteger(), service2.promiseInteger());
В этом случае обещание будет выполнено как сбой только в том случае, если все обещания будут выполнены как сбой.
Еще одним важным свойством этого метода является то, что он отменит (т.Е. разрешит с помощью особого типа сбоя) все оставшиеся невыполненные обещания после достижения успеха.
Обещаю.все()
Довольно часто необходимо получить несколько результатов, прежде чем выполнять дальнейшую обработку. Набор статических Promise.all() метод служит именно этой цели – результирующее обещание будет выполнено успешно только тогда, когда все переданные обещания будут выполнены успешно. Успешный результат в этом случае возвращается как Кортеж :
final Promise> promise = Promise.all(integerService.promiseInteger(), stringService.promiseString(), uuidService.promiseUuid());
Иногда необходимо получить некоторые промежуточные значения, а затем передать их еще одному сервису, который рассчитает результат. Другими словами, это случай зависимых вызовов, которые возвращают обещания. Для этой цели библиотека предоставила специальный метод:
Обещание.цепная карта()
Приведенный ниже пример иллюстрирует использование этого метода:
private ArticleService articleService;
private TopicService topicService;
private UserService userService;
public Promise> userFeedHandler(final User.Id userId) {
return all(topicService.topicsByUser(userId, Order.ANY),
userService.followers(userId))
.chainMap(tuple -> tuple.map((topics, users) -> articleService.userFeed(map(topics, Topic::id), map(users, User::id))))
.when(timeout(30).seconds(), failure(Errors.TIMEOUT));
}
Давайте рассмотрим этот пример глубже. Прежде всего, Promise.all() вызывается для получения результатов из службы тем и Обслуживание пользователей . Когда эти службы успешно выполняют свои обещания, то Служба статей вызывается с преобразованными значениями, полученными от первых двух вызовов. Наконец, тайм-аут настроен для возвращенного обещания, поэтому, даже если возникнут какие-либо проблемы, окончательное обещание будет разрешено с ошибкой тайм-аута.
Вся эта сложная обработка со всей соответствующей обработкой ошибок – всего лишь одна строка кода.
Резюме
Асинхронная обработка на основе обещания в сочетании с другими методами функционального программирования является очень мощным и выразительным инструментом. Это позволяет удобно выражать сложную обработку, сохраняя при этом код чистым и легким для чтения и записи. Общее применение FP-стиля к Java позволяет создавать современные приложения, которые удобно создавать, расширять, тестировать и поддерживать.
Послесловие
Ядро Реактивного инструментария все еще находится в стадии разработки. Это требует больших усилий, и я понимаю, что весь проект намного больше, чем может выполнить один человек за разумное время. Итак, те, кто хочет принять участие, могут присоединиться к проекту. Комментарии, идеи, предложения также приветствуются.
Оригинал: “https://dev.to/siy/asynchronous-processing-in-java-with-promises-2oj0”