Спасибо. Возможно код выше для кого-то и выглядит привычнее, но возвращаясь к самому первому комментарию в ветке, я вижу ровно наоборот: rxjs пытается решить проблемы, которые давно уже решены.
Безусловно rxjs хорош в потоках и управлении ими, но в данном примере у нас только компонент autosuggest и обработка действий пользователя. При попытке сравнить это с потоком выходит гораздо больше сложностей (tap, finalize, pipe, subscribe, switchMap, rxjsClient).
В случае с моим примером на генераторах можно вообще ничего не знать об effection (в логике обработки данных есть только sleep из него), т.е. имея базовые знания о js и генераторах уже можно прочитать и написать похожий компонент.
Все таки хочется применять инструменты там, где они принесут пользу, а не просто потому, что мы их знаем (Это справедливо также и для effection и React в целом)
где нужно выставлять индикатор загрузки? Т.к. в наших примерах он ставится после дебаунса, то логически его нужно вставить между debounceTime и map. Но насколько правильно менять стейт внутри pipe? Судя по комментарию из твоего кода, результаты автозаполнения задаются в subscribe. Т.е. в моем понимании нужно сделать еще один subscribe, а потом снова иметь pipe.
каким образом происходит обработка ошибок? Насколько я помню, в rxjs есть момент, что после ошибок поток закрывается.
как принудительно отменить всю цепочку? Например, если пользователь нажал кнопку "очистить" в поле для ввода или ушел с этого компонента.
не особо критично, но все же. Как действительно отменить http запрос в этой цепочке? В этом случае все же switchMap просто отбрасывает результат промиса, но сам запрос и промис все равно будут выполнятся до конца.
Возможно после этого строк будет уже больше. Хотя даже сейчас уже можно сравнить с кодом на генераторах из раздела ## Генераторы + effection (10 строк включая try/catch). Ужасом я ни то, ни другое не хочу назвать, но при прочих равных, кажется, что декларативный стиль с rxjs тут ничем не выигрывает у привычного императивного с yield. Плюс это дополнительные проблемы с дебагом. Тут, допустим, все относительно несложно, но в сложной логике переход между конструкциями в дебаггере внутри pipe может доставить боль.
Спасибо за комментарий. Хотелось бы обсудить несколько моментов:
В реальности отменяемость асинхронных запросов и цепочек действий нужна очень редко. Это очень спорный момент, тут скорее можно сказать, что об отмене задумываются очень редко, из-за чего можно словить разного рода баги. Я нередко ловлю баги связанные с браузерной навигацией (вперед\назад). Я пользуюсь ей довольно часто (у меня эти кнопки есть на мышке). Ситуации бывают разные, лучше смотреть на конкретный пример. В случае с autosuggest - это стоит делать.
но из-за этого же теряет в читабельности. В статье я хотел донести, что читаемость не теряется (возможно, если только стоит привыкнуть к yield* вместо await). Но все остальное - вполне себе линейно, просто и понятно. Можем на конкретном примере обсудить.
То есть инструменты вроде Effector. Может быть опечатка, но в статье я использую Effection. Согласен, запутаться очень легко, есть Effector, Effection, а еще есть useEffect в реакте
В обоих случаях генераторный API будет приватным и за пределами модуля о нём никто знать не будет. Уточню. Апи вроде run/halt/call из effection будут использоваться не так часто, и по хорошему эти вызовы должны быть завернуты в проектные хуки или функции, но сами генераторы могут использоваться в любом месте и легко переиспользуются в других генераторах черезyield*.
В реальном приложении код из примера будет выглядить так: Немного комментариев по поводу кода:
Из плюсов: очевидно кода стало меньше
Но самый главный минус - код перестает быть линейным. Функция onChange меняет только стейт input. Дальше хук useDebounce меняет еще один стейт debouncedSearch. А дальше эта результат уже используется в другом хуке useSWR. То есть мы пытаемся заменить линейный код на обработчике последовательными вызовами useEffect, которые один за одним меняют промежуточный стейт, вызывая дополнительные ререндеры, из-за чего ухудшается читаемость, производительность и усложняется обработка промежуточных результатов.
Насколько я понимаю, useSWR не может отменять запросы и перезапрашивать данные с таким же параметром. Это может хорошо подойти, например, для детального вида, но в случае с обработкой действий пользователя уже является ограничениями.
Попался, потому что в таком случае вызов setLoading(false) будет происходить каждый раз при отмене запроса. Поэтому порядок вызовов при отмене будет следующий:
// новый запрос
setLoading(true);
// отмена предыдущего запроса
abortRef.current?.abort();
// предыдущий запрос завершается с ошибкой,
// срабатывает условие выхода из catch
// if ((e as Error)?.name === "AbortError") return;
setLoading(false) // из finally
// следующий запрос успешно завершается
setLoading(false);
setData(data);
setError(null);
Т.е. после отмены запроса компонент будет в состоянии loading = false, что конечно же, недопустимо. Поправлю это в статье
Спасибо. Возможно код выше для кого-то и выглядит привычнее, но возвращаясь к самому первому комментарию в ветке, я вижу ровно наоборот: rxjs пытается решить проблемы, которые давно уже решены.
Безусловно rxjs хорош в потоках и управлении ими, но в данном примере у нас только компонент autosuggest и обработка действий пользователя. При попытке сравнить это с потоком выходит гораздо больше сложностей (tap, finalize, pipe, subscribe, switchMap, rxjsClient).
В случае с моим примером на генераторах можно вообще ничего не знать об effection (в логике обработки данных есть только sleep из него), т.е. имея базовые знания о js и генераторах уже можно прочитать и написать похожий компонент.
Все таки хочется применять инструменты там, где они принесут пользу, а не просто потому, что мы их знаем (Это справедливо также и для effection и React в целом)
Жду ссылки :)
В статье я использую fetch (+есть пример с WebSocket), но это никак не мешает использовать axios или какой-нибудь graphQL :)
Было бы хорошо, если бы был полный пример, можно ссылкой на gist.
А как отписываться, если подписки не было (все внутри pipe)?
Похоже, что rxjs заставляет избавиться от множества привычных нативных конструкций
Перезапросить данные можно, например, при повторном нажатии на кнопку поиск или по клавише Enter. В репозитории, который я приложил к статье, как раз есть пример autosuggest с пагинацией и подвертждением https://github.com/Atrue/react-concurrency-examples/tree/main/src/examples
Спасибо. У меня несколько вопросов:
где нужно выставлять индикатор загрузки? Т.к. в наших примерах он ставится после дебаунса, то логически его нужно вставить между debounceTime и map. Но насколько правильно менять стейт внутри pipe? Судя по комментарию из твоего кода, результаты автозаполнения задаются в subscribe. Т.е. в моем понимании нужно сделать еще один subscribe, а потом снова иметь pipe.
каким образом происходит обработка ошибок? Насколько я помню, в rxjs есть момент, что после ошибок поток закрывается.
как принудительно отменить всю цепочку? Например, если пользователь нажал кнопку "очистить" в поле для ввода или ушел с этого компонента.
не особо критично, но все же. Как действительно отменить http запрос в этой цепочке? В этом случае все же switchMap просто отбрасывает результат промиса, но сам запрос и промис все равно будут выполнятся до конца.
Возможно после этого строк будет уже больше. Хотя даже сейчас уже можно сравнить с кодом на генераторах из раздела ## Генераторы + effection (10 строк включая try/catch). Ужасом я ни то, ни другое не хочу назвать, но при прочих равных, кажется, что декларативный стиль с rxjs тут ничем не выигрывает у привычного императивного с yield. Плюс это дополнительные проблемы с дебагом. Тут, допустим, все относительно несложно, но в сложной логике переход между конструкциями в дебаггере внутри pipe может доставить боль.
Было бы хорошо сравнить, если напишешь тот же самый пример на RxJs (желательно на React)
Спасибо за комментарий. Хотелось бы обсудить несколько моментов:
В реальности отменяемость асинхронных запросов и цепочек действий нужна очень редко. Это очень спорный момент, тут скорее можно сказать, что об отмене задумываются очень редко, из-за чего можно словить разного рода баги. Я нередко ловлю баги связанные с браузерной навигацией (вперед\назад). Я пользуюсь ей довольно часто (у меня эти кнопки есть на мышке). Ситуации бывают разные, лучше смотреть на конкретный пример. В случае с autosuggest - это стоит делать.
но из-за этого же теряет в читабельности. В статье я хотел донести, что читаемость не теряется (возможно, если только стоит привыкнуть к yield* вместо await). Но все остальное - вполне себе линейно, просто и понятно. Можем на конкретном примере обсудить.
То есть инструменты вроде Effector. Может быть опечатка, но в статье я использую Effection. Согласен, запутаться очень легко, есть Effector, Effection, а еще есть useEffect в реакте
В обоих случаях генераторный API будет приватным и за пределами модуля о нём никто знать не будет. Уточню. Апи вроде run/halt/call из effection будут использоваться не так часто, и по хорошему эти вызовы должны быть завернуты в проектные хуки или функции, но сами генераторы могут использоваться в любом месте и легко переиспользуются в других генераторах через
yield*
.В реальном приложении код из примера будет выглядить так: Немного комментариев по поводу кода:
Из плюсов: очевидно кода стало меньше
Но самый главный минус - код перестает быть линейным. Функция onChange меняет только стейт input. Дальше хук useDebounce меняет еще один стейт debouncedSearch. А дальше эта результат уже используется в другом хуке useSWR. То есть мы пытаемся заменить линейный код на обработчике последовательными вызовами useEffect, которые один за одним меняют промежуточный стейт, вызывая дополнительные ререндеры, из-за чего ухудшается читаемость, производительность и усложняется обработка промежуточных результатов.
Насколько я понимаю, useSWR не может отменять запросы и перезапрашивать данные с таким же параметром. Это может хорошо подойти, например, для детального вида, но в случае с обработкой действий пользователя уже является ограничениями.
В Effection есть только конструкция yield*, через которую можно вызвать только генератор. Все, что не является генератором, можно обернуть через call (https://deno.land/x/effection@3.0.3/lib/call.ts?s=Callable и https://deno.land/x/effection@3.0.3/lib/call.ts?s=call). Event loop тут тот же самый, синхронные операции выполняется синхронно, промисы и таймауты выполняются в своей очереди задач
Попался, потому что в таком случае вызов setLoading(false) будет происходить каждый раз при отмене запроса. Поэтому порядок вызовов при отмене будет следующий:
Т.е. после отмены запроса компонент будет в состоянии loading = false, что конечно же, недопустимо. Поправлю это в статье