Тип Promise имеет «слепое пятно» в продакшене
Представьте типичный контракт в вашей кодовой базе: Promise<User>. Этот тип идеально описывает идеальный сценарий — вы ожидаете объект User, если всё идет хорошо. Но как насчет реального мира? Он ничего не говорит об HTTP-ошибке, отсутствующем пользователе или запросе, который зависает на неопределенный срок, заставляя ваше приложение ждать.
TypeScript, к сожалению, не типизирует отклонения промисов (rejections) надежным образом. Ваши обработчики catch часто получают значение unknown, заставляя вас во время выполнения гадать, что пошло не так. Это означает, что команды тратят драгоценное время на установление поведения при сбоях методом проб и ошибок, а не через надежные проверки компилятора.
Рассмотрим сервис, который получает профили пользователей. Вызывающим сторонам нужно больше, чем просто User или нетипизированная ошибка. Им нужно знать, следует ли автоматически повторить запрос (например, при кратковременном сбое сети), отобразить четкое сообщение «пользователь не найден» или прекратить ожидание по истечении определенного тайм-аута. Без этой ясности в ваших типах производственная среда становится «слепым пятном», скрывающим важную информацию о причинах сбоев.
Effect добавляет недостающие части в контракт
Effect меняет контракт с Promise<User> на нечто более надежное. Его основная сигнатура — Effect<A, E, R>, которая точно описывает три важнейших аспекта любого вычисления: значение успеха, типизированную ошибку и любые необходимые зависимости.
Давайте разберем это. A представляет значение, которое вы получаете при успехе, например User в нашем примере. E — это место, где происходит магия для ошибок, предоставляя типизированное объединение (union) всех ожидаемых ошибок, таких как HttpError | NotFound. Наконец, R означает «требования» (requirements) — сервисы или зависимости, необходимые коду для работы, например HTTP-клиент или подключение к базе данных.
Рассмотрим нашу функцию поиска пользователя из видео. Вместо простого Promise<User>, ее тип Effect может быть Effect<User, HttpError | NotFound, SomeHttpClient>. Это делает контракт явным: он говорит вам не только о том, что вы можете получить User, но и о том, что HttpError или NotFound являются конкретными, ожидаемыми режимами сбоя.
Это существенное отличие от обычного промиса. Вычисления Effect являются ленивыми значениями (lazy values), что означает, что они описывают, что нужно сделать, но не когда это делать. Вы составляете всё вычисление целиком, включая повторные попытки, тайм-ауты и политики обработки ошибок, до выполнения. Это делает все требования и потенциальные результаты видимыми по мере создания программы, превращая «слепое пятно» в подробную карту.
Наблюдайте, как меняется тип ошибки по мере добавления безопасности
Давайте проследим, как эволюционирует тип ошибки по мере добавления устойчивости в конвейер получения данных. Изначально наш Effect<User, HttpError | NotFound, R> может завершиться с ошибкой HttpError или NotFound.
Сначала мы применяем политику экспоненциальных повторных попыток для обработки кратковременных HttpError, но это не меняет сигнатуру типа, потому что повторные попытки не исключают возможность возникновения HttpError. Затем мы добавляем двухсекундный тайм-аут. Это немедленно вводит TimeoutError в наш тип ошибки, превращая его в Effect<User, HttpError | NotFound | TimeoutError, R>.
Затем мы явно обрабатываем случай NotFound, предоставляя резервное значение. Как по волшебству, тип NotFound исчезает из нашей сигнатуры ошибки, потому что теперь гарантируется его обработка. Наш тип становится Effect<User, HttpError | TimeoutError, R>. Это не просто хитрый трюк; это компилятор, выполняющий учет потенциальных сбоев в режиме реального времени.
Чтобы доказать это, мы сокращаем время ожидания до 50 миллисекунд. Запуск кода приводит к TimeoutError, в точности как предсказывала система типов. Этот цикл обратной связи на этапе компиляции, проверяемый во время выполнения, является мощной особенностью Effect: Production-Grade TypeScript и его подхода к тому, чтобы сделать ошибки видимыми и управляемыми.
Нравится статья? Получайте такие каждое утро на почту.
одно письмо в день · отписка в два клика · без сторонних трекеров
Результат — и цена перехода
Явное отслеживание ошибок и зависимостей особенно эффективно в критически важных сервисах, таких как аутентификация и платежи. В этих областях скрытые пути отклонения запросов — например, тайм-аут внешнего API или разрыв соединения с базой данных — значительно усложняют восстановление и проверку. Подход Effect, основанный на типах, гарантирует, что вы будете устранять эти потенциальные режимы сбоев проактивно, а не реактивно.
Effect предлагает больше, чем просто базовый тип Result. Он включает в себя надежную среду выполнения, предоставляющую инструменты для повторных попыток (retries), тайм-аутов, управления параллелизмом и зависимостями «из коробки». Вместо того чтобы мгновенно заменять ваш существующий код на основе Promise, Effect может дополнить его, позволяя постепенно внедрять и интегрировать библиотеку в конкретные, наиболее важные части вашего приложения.
Внедрение Effect требует инвестиций в обучение. Модель с ее характерной сигнатурой Effect<A, E, R> требует времени для освоения, особенно в части того, как типы ошибок развиваются в вашем конвейере. Поскольку типы Effect естественным образом распространяются за границы приложения, командам следует тщательно взвесить эти затраты на обучение и миграцию против преимуществ повышенной надежности и наблюдаемости, особенно для чувствительных систем.
Часто задаваемые вопросы
Что такое Effect в TypeScript?
Effect — это библиотека для моделирования асинхронных программ с явными значениями успеха, типизированными ошибками и необходимыми сервисами.
Чем Effect отличается от Promise?
Promise описывает значение, которое может быть успешно разрешено или отклонено, но не кодирует типы ошибок. Effect также моделирует типизированные сбои и зависимости.
Как меняются типы Effect при обработке ошибки?
Обработка типизированной ошибки удаляет этот сбой из типа ошибки Effect; добавление операции, такой как тайм-аут, может добавить новый типизированный сбой.
Стоит ли каждому проекту на TypeScript внедрять Effect?
Не обязательно. Это может помочь сложным системам, которым нужны явные сбои и элементы управления во время выполнения, но его концепции и влияние на систему типов требуют инвестиций в освоение.

