Пользователь запускает долгий расчёт, закрывает вкладку и теряет результат, потому что интерфейс показывал только спиннер. Нужно разложить операцию на состояния с идентификатором и честно показывать, где она сейчас.
Долгий расчёт показывают через идентификатор задачи и явные состояния: принято, в очереди, обработка, готово, частичный результат, ошибка, истекло. Проценты допустимы только при измеримой модели прогресса, иначе показывают этапы. Идентификатор переживает закрытие вкладки и потерю сети, а повторный запуск защищают ключом идемпотентности.
В условном сценарии менеджер небольшой оптовой компании загружает на сайт прайс из 18 000 позиций и просит пересчитать матрицу скидок по трём каналам. Расчёт идёт на сервере, вкладка открыта, спиннер вращается. Через минуту менеджер переключается на звонок, закрывает вкладку, а вернувшись, видит пустую форму и не понимает, сохранился ли результат.
Долгая операция ломает привычную схему «запрос — ответ». Браузер не держит соединение бесконечно, человек не держит внимание на спиннере, а сервер не обязан помнить, кто именно ждал ответа. Поэтому задачу приходится разложить на состояния: запрос принят, поставлен в очередь, обрабатывается, готов, выдан или истёк. Каждое состояние — отдельный факт с идентификатором, и интерфейс показывает не «крутится», а «где мы сейчас».

