Можно ли Deferred рассматривать как замену Listena...
# russian
b
Можно ли Deferred рассматривать как замену ListenableFuture или CompletableFuture?
a
Про первое не знаю, про второе - есть CompletableDeferred, который является аналогом.
Только зависимости по-другому подключаются.
b
Первое из Google Guava
Хочется тут в систему добавить удалённые асинхронные вызовы, и не знаю, взять ли пока CompletableFuture или сразу на Котлине заморочиться
a
Ну корутины удобнее. Если у вас уже библиотека подключена и не нужна обратная совместимость с джавой - рекомендую
b
Котлин сам использую широко, но корутины ещё нет
a
Критичный момент - это джава. Корутины обратно не шибко совместимы.
b
Об обратной совместимости можно не беспокоиться
a
Ну тогда если 1 мб лишней зависимости не волнует - берите, не пожалеете.
g
Если использование из Java не проблема, то однозначно корутины. Но на самом деле Deferred в 95% не нужен совсем, нужны только suspend функции, Deferred почти никогда не должен появлятся в публичном API Я бы рекомендовал для начала прочесть официальный гайд https://kotlinlang.org/docs/reference/coroutines/coroutines-guide.html
b
У меня есть интерфейсы на клиенте, которые удалённо вызывают сервер, реализовано через java.lang.reflect.Proxy, но там сейчас только геттеры и сеттеры, нужно добавить асинхронные методы
Как это сделать на фьючах я хотя бы представляю
g
ну вот эти асинхронные методы должны быть suspend, внутри них можно асинхронный код использовать (другие syspend функции, или через адаптеры любые дрпугие асинхронные API), а клиент запускает корутину через launch и вызывает эту функцию
b
Могу я сделать динамический прокси для suspend методов?
g
должно быть возможно
Retrofit это делает
но это не так то элементарно
b
Там же вроде добавляется какой-то скрытый параметр?
g
да
b
Что-то как-то не слишком просто...
e
Кстати, учитывая что уже не первый раз такой вопрос про Proxy для suspend functions, вот написал тикет чтобы не потерялось: https://youtrack.jetbrains.com/issue/KT-34399
g
Если не хочетя заморачиваться с кастомными Proxy, то можно юзать Deferred или даже оставить Future, просто использовать адаптер и просто вызывать await() на стороне клиента который использует корутины
t
Я бы рекомендовал для начала прочесть официальный гайд
Там пока до сути дойдёшь уже забудешь, что хотел, уж лучше "неофициальный блог" читать, там про юзкейсы написано
a
Да ладно. Классная документация.
1
Если надо бац-бац и тама, то документации на kotlinlang достаточно.
t
Ну, видимо я очень избалован rx-ом
a
Корутины - это сильно шире, чем rx. Если брать только функциональность rx, то там будет на две странички
t
Ну вот я бы предпочёл эти две страницы вместо текущих 20. А ещё во flow (хорошо хоть он появился!) мне сильно не хватает Single/Maybe вместо голых suspend-ов.
t
Такое и у observable есть, но это не соглашение на уровне контракта
g
зачем Flow с синглом?
Там пока до сути дойдёшь уже забудешь, что хотел
Мне кажется просто пока не прочтешь и не поймешь о чем это вообще, лучше не соваться Там большая часть доки как раз про юзкейсы
мне сильно не хватает Single/Maybe вместо голых suspend-ов
а что хочется добится?
но это не соглашение на уровне контракта
У корутинь тоже, на уровне susped функций
e
Можно завести
typealias Single<T> = suspend () -> T
. Но зачем?
t
Maybe отличается от просто suspend кучей важных деталей: - onSuccess/onError/onComplete - возможностью transform (в том числе блокирующими вызовами) - блокирующие вызовы можно писать прямо в теле fromAction/fromCallable и не надо заворачивать это в async/withContext - тредингом озаботится вызывающий
Flow это всё даёт, но это всё-таки Observable, а не Single/Maybe. Ну и emit это не совсем то же самое, что и fromAction (к вопросу о юзкейсах, собственно).
e
Вопрос не “что дает“, а “зачем“.
Все наши эксперименты по переписываю кода с single/maybe на простые suspend функции, которые мы до сих пор проводили, приводили к значительному упрощению и сокращению кода. Поэтому вопрос “зачем” у меня не праздный. Если есть примеры, где это полезно, то хотелось бы их видеть. Может мы что-то упускаем.
Особенно интересует вопрос про трединг. Этого комментария я вообще не понял. А что мешает вызывающему озаботиться тредингом при работе с suspend функциями?
Мета-замечание: Котлин прагматичный язык. Будь у нас примеры показывающее преимущество single/maybe при решении какого-то широкого класса задач, так мы бы их сделали.
(Понятно что в Rx и Project реактор он нужны и без них не как не обойтись, так как в Java нет suspend функций)
t
Общий ответ на вопрос "зачем" - это в первую очередь "простота перехода". Понятно что можно переписать всё на suspend/flow и будет хорошо, но на практике так бывает далеко не всегда ведь и удобнее переносить небольшие часть.
Другой пример, как я уже говорил - transform:
Copy code
single()
  .map(::func1)
  .subscribeOn().observeOn().subscribe()
