Перейти к содержимому
ГоМастер

Поиск по урокам

Найдите урок, курс, термин или раздел

Урок 7 из 25 ~18 мин +50 XP

tea.Batch и tea.Sequence: комбинации команд

tea.Batch и tea.Sequence собирают несколько асинхронных команд в одну: Batch запускает их параллельно, Sequence — строго по очереди. Разберём, когда какая уместна, в каком порядке приходят сообщения, как вкладывать комбинации друг в друга и почему предположение о порядке в Batch — самая дорогая ошибка новичка.

Содержание урока · 11 разделов

Init возвращает ровно одну команду — это ограничение интерфейса, и на старте оно кусается. Программе нужно одновременно: загрузить задачи, поднять спиннер, проверить настройки, запустить часы. Возвращать их по одной невозможно, а выбирать «самое важное» — значит халтурить. Update попадает в ту же ловушку: пользователь нажал Enter, и программе нужно и отправить форму, и включить индикатор. Ответ — комбинаторы: tea.Batch и tea.Sequence принимают любое число команд и возвращают одну составную, которая честно исполняет все. Оба появились не для красоты: одиночной команды хватает ровно до тех пор, пока приложение не начало по-настоящему работать с внешним миром. Разница между ними — в порядке и одновременности, и разница эта дороже, чем кажется: от неё зависит, упадёт ли ваш экран запуска на медленной сети. Это урок о том, как планировать много асинхронной работы сразу — и не доверять порядку её выполнения.

tea.Batch: параллельный запуск команд#

tea.Batch(cmds ...Cmd) — вариадическая функция: принимает сколько угодно команд и возвращает одну. Фреймворк разворачивает её и запускает все команды одновременно, каждую в своей горутине. Сообщения приходят в Update по мере завершения: какая работа закончилась раньше, то сообщение и прилетело первым. Никакой синхронизации между командами нет — Batch это чистый fan-out: раздал задачи, собрал результаты. Идеальный клиент — независимые операции: загрузка настроек, тик спиннера, проверка версии на сервере. Ни одна не ждёт другую, значит, параллельность только ускорит старт. Механика честная: изнутри Batch возвращает команду со списком остальных, которую фреймворк разворачивает рекурсивно, — для вас это невидимо и не меняет правил игры. Ключевое следствие, которое стоит выучить наизусть: Batch не даёт никаких гарантий порядка сообщений. Ни «первая в списке придёт первой», ни «порядок стабильный между запусками». Первым придёт тот, кто закончил, — и точка. Всё, что построено на предположениях об этом порядке, построено на песке; почему — увидим на примере ниже.

Старт программы пачкой

GO
func (m model) Init() tea.Cmd {
        return tea.Batch(
                m.spinner.Tick,    // тикающий спиннер
                loadTasksCmd(),    // задачи из хранилища
                loadSettingsCmd(), // настройки
        )
}

Три независимые операции запущены одновременно. Тики спиннера придут постоянно, результаты загрузок — как закончатся; Update примет их по одному обычным type-свитчем. Ни одна загрузка не блокирует анимацию, и наоборот — именно так выглядит живой старт программы.

tea.Sequence: команды по очереди#

tea.Sequence(cmds ...Cmd) запускает команды последовательно: следующая стартует только после того, как сообщение предыдущей дошло до Update. Это конвейер с очередью шагов, и он нужен всюду, где порядок — часть смысла. Классический пример — протокол: сначала connectCmd, потом authCmd, потом loadTasksCmd. Запускать их пачкой бессмысленно: авторизация без установленного соединения просто упадёт, и вы получите три ошибки вместо одной. Sequence превращает этот список в детерминированную цепочку: каждый шаг гарантированно видит результат предыдущего — потому что он уже обработан в Update и разложен по модели. Плата за порядок — скорость: суммарное время равно сумме шагов, параллельности нет. Поэтому правильный вопрос при выборе — не «что быстрее», а «зависит ли шаг от предыдущего». Зависит — Sequence. Не зависит — Batch. Sequence выручает и в сценариях онбординга: показать приветствие, дождаться подтверждения, загрузить первый экран — тоже цепочка шагов. Комбинации обоих закрывают остальные случаи.

