Типы в стиле Result/Either
Явно моделируйте успех и сбой с помощью объединения Result/Either, добавляйте вспомогательные функции (map/flatMap/mapError) и сохраняйте предсказуемость потоков без повсеместного использования try/catch.
«Типы в стиле Result/Either» — бесплатный урок TypeScript Academy на CoddyKit. Это урок 1 из 2. Ты можешь прочитать весь урок бесплатно ниже — а потом практиковать его прямо в браузере с встроенным редактором кода и ИИ-репетитором 24/7. Это часть пути обучения TypeScript Academy, и твой прогресс синхронизируется между веб-версией и приложением CoddyKit. Курс TypeScript Academy содержит 2 уроков всего.
Введение
Цель: сделать сбои видимыми. Вы смоделируете результаты с помощью объединения с различаемыми вариантами, соберете операции с помощью вспомогательных функций и преобразуете исключения на границе системы.
- Тип результата
- map/flatMap/mapError
- Интеграция с HTTP
Основы результата
Создайте небольшую утилиту для результата: конструкторы ok/err, а также map, flatMap, mapError для композиции.
export type Ok<T> = { ok: true; value: T }
export type Err<E> = { ok: false; error: E }
export type Result<T, E> = Ok<T> | Err<E>
export const ok = <T>(value: T): Ok<T> => ({ ok: true, value })
export const err = <E>(error: E): Err<E> => ({ ok: false, error })
export function map<T, E, U>(r: Result<T, E>, f: (t: T) => U): Result<U, E> {
return r.ok ? ok(f(r.value)) : r
}
export function flatMap<T, E, U>(r: Result<T, E>, f: (t: T) => Result<U, E>): Result<U, E> {
return r.ok ? f(r.value) : r
}
export function mapError<T, E, F>(r: Result<T, E>, g: (e: E) => F): Result<T, F> {
return r.ok ? r : err(g(r.error))
}Доменные ошибки
Представляйте доменные ошибки как объединение с различаемыми вариантами и полем tag. Каждый вариант содержит точные данные для формирования понятных сообщений.
export type CreateUserError =
| { tag: "InvalidEmail"; detail?: string }
| { tag: "Duplicate"; email: string }
export function validateEmail(email: string): Result<string, CreateUserError> {
const okEmail = /.+@.+/.test(email)
return okEmail ? ok(email.toLowerCase()) : err({ tag: "InvalidEmail", detail: "bad format" })
}
const existing = new Set(["ada@example.com"]) // pretend DB
export function saveUser(email: string): Result<{ id: string }, CreateUserError> {
if (existing.has(email)) return err({ tag: "Duplicate", email })
return ok({ id: Math.random().toString(36).slice(2) })
}Композиция потоков
Объединяйте проверки с помощью flatMap. Используйте оператор выбора с проверкой never, чтобы обеспечить полноту обработки во время компиляции.
export function createUser(flowEmail: string) {
return flatMap(validateEmail(flowEmail), email => saveUser(email))
}
// Use
const r = createUser("Ada@example.com")
if (r.ok) {
console.log("id:", r.value.id)
} else {
switch (r.error.tag) {
case "InvalidEmail": console.error("invalid email:", r.error.detail); break
case "Duplicate": console.error("already used:", r.error.email); break
default: ((x: never) => x)(r.error) // exhaustiveness
}
}Исключение → результат
На границах операций ввода-вывода перехватывайте исключения и преобразуйте их в Result. У вызывающего кода остается единый путь обработки ошибок.
export async function fromPromise<T>(p: Promise<T>): Promise<Result<T, { tag: "Exception"; message: string }>> {
try { return ok(await p) }
catch (e) { return err({ tag: "Exception", message: e instanceof Error ? e.message : String(e) }) }
}
// Example: wrap a fetch
export async function fetchJson<T>(url: string): Promise<Result<T, { tag: "Exception"; message: string }>> {
return fromPromise(fetch(url).then(r => r.json() as T))
}Советы по сопоставлению с HTTP
Интеграция с HTTP: сопоставляйте доменные ошибки с 400/409, а исключения операций ввода-вывода — с 502/503. Используйте небольшую функцию преобразования, чтобы контроллеры оставались чистыми и предсказуемыми.
Проверка пользы результата
Быстрая проверка: почему следует предпочитать объединение результатов с различаемыми вариантами?
Итоги
Итоги: определите небольшой тип результата, объединяйте операции с помощью map/flatMap, представляйте доменные ошибки как объединения и преобразуйте исключения на границах системы.
Часто задаваемые вопросы
Урок «Типы в стиле Result/Either» бесплатный?
Да — полный текст урока «Типы в стиле Result/Either» бесплатно доступен здесь в веб-версии. Чтобы практиковать его интерактивно (встроенный редактор кода и ИИ-репетитор 24/7) и разблокировать остальной курс TypeScript Academy, подпишись на CoddyKit PRO. Курс TypeScript Academy содержит 2 уроков всего.
Чему я научусь в уроке «Типы в стиле Result/Either»?
Явно моделируйте успех и сбой с помощью объединения Result/Either, добавляйте вспомогательные функции (map/flatMap/mapError) и сохраняйте предсказуемость потоков без повсеместного использования try/c… Ты практикуешь TypeScript Academy с помощью реального кода, который запускаешь прямо в браузере, и ИИ-репетитор 24/7 отвечает на твои вопросы во время урока.
Нужен ли мне опыт, чтобы начать TypeScript Academy?
Предыдущий опыт не требуется. TypeScript Academy на CoddyKit структурирован для всех уровней — от новичков до продвинутых, поэтому ты можешь начать отсюда или с самого начала и учиться в своем темпе. Это урок 1 из 2.
Сколько времени занимает урок «Типы в стиле Result/Either»?
Большинство уроков CoddyKit занимают около 5–10 минут. Каждый из них компактный и интерактивный, поэтому ты постоянно делаешь прогресс и продолжаешь с того же места в веб-версии и приложении.
Можно ли писать и запускать код в этом уроке TypeScript Academy?
Да. Каждый урок TypeScript Academy включает встроенный редактор кода, поэтому ты пишешь и запускаешь реальный код прямо в браузере и получаешь моментальную обратную связь от AI — локальная установка не требуется.
Все уроки этого курса
- Типы в стиле Result/Either
- Полная обработка ошибок с дискриминируемыми объединениями