Идентификатор расчёта как единственный якорь
Первый шаг — перестать считать, что ответ придёт в тот же HTTP-запрос, который его запустил. Сервер при приёме задачи возвращает короткое подтверждение и идентификатор расчёта. Дальше клиент спрашивает по этому идентификатору статус, а не ждёт у моря погоды. Это ровно тот приём, который Microsoft описывает как asynchronous request-reply pattern: принять, дать ссылку на состояние, опрашивать или уведомить, отдать результат.
Идентификатор — не косметика. Он переживает перезагрузку страницы, смену устройства и потерю сети. Если менеджер закрыл вкладку, расчёт не обязан умирать: он привязан к идентификатору, а не к открытому окну. При следующем входе интерфейс спрашивает «что у меня в работе» и восстанавливает картину. Без такого якоря любая долгая операция превращается в лотерею: угадай, успело или нет.
Важно сразу договориться, чем идентификатор не является. Это не сессия пользователя и не токен доступа. Это ссылка на конкретную задачу с её параметрами, статусом и сроком жизни. Права на просмотр результата проверяются отдельно, иначе чужой идентификатор станет пропуском к чужим данным.
Если ваша задача — показать состояние долгого расчёта, на странице соответствующего направления 404ai можно посмотреть опубликованные возможности и оставить описание текущего процесса. Конкретные подключения, исходные данные и критерии приёмки согласуем отдельно.
Очередь, обработка и готовность — три разных факта
Соблазн свести всё к «в процессе» велик, но он скрывает разные ситуации. В очереди задача ждёт свободного обработчика, и это может длиться долго при пиковой нагрузке. В обработке она уже считается, и здесь появляется шанс показать осмысленный прогресс. Готовность означает, что результат посчитан и лежит на хранении. Смешав эти состояния, интерфейс врёт пользователю: он показывает бодрый прогресс там, где на самом деле ещё ничего не началось.
Разделение помогает и поддержке. Когда менеджер звонит с вопросом «почему так долго», оператор по идентификатору видит, что задача стоит в очереди двадцать минут из-за наплыва загрузок, а не «зависла». Это разговор о факте, а не о догадке. И это же разделение задаёт честные ожидания: если очередь длинная, лучше сказать «в очереди, ориентировочно столько-то», чем рисовать фальшивые проценты.
Отдельно стоит готовность к выдаче. Результат может быть посчитан, но ещё не выгружен в нужный формат или не проверен на целостность. Между «готово» и «можно скачать» иногда есть шаг, который тоже надо показать, иначе пользователь нажмёт кнопку и получит ошибку.
Что показывать, когда процент неизмерим
Не у каждой задачи есть честная шкала. Пересчёт матрицы скидок по фиксированному числу комбинаций можно считать по шагам: обработано 4 000 из 18 000. А вот операция, где заранее неизвестно, сколько данных придётся перебрать, процентов не имеет. Рисовать «67%» на такой задаче — значит выдумывать число, которое ни на чём не основано.
Здесь выручает смена модели прогресса. Вместо доли от целого показывают этапы: «проверка данных», «расчёт по каналам», «сборка результата». Это не точнее в процентах, зато честнее. Пользователь видит, что работа движется, и примерно понимает, на каком она свете. Для доступности индикатор можно размечать ролью ARIA progressbar. Измеримый прогресс получает корректное aria-valuenow, а у неопределённого прогресса это значение не задают. Название этапа и существенные изменения состояния должны быть доступны текстом, без выдуманного процента.
Правило простое: процент показываем только при определённой модели прогресса, где известно общее количество шагов и оно не меняется по ходу. Во всех остальных случаях — этапы и текст. Это избавляет от классической проблемы, когда полоска замирает на 90% и висит там десять минут.
Возврат к результату после закрытой вкладки
Самый частый провал — не технический сбой, а человеческое поведение. Менеджер запустил расчёт, ушёл на встречу, вернулся и не помнит, где искать. Если результат живёт только в памяти вкладки, он потерян. Если у задачи есть идентификатор и список «мои расчёты», картина восстанавливается за пару секунд.
Условный пример: расчёт запущен в 10:14, вкладка закрыта в 10:16, менеджер вернулся в 11:40. В списке задач он видит запись «матрица скидок, три канала, запущено 10:14, готово 10:19, срок хранения до 17:40». Он открывает результат, сверяет и продолжает работу. Никаких повторных запусков и никакой надежды на память.
Список задач — это не архив на века. У каждого результата есть срок жизни, и это нормально. Хранить всё вечно дорого и рискованно, поэтому интерфейс заранее говорит, до какого момента результат доступен, и предлагает перезапустить, если срок истёк. Честное «истёк, запустить заново» лучше, чем битая ссылка без объяснений.
Сеть пропала: что переживает клиент, а что сервер
Потеря связи на стороне пользователя не должна означать потерю задачи. Сервер уже принял запрос и работает по своему расписанию. Клиент в этот момент теряет возможность опрашивать статус, но не саму задачу. Когда связь вернулась, интерфейс снова спрашивает по идентификатору и получает актуальное состояние — возможно, уже «готово».
Опасность в другом: интерфейс, не получив ответа, решает, что запрос не дошёл, и отправляет его повторно. Так появляется двойной расчёт. Защита здесь — идемпотентность на приёме: повторный запрос с тем же ключом идемпотентности не создаёт новую задачу, а возвращает существующую. Ключ генерируется на клиенте один раз при запуске конкретной задачи и переиспользуется только при повторах именно этого запуска; для новой задачи с другими параметрами генерируется новый ключ.
Отмена — отдельная история. Кнопка «отменить» останавливает ожидание на клиенте, но не обязана останавливать работу на сервере. Если задача уже в обработке, её отмена — это отдельный запрос, который сервер может принять или отклонить. AbortController аккуратно закрывает клиентское ожидание, но не является командой «останови расчёт». Путать эти вещи опасно: пользователь думает, что отменил, а результат всё равно появляется.
Двойной запуск и цена лишней работы
Двойной запрос — не редкость: двойной клик, повтор после таймаута, две вкладки с одной формой. Без защиты каждый такой случай порождает лишнюю нагрузку и путаницу в результатах. Идемпотентный приём решает проблему на входе, но нужен и на выходе: список задач должен показывать одну запись, а не три одинаковых.
Полезно ввести понятие активной задачи. Если по тем же параметрам уже идёт расчёт, интерфейс предлагает подождать его, а не запускать новый. Это экономит ресурсы и избавляет от ситуации, когда два почти одинаковых результата приходят с разницей в секунды, и непонятно, какой из них актуальный.
При этом не стоит превращать защиту в запрет. Иногда пользователю действительно нужен пересчёт с другими параметрами, и это законная новая задача. Различие простое: тот же ключ идемпотентности — та же задача; новый ключ — новая. Интерфейс должен это различать, а не блокировать всё подряд.
Частичный результат: когда готово не всё
Бывает, что расчёт по двум каналам завершён, а третий упал из-за некорректных данных. Показывать «ошибка» на всю задачу — значит терять уже сделанную работу. Показывать «готово» — значит скрывать проблему. Правильный ответ — частичный результат с явной пометкой, какие части посчитаны, а какие нет, и почему.
Такой результат полезен: менеджер может забрать готовые два канала, исправить данные для третьего и дозапустить только его. Это требует, чтобы задача умела дробиться на подзадачи со своими статусами, а итоговый статус собирался из них. Не самая простая модель, зато она отражает реальность, где данные редко бывают идеальными.
В интерфейсе частичный результат стоит показывать отдельным состоянием, а не прятать за общим «готово». Пользователь должен видеть, что именно он получил, прежде чем начнёт на это опираться.
Состояние долгого расчёта на экране сотрудника
Спиннер сообщает только одно: «что-то происходит». Для долгой задачи этого мало. Рабочий экран должен отвечать на три вопроса: что за задача, на каком она этапе, что можно сделать прямо сейчас. Ответы меняются по мере продвижения, и интерфейс обязан меняться вместе с ними.
| Состояние | Что видит сотрудник | Доступное действие |
|---|---|---|
| Принято | «Задача принята, номер такой-то» | Закрыть вкладку, вернуться позже |
| В очереди | «Ждёт обработки, впереди N задач» | Отменить ожидание |
| Обработка | Этапы или процент, если измерим | Смотреть, не закрывать принудительно |
| Готово | «Результат готов, доступен до такого-то» | Открыть, скачать, перезапустить |
| Частично | «Посчитано 2 из 3 частей» | Забрать готовое, дозапустить остальное |
| Ошибка | «Не удалось: причина» | Исправить данные, повторить |
| Истекло | «Срок хранения прошёл» | Запустить заново |
Такая таблица — не украшение, а контракт между бэкендом и фронтендом. Каждое состояние должно быть явно представлено в API, иначе интерфейс начнёт додумывать. А додумывание в долгих операциях почти всегда заканчивается неверным сообщением.
Полезно добавить историю переходов: когда задача принята, когда началась обработка, когда завершилась. Это помогает и сотруднику, и поддержке. История — это факты, а не догадки, и на неё можно опереться в разговоре.
Состояние долгого расчёта: исключения и проверка условий
Первое исключение — задача, которая никогда не завершится. Обработчик упал, очередь потеряла запись, внешний сервис молчит. Нужен таймаут на уровне задачи: если за отведённое время статус не сменился, задача помечается как зависшая, и сотрудник видит это явно. Молчаливое «в обработке» сутками — худший из вариантов.
Второе — изменение параметров на лету. Пользователь запустил расчёт, потом решил поменять входные данные. Это не правка текущей задачи, а новая задача с новым идентификатором. Старая продолжает считаться по прежним параметрам, и результат по ним остаётся валидным для того набора данных. Смешивать нельзя.
Третье — права доступа, которые изменились, пока задача считалась. Сотрудник переведён в другой отдел, и доступ к результату должен проверяться на момент выдачи, а не на момент запуска. Иначе получится, что уволенный сотрудник забирает результат по старой ссылке.
Четвёртое — результат, который стал неактуален. Данные в системе поменялись, а расчёт сделан по старым. Это не ошибка расчёта, а вопрос актуальности, и интерфейс должен честно показывать, на каких данных построен результат и когда они были зафиксированы.
Прогон расчёта после перезагрузки и истечения хранения
Проверка начинается не с кода, а со сценариев. Их стоит прогнать на бумаге до реализации, потому что каждая дырка в логике состояний потом стоит дорого.
| Сценарий | Ожидаемое поведение | Что проверяем |
|---|---|---|
| Закрыл вкладку во время расчёта | Задача продолжается, результат доступен в списке | Идентификатор переживает сессию |
| Пропала сеть на минуту | После восстановления статус актуален | Опрос возобновляется, задача не дублируется |
| Двойной клик по кнопке | Одна задача, один результат | Ключ идемпотентности работает |
| Один канал упал | Частичный результат с пометкой | Дробление на подзадачи |
| Истёк срок хранения | Явное сообщение и предложение перезапуска | Честность вместо битой ссылки |
| Отмена во время обработки | Ожидание закрыто, сервер решает отдельно | Разделение клиентской и серверной отмены |
Эти сценарии стоит превратить в автотесты на уровне API и ручные проверки на уровне интерфейса. Отдельно проверяют доступность: скринридер должен корректно читать состояния, а не только визуальную полоску. И отдельно — поведение при медленной сети, потому что именно там вылезают гонки между опросом статуса и повторным запуском.
Практикум: расчёт стоимости доставки по 40 регионам
Возьмём другой пример, чтобы не повторять матрицу скидок. Условная логистическая компания считает стоимость доставки по 40 регионам с учётом веса, габаритов и сезонных коэффициентов. Расчёт занимает от десяти секунд до нескольких минут в зависимости от объёма справочников.
Шаг первый — приём. Клиент отправляет параметры с ключом идемпотентности и получает идентификатор. Шаг второй — опрос статуса с разумным интервалом, например раз в несколько секунд, с постепенным замедлением, чтобы не забивать сервер. Шаг третий — выдача результата по идентификатору с проверкой прав. Шаг четвёртый — хранение с явным сроком и понятным сообщением при истечении.
Прогресс здесь измерим: 40 регионов — это 40 шагов, и доля посчитанных регионов даёт честный процент. Если какой-то регион не удалось посчитать из-за отсутствия справочника, он попадает в частичный результат с причиной. Сотрудник забирает 37 готовых регионов и отдельно разбирается с тремя.
Проверка на этом примере простая: закрыть вкладку на середине, вернуться через полчаса, убедиться, что результат на месте и помечен сроком хранения. Затем запустить тот же расчёт дважды подряд и убедиться, что появилась одна задача, а не две. Затем оборвать сеть на этапе опроса и проверить, что после восстановления статус подтянулся, а расчёт не задвоился.
Один шаг, который стоит сделать первым
Прежде чем рисовать прогресс-бары, стоит завести идентификатор задачи и список состояний. Это скучная часть, но именно она решает исходную проблему: пользователь обновил страницу и всё-таки узнал результат. Проценты, этапы и красивые полоски — надстройка над этим фундаментом, и без него они только маскируют потерю задачи.
Конкретный шаг проверки: возьмите одну свою долгую операцию и пройдите по ней сценарий «закрыл вкладку — вернулся через час». Если результат находится, схема уже работает. Если нет — начинать надо с идентификатора и состояний, а не с анимации спиннера.
Повторное открытие результата с другого устройства
Представим условный расчёт, который пользователь начал на ноутбуке, а результат открыл с телефона. Если задача существует только в памяти первой вкладки, второе устройство ничего о ней не узнает. Для восстанавливаемого просмотра нужен серверный идентификатор задания, привязка к допустимому пользователю и проверка доступа при каждом запросе состояния. Один факт знания адреса результата не должен автоматически давать право видеть данные расчёта.
На втором устройстве интерфейс загружает подтверждённое состояние, а не продолжает старый визуальный процент. После завершения он показывает версию входных данных, время расчёта и сам результат либо понятную причину, почему результат пока недоступен. Если исходные данные уже изменились, это видно отдельно: старый завершённый расчёт не становится актуальным только от повторного открытия страницы.
В приёмочном прогоне закройте первую вкладку, дождитесь завершения работы и откройте разрешённый маршрут заново. Затем повторите проверку без нужных прав. Первый случай должен восстанавливать доступное состояние, второй — корректно отказывать без раскрытия результата. Это требования к проектированию приложения; серверные задания, хранение результатов и проверку полномочий нужно реализовать и проверить отдельно.
Смежный вопрос разобран отдельно: Push-уведомления в приложении: сообщение должно дожить до смысла.
Источники и границы разбора
Источники и статус проверки на 9 ноября 2026. Первичные документы поясняют указанные механизмы; доступность и границы каждого источника отмечены ниже. Методические рекомендации и условные примеры составляют самостоятельную часть разбора.
- Приложения для бизнеса
Опубликованное направление работы 404ai. Предлагаемая в статье методика не означает, что каждое описанное действие уже доступно без настройки; состав решения согласуется отдельно.
- Microsoft: Asynchronous request-reply pattern
Первичная документация указанного инструмента или механизма. Практические рекомендации и условные эпизоды статьи — самостоятельная методика, а не описание готовых функций 404ai.
- MDN: ARIA progressbar role
Первичная документация указанного инструмента или механизма. Практические рекомендации и условные эпизоды статьи — самостоятельная методика, а не описание готовых функций 404ai.
- MDN: AbortController
Первичная документация указанного инструмента или механизма. Практические рекомендации и условные эпизоды статьи — самостоятельная методика, а не описание готовых функций 404ai.