Протокол запуска по шагам

GO
func (m model) Init() tea.Cmd {
        return tea.Sequence(
                connectCmd(),   // 1. установить соединение
                authCmd(),      // 2. авторизоваться
                loadTasksCmd(), // 3. загрузить данные
        )
}

Каждая команда стартует после доставки сообщения предыдущей. Ошибка на шаге обрывает цепочку там, где случилась: если connectCmd вернёт сообщение с ошибкой, authCmd не запустится, — обрабатывайте её в Update так же, как обрабатывали бы одиночную команду.

Batch против Sequence#

Свойствоtea.Batchtea.Sequence
Запусквсе команды сразупо одной, строго по порядку
Порядок сообщенийне гарантированпредсказуем: шаг за шагом
Скоростькак самая долгая командакак сумма всех команд
Зависимости между шагаминедопустимыразрешены и поддержаны
Типичное применениенезависимые загрузки, тикипротоколы, миграции, сценарии

Ловушка: порядок в Batch ничего не значит#

Самая частая ошибка с Batch выглядит невинно. В Init возвращают Batch(loadConfigCmd(), loadTasksCmd()), а в Update ловят сообщение настроек раньше сообщения задач и строят на этом логику: «настройки пришли первыми, применю их до отрисовки». На машине разработчика с локальной базой это работает месяцами. Потом программа уезжает к пользователю с медленной сетью, настройка начинает приходить позже задач — и экран стартует с пустым профилем. Я ловил именно такой баг в панели мониторинга: две команды в Batch, на моей машине порядок был стабильным год, а у коллеги в другом дата-центре — противоположным. Диагностика мучительная, потому что баг «плавает». Лечение — принципиальное: Batch годится только для независимых команд, а если B не может работать без результата A, между ними Sequence или второй заход через Update. Никогда не угадывайте порядок — проектируйте код, которому порядок безразличен.

Вложенность: комбинации внутри комбинаций#

Batch и Sequence возвращают обычный tea.Cmd — значит, их можно скармливать друг другу и смешивать со своими командами на любом уровне. Механика честная: BatchMsg изнутри разворачивается фреймворком рекурсивно, и глубже одного-двух уровней уходить обычно не приходится. Живой шаблон старта: Sequence снаружи, потому что этапы зависят друг от друга, а внутри этапа — Batch из независимых задач. Так вы получаете и порядок фаз, и параллельность внутри фазы. Обратная комбинация — Batch из последовательностей — встречается реже, но уместна, когда несколько независимых протоколов идут параллельно, и каждый — по шагам. Полезная привычка: читать такие вложенности как дерево, где ветка Sequence исполняется вертикально, а ветка Batch — веером. Если дерево в голове не собирается, код для читающего соберётся ещё меньше — дробите на именованные функции-команды.

Смешанный старт: порядок фаз, параллельность внутри

GO
func (m model) Init() tea.Cmd {
        return tea.Sequence(
                tea.Batch(
                        m.spinner.Tick,
                        loadSettingsCmd(),
                ),
                authCmd(),
                tea.Batch(loadTasksCmd(), loadStatsCmd()),
        )
}

Первым стартует веер: тики спиннера плюс настройки параллельно. После доставки их сообщений начинается authCmd, и только после неё — вторая пачка: задачи и статистика. Порядок фаз детерминирован, внутри фаз — честная параллельность.

Fan-out: команда на каждый элемент#

