Для тех, кто не знает, Vert.x – это управляемый событиями и неблокирующий инструментарий приложений. Это полиглот, поэтому вы можете использовать его с разными языками (как Java , Котлин , JavaScript , Заводной , Ruby или Scala ).
Что значит “не блокирующий”?
В синхронном программировании при вызове функции вызывающий должен подождать, пока не будет возвращен результат. Такое поведение может привести к проблемам с производительностью.
Часто “очевидным решением” кажется параллельное программирование , но иметь дело с общими ресурсами и потоками непросто, и тупики не за горами.
Объяснение того, как Vert.x гарантирует асинхронность через цикл событий концепция не входит в рамки этой статьи, но все, что вы, возможно, захотите узнать, раскрывается в замечательном Мягком руководстве по асинхронному программированию с помощью Vert.x .
В “неблокирующем” мире, когда результат функции может быть предоставлен немедленно, так и будет. В противном случае предоставляется обработчик для обработки, когда он будет готов.
asyncFunctionCall(result -> { doSometingWith(result); })
Обычно большое “но” возникает при первом просмотре асинхронного фрагмента кода: Но… Мне нужен этот результат сейчас!
Этот вид программирования требует изменения мышления: необходимо знать и понимать основные закономерности.
Фьючерсы против обратных вызовов
Существует два способа обработки асинхронных вызовов в Vert.x.
- Передайте обратный вызов, который будет выполнен по завершении вызова.
- Обработайте будущее, возвращенное из вызова функции/метода.
Обратный звонок
asyncComputation(asyncResult -> { if (asyncResult.succeeded()) { asyncResult.result() // do stuff with result } else { // handle failure } })
Обратный вызов – это функция, передаваемая асинхронному методу, используемому для обработки результата ее вычисления. Это просто реализовать, но это имеет некоторые недостатки:
- Модульное тестирование становится не таким уж быстрым
- Ведет в ужасный Ад обратного вызова
- Нужно написать еще больше кода.
Будущее
Future future = asyncComputation() future.onSuccess(result -> { // do stuff }) future.onFailure(cause -> { // handle failure })
Чтобы избежать проблем, перечисленных для “способа обратного вызова”, Vert.x реализует концепцию под названием Будущее .
Будущее – это объект, который представляет собой результат действия, которое может произойти, а может и не произойти еще (ср. апидок ).
Как переключиться с обратного вызова, подобного вызову, на будущий
Рассмотрим пример обратного вызова, показанный выше. Мы хотим, чтобы будущий объект использовал преимущества шаблонов, описанных ниже, но
асинхронное вычисление
Мы можем использовать Обещание . Согласно apidoc , он представляет собой доступную для записи сторону действия, которое может произойти, а может и не произойти. Это идеально соответствует вашим потребностям:
Promise promise = Promise.promise(); asyncComputation(asyncResult -> { if (asyncResult.succeeded()) { promise.complete(asyncResult.result()); } else { promise.fail(asyncResult.cause()); } }) return promise.future()
Это оно. Мы превратили обратный звонок в будущее. API обещания дают нам способ сделать этот код более читабельным:
Promise promise = Promise.promise(); asyncComputation(promise::handle) return promise.future()
Метод handle заботится о выполнении или невыполнении обещания, учитывая асинхронный результат.
Будущие модели
В объекте Future реализованы некоторые интересные шаблоны, которые эффективно помогают решать проблемы асинхронности:
- Карта
- Составить
- Восстанавливать
Отображение будущего
Для тех из вас, кто знает о функции map
, являющейся частью java Stream
API, эта функция должна быть понятна немедленно.
Функция карты будущего принимает функцию, которая преобразует результат из одного типа в другой.
asyncComputation() // returns a Future.map(Integer::valueOf) // returns a Future .onSuccess(...) .onFailure(...)
Будущий состав
Метод compose
аналогичен методу map
, но используется, когда сопоставление само по себе является асинхронной операцией:
asyncComputation() .map(Integer::valueOf) .compose(id -> retrieveDataById(id)) // retrieveDataById returns a Future .onSuccess(...) .onFailure(...)
Будущее Восстановление
Будущее может быть успешным, но может и потерпеть неудачу. Для обработки неудачного будущего и рассмотрения другого поведения можно использовать функцию восстановление
:
asyncComputation() .map(Integer::valueOf) .recover(cause -> Future.succeededFuture(0)) // when Integer::valueOf fails, the future could be recovered with a default value .compose(id -> retrieveDataById(id)) .onSuccess(...) .onFailure(...)
Параллельная композиция
Для обработки нескольких будущих результатов одновременно необходим класс Составная функция
, он предоставляет два статических заводских метода:
all
возвращает будущее, которое завершится успешно, если все функции, переданные в качестве параметров, завершатся успешно, и завершится неудачно, если хотя бы одна из них завершится неудачно.
CompositeFuture.all(futureOne, futureTwo) .onSuccess(compositeResult -> // all futures succeeded ) .onFailure(cause -> // at least one failed );
любой
возвращает будущее, которое завершится успешно, если какая-либо из функций, переданных в качестве параметров, завершится успешно, и завершится неудачно, если все завершатся неудачно.
CompositeFuture.any(futureOne, futureTwo) .onSuccess(compositeResult -> // at least one succeed ) .onFailure(cause -> // all failed );
Выводы
API будущей композиции в Vert.x представляет собой надежный способ написания простого и доступного асинхронного кода. Всегда помни:
- никогда не создавайте исключений в асинхронном коде, вместо этого используйте failed future для обработки поведения при сбое.
- в конце будущей композиции не забудьте указать будущие успехи (
об успехе
) и неудачи (При сбое
)
Оригинал: “https://dev.to/cherrychain/future-composition-in-vert-x-3gp8”