с
suspend
вместо
single
придётся
func1
(или оба `func1`+`suspend`) заворачивать в
withContext
или переписывать тоже на
suspend
.
Если говорить про
Maybe
, то это обычно своеобразный guard, типа
Copy code
maybeLoadUser()
  .map(::loadExtendedData)
  .subscribeOn().observeOn().subscribe(::showNotification)
на suspend придётся делать
suspendLoadUserOrNull
и либо городить заборы, либо делать early return
Мета-замечание: Котлин прагматичный язык.
Абсолютно согласен, потому и возмущаюсь некоторыми штуками в корутинах. Ручной вызов await + лишняя переменная (когда нужно сделать 2 или больше разных параллельных запросов) - БОЛЬ, даже zip и тот лучше выглядит.
g
Ручной вызов await
Copy code
val (a, b) = awaitAll(async { "a" }, async { "b" })
👍 1
e
@thevery “Ручной вызов await + лишняя переменная“. Не понял. Пример можно?
t
А там @gildor показал выше обход проблемы, собственно. Совсем красиво было бы
val a by async { "a" }
, но это мы как раз год назад обсуждали.
e
Если я не ошибаюсь, то после переписывания на suspend функции вот такой код с Maybe/Single:
Copy code
maybeLoadUser()
  .map(::loadExtendedData)
  .subscribeOn().observeOn().subscribe(::showNotification)
становится вот таким
Copy code
val user = maybeLoadUser() ?: return
val data = loadExtendedData(user)
showNotification(data)
Ну а для тех, кто не любит дополнительные переменные, можно написать:
Copy code
maybeLoadUser()
    ?.let(::loadExtendedData)
    ?.let(::showNotification)
В любом случае это 100% такой же как код, так я если бы никаких медленных сетевых операций не было.
Для упрощения
awaitAll + async
можно сделать extension в пару строк у себя в проекте, если вот прямо так часто нужно:
Copy code
val (a, b) = awaitAllAsync({ "a" }, { "b" })
t
val user = maybeLoadUser() ?: return
maybeLoadUser
становится nullable (не очень большая проблема, но всё равно неявный контракт) и это как раз тот early return, про который я выше говорил
val data = loadExtendedData(user)
loadExtendedData всё равно нужно качественно на suspend переписать или завернуть в async
e
Что значить “качественно на suspend переписать“? Если у вас в Rx было
map(::loadExtendedData)
(а не
flatMap
), то я из этого делаю вывод, что
loadExtendedData
это какая синхронная локальная операция (достает что-то из кэша?). Не нужно её на suspend переписывать.
t
map от flatMap отличается тем, что первая возвращает результат (быстро или медленно - не так принципиально), а вторая - Single. Не должна ли быть медленная функция и так завёрнутой в Single - вопрос хороший, но я точно уверен, что не 100% таких функций завёрнуто на практике. Да банально list.filter (представим loadUsers вместо loadUser) не очень хочется делать на ui-потоке.
e
Ну если у вас медленная (блокируящая) функция не завернута в Single, то вы проиграли — какой-то поток будет заблокировать. В Rx вы можете указать поток-жертвую для этого используя
.subscribeOn(someBackgroundDispatcher)
. С корутинами же в этом случае вы пишете
withContext(someBackgroundDispatcher) { loadExtendedData(user) }
. Разницы вроде как нет.
t
Разница на мой взгляд в том, что subscribeOn и так есть практически всегда и, что тоже немаловажно, он один на всю цепочку map/filter/etc.
e
Ну если у вас такое legacy, то пишите так же и один
withContext
на всё. Вреда от него не будет.
t
Чувство прекрасного страдает!