Второй шаблон, где Batch незаменим, — одинаковая работа по списку. Десять адресов, которые нужно пропинговать; двадцать файлов, которые нужно прочитать; пакетный индекс, который надо обновить по одному элементу. Решение механическое: в цикле сгенерируйте срез команд и отдайте его в tea.Batch пачкой. Фабрика-замыкание из пятого урока здесь главное действующее лицо: каждый элемент списка захватывается своим замыканием, и команды не путаются между собой, а ошибка элемента возвращается его же командой — сразу понятно, кто виноват. Сотни одновременных команд фреймворку не страшны — горутины дешёвые. Страшна цель: если адресатов тысячи, подумайте о пуле ограничений — слишком много одновременных соединений упрутся в лимиты сервера или самого терминала по отрисовке сообщений. Простой вариант — нарезать список на куски и планировать следующий Batch из Update, когда предыдущий отчитался. А материал для настоящих сетевых fan-out'ов ждёт в уроке про HTTP-данные.

Гонки и детерминизм: как думать о параллельности#

Параллельность пугает, если привыкли к потокам с блокировками. В Bubble Tea гонок между вашими командами почти не бывает — и вот почему. Команды не делят состояние: каждая работает со своими аргументами и возвращает сообщение, а всё состояние меняет один-единственный Update, который обрабатывает сообщения строго по одному. Пока вы держитесь этого правила elm архитектуры, параллельный Batch безопасен по построению: каким бы ни был порядок, каждая обработка видит целостную модель. Проблемы начинаются не от параллельности, а от ожиданий: код, который предполагает порядок прибытия или однократность события, — уже не конкуренция за память, а логическая гонка, и таблица Batch против Sequence выше — про неё. Противоядие то же: пишите обработчики так, будто каждый факт может прийти в любой момент и даже дважды — идемпотентно и без предположений о соседях. Тогда скорость пачки достаётся бесплатно, а редкие порядки превращаются из багов в просто события.

Проверка: семантика Sequence

Чем tea.Sequence отличается от tea.Batch?

Проверка: порядок сообщений

В Batch положили loadConfigCmd и loadTasksCmd. Что можно предполагать о порядке их сообщений в Update?

Какое сообщение придёт первым?

Прочитайте код и выберите, что он выведет:

type doneMsg struct{ tag string }

func fastCmd() tea.Cmd {
        return func() tea.Msg {
                time.Sleep(50 * time.Millisecond)
                return doneMsg{tag: "быстрая"}
        }
}

func slowCmd() tea.Cmd {
        return func() tea.Msg {
                time.Sleep(2 * time.Second)
                return doneMsg{tag: "медленная"}
        }
}

func (m model) Init() tea.Cmd {
        return tea.Batch(slowCmd(), fastCmd())
}

Заполните пропуски в комбинациях

Восстановите выбор комбинатора: независимые задачи — пачкой, протокол — по шагам.

func (m model) Init() tea.Cmd { return tea.( m.spinner.Tick, loadTasksCmd(), ) } func (m model) boot() tea.Cmd { return tea.(connectCmd(), authCmd(), loadTasksCmd()) }

Соберите вложенный старт

Расставьте строки возвращаемого выражения Init: порядок фаз с параллельностью внутри.

Строки

Ваша программа

Нажимайте на строки слева, чтобы собрать программу

Карточки: словарь комбинаций

Термин

tea.Batch

нажмите, чтобы перевернуть
Определение

Собирает команды в одну и запускает все параллельно; сообщения приходят по мере завершения, порядок не гарантирован.

1 / 5

Что дальше#

Комбинаторы закрыли дыру «одна команда на событие» — и попутно показали, что модель умеет разрастаться. Когда загрузок и панелей становится много, Update начинает нуждаться в порядке не меньше, чем Init: восьмой урок про композицию подмоделей учит делить экран и сообщения между частями программы. Тики часов и таймауты — те же команды, собранные через Batch, — разбирает урок про tea.Tick и таймеры. А если хотите освежить одиночные команды, вернитесь к пятому уроку или откройте страницу курса Bubble Tea — там вся программа из 25 уроков. Практика на сегодня: возьмите старт из примера с Sequence, добавьте четвёртый этап loadLogsCmd и замерьте локально, как меняется время до первого кадра, если переставить фазы местами.

