Комментарии 12
Ну, например, есть у меня двухссылочный список. Возможно — закольцованный. Мне нужно начиная с переданного элемента итерироваться по нему в заданную сторону, причём, условие завершения может быть разным.
Я пишу генератор, с простым условием завершения — пустая ссылка. Он потенциально бесконечный. А настоящим условием завершения ведает вызывающая функция, которая в некоторых случаях тоже может быть генератором.
Очень удобно.
Всё то же самое можно сделать с колбэком, в стиле forEach. Возврат, к примеру, значения false в колбэке — признак что пора закругляться. Если признак завершения определяется асинхронно, то в нашем списковом forEach можно предусмотреть возврат промиса и использовать async функцию. Вряд ли это будет сильно сложнее.
Можно, но это менее гибко. Я так сперва и сделал, но потом потребовалась различная обработка результатов и сложное условие завершения. Чтобы не усложнять логику обработки возврата из коллбека, я перешёл на генератор. Сложный метод распался на два простых.
Вряд ли это будет сильно сложнее.
Однозначно сложнее. Потому что проще чем генератор — уже некуда.
Есть ощущение, что в redux-saga 90% вещей можно было бы упростить, сменив интерфейс на async-await (или хотя бы сделав его async-совместимым). Т.е. генераторы там как бы слишком чрезмерно и широко используются. Интересно, это правда так?
Итерирование по узлам дерева обычным циклом, например.
Самый распространённый в моей практике случай: загрузка каких-либо данных (например постов блога) частями. Это СУПЕР удобно.
Пишешь асинхронный генератор, который хранит в себе данные для пагинации и загружает посты.
А дальше используешь
iterator.next(<post_per_page>)
чтобы загрузить новую партию постов.
Слушайте, итераторы и генераторы — это всего где-то 5% сложности. Статья из разряда “пишем hello world”. А остальные 95% сидят вот где:
- AsyncIterator
- AsyncIterable и AsyncGenerator
- Конкуретное выполнение next() в AsyncIterator (там вообще черт ногу сломит).
- Внешний вызов return() у AsyncIterator (или уже AsyncGenerator?) и его поведение при выбросе исключений.
- Конкурентный внешний вызов return() (черт сломает вторую ногу).
- Внешний вызов throw() у AsyncIterator (или AsyncGenerator все же?).
- TS-типы этих трех интерфейсов, их схожесть и различие.
На все это есть спека в Ecmascript (написанная на таком своеобразном птичьем псевдоязыке), но она такая громадная и непонятная, что черт сломит четвертую, пятую и все остальные ноги.
Еще одно. Поправьте меня если я ошибаюсь (и очень интересны мнения специалистов на эту тему в комментах тоже), но AsyncIterator/AsyncGenerator — это такой ответ JS на концепцию Observables, сделанный с тем расчетом, чтобы их упростить (потому что сами Observables — крайне сложная в освоении концепция для большинства инженеров). Такой своеобразный протест. В принципе, использовать AsyncGenerator и правда проще в подавляющем большинстве случаев, чем Observables. (Хотя они немного различаются: AsyncGenerator — это скорее pull, а Observable — это скорее push, но в реальной жизни возможны комбинации.)
Также это ответ на Streams и их крайнюю сложность в вопросах backpressure (а без обработки backpressure почти ничего серьезного, по сути, и не сделать). Такая отчаянная попытка починить все это, пока окончательно не схлопнулось под весом собственной сложности.
Вообще когда говорят о генераторах, забывают, что они сами по себе — просто сервисная штука для создания итераторов.
И вот когда вы привыкаете думать итераторами, все становится проще и появляется смысл.
Пример — небольшая игра, типа рогалик. Герой заходит в комнату. Как мы получаем рандомную комнату? Вот так
const room = roomBuilder.next();
Углублённое руководство по JavaScript: генераторы. Часть 1, основы