Песочница урока

Редактируйте код и запускайте его прямо здесь — интерпретатор работает в браузере, ничего устанавливать не нужно. За каждый запуск — +2 XP.

Виртуальный терминал

модель Bubble Tea · Model → Update → View

Отредактируйте модель, Update или View — и нажмите «Собрать модель»: код разберётся и начнёт отвечать на клавиши, как настоящий tea.Program.

виртуальный терминал · bubbleteaalt-screen
настройки: 0 · задачи: 0 · ждём: 2
b — пачка, s — цепочка, тик — пришло Msg, r — сброс, q — выход

Клавиатура — обработано в Update

Журнал программы

модель собрана: settings int = 0 · tasks int = 0 · pending int = 2 · log []string (0)

программа работает — нажмите клавишу ниже

Коротко: что запомнить

  • tea.Batch(cmds...) запускает команды параллельно; порядок сообщений не гарантирован.
  • tea.Sequence(cmds...) исполняет команды по очереди: следующий шаг ждёт доставки сообщения предыдущего.
  • Зависимые команды кладут в Sequence; независимые — в Batch; смешивать можно и нужно.
  • Предположение о порядке сообщений из Batch — плавающий баг, который живёт месяцами.
  • Комбинаторы возвращают обычный tea.Cmd и вкладываются друг в друга.
  • Fan-out по списку — цикл с фабрикой замыканий и Batch(cmds...).

Домашнее задание

Соберите стартовую цепочку: Sequence из двух фаз, внутри первой — Batch из спиннера и настроек, внутри второй — Batch из задач и статистики. Замедлите каждую команду своим time.Sleep и запустите локально. Запишите порядок прихода сообщений, потом поменяйте команды местами внутри пачек и повторите: убедитесь, что фазы не перепутались, а порядок внутри пачек — плавает.

Личные заметкиПусто
Хранятся только в вашем браузере (Bubble Tea · tea.Batch и tea.Sequence: комбинации команд)

Вопросы и ответы по уроку

Что вернёт tea.Batch, если одна из команд упадёт с ошибкой?

Batch ничего не агрегирует: ошибка придёт в Update обычным сообщением той команды, которая упала, остальные продолжат работать. Это ещё один довод не полагаться на порядок — сбои перемешивают его окончательно.

Можно ли вкладывать Batch в Sequence и наоборот?

Да: оба комбинатора возвращают обычный tea.Cmd, поэтому вложенность любой глубины собирается компилируемо. Практичный потолок — два уровня: Sequence фаз с Batch внутри каждой, иначе дерево команд перестаёт читаться и тестироваться.

Сколько команд выдержит один Batch?

Сотни — без вопросов: под каждую команду фреймворк заводит горутину, а они дешёвые. Ограничение обычно не в bubbletea, а во внешних ресурсах: лимитах соединений, rate limits API и способности Update переварить лавину сообщений.

Термины урока

Ключевые понятия из этого урока — короткие определения в глоссарии. Наведите на термин, чтобы вспомнить, откройте карточку для подробностей и ссылки на практику.

Сравните с альтернативами

Практический вопрос после первых уроков: а почему именно эта библиотека? Мы разобрали конкурентов по одним критериям, с кодом одной задачи рядом и персональным подбором под ваши приоритеты.

Следующая ступень учебного пути

Zap25 уроков~9 чпрактика25 уроков уже доступны

Zap: структурное логирование на Go

Интерактивный курс Zap — быстрого логгера для Go: уровни, поля, JSON, ротация, интеграции с Gin, GORM и gRPC. 25 уроков с примерами и итоговым проектом.

  • Писать структурные логи с полями вместо строк
  • Выбирать между Logger и SugaredLogger осознанно
  • Настраивать энкодеры, выводы и ротацию файлов
  • Разбираться в zapcore: Core, Encoder, WriteSyncer, LevelEnabler