Рубрики
Без рубрики

Тип безопасным Обработка ошибок в Teadercript

В этом посте я представляю концепцию типа результата, что исключает необходимость бросков исключений и снижает риск ошибок времени выполнения.

Автор оригинала: Giorgio Delgado.

Мы все были там раньше. Мы пишем функцию, которая должна иметь дело с некоторым краем, и мы используем бросить Ключевое слово для решения этой ситуации:

type ResponseData = {
  statusCode: number
  responseBody?: ResponseBody
}

const makeHttpRequest = async (url: string): Promise => {
  if (!isUrl(url)) {
    throw new Error(
      'Invalid string passed into `makeHttpRequest`. Expected a valid URL.'
    )
  }

  // ...
  // other business logic here
  // ...
  
  return { ... } // ResponseData
}

Теперь представьте через месяц спустя, вы работаете над своим проектом, когда вы или коллега забудьте обернуть makehttprequest внутри попробуйте/поймать блокировать.

Здесь происходят две вещи:

  • Компилятор больше не в состоянии сказать, ваш код безопасен от ошибок выполнения. Другими словами, используя бросить не типовойafe. И так же опасно, как любой Отказ Они оба разбавляют преимущества использования TypeScript в первую очередь.

  • Потому что ни компилятор, ни типы не говорят вам, что makehttprequest Может выйти из строя (читать: бросить), вы в конечном итоге получите ошибку выполнения. Это пустая трата времени, денег и счастья для всех. Люди начинают задавать, почему они используют TypectScript, если компилятор не помогает им с чем-то таким базовым, как добавление попробуйте/поймать блокировать.

Так что вопрос в том,

Как мы кодируем сжатие в типографию?

Во-первых, давайте начнем с признания этого бросить не типовойafe. Мы должны использовать другой подход для того, чтобы получить компилятор Tymdercript на нашей стороне.

Что если у нас было Тип или Интерфейс который представлял результат вычисления, который может потерпеть неудачу?

Наш тип будет представлять два простых результата:

  • Кейс успеха: который бы вернул/содержать содержать «значение успеха» (I.E. Reffectedata в случае makehttprequest )
  • Сбой случая: который бы вернул/содержит полезную информацию о Почему произошла неудача

Давайте позвоним нашему типу, что-то интуитивно понятное, как Результат Отказ Давайте назовем наш вариант успеха Хорошо и наш отказ вариант Err Отказ

Таким образом, если бы мы были формализовать наш тип в код, он будет выглядеть что-то подобное:

type Result
  = Ok // contains a success value of type T
  | Err // contains a failure value of type E

Возвращаясь к нашему makehttprequest Функция, мы хотели бы кодировать потенциал для неудачи в типографию.

Таким образом makehttprequest будет иметь следующую подпись:

makeHttpRequest(url: string): Promise>

И определение функции будет выглядеть что-то подобное:

// utility functions to build Ok and Err instances
const ok = (value: T): Result => new Ok(value)

const err = (error: E): Result => new Err(error)

const makeHttpRequest = async (url: string): Promise> => {
  if (!isUrl(url)) {
    return err(new Error(
      'Invalid string passed into `makeHttpRequest`. Expected a valid URL.'
    ))
  }

  // ...
  // other business logic here
  // ...
  
  return ok({ ... }) // Ok(ResponseData)
}

Конечно ERR (новая ошибка («...»)) кажется немного утомительным. Но вот некоторые вещи, которые вы должны знать:

  • Аргумент Err Функция должна быть типа Е или вы получите ошибку компиляции (несоответствие типа) между типом внутри Err и возвратный тип makehttprequest (где тип E тип представлен как ошибка экземпляр).

    • Свидетельство, я просто выбрал Ошибка . Как тип для Е Ради простоты … Значение Е может быть все, что вы хотите! Больше на это немного!
  • Пользователь makehttprequest Можно использовать эту функцию, не опасаясь, что это может произвольно бросить. Нет больше ошибок времени выполнения 🚀

  • Автор makehttprequest Функция также не должна беспокоиться о написании и обновлении документации каждый раз, когда появляется новый крайний корпус, который приведет к ошибке функции. Все поведение функции кодируется в типе возврата. Соответственно, тип служит документацией сейчас: « makehttprequest – асинхронная функция, которая может либо добиться успеха с respurededata или сбой с помощью |

… “Но подожди, как мне получить значение T Value или E Значение, которое завернуто внутри Результат ?”

Отличный вопрос. Позвольте мне показать вам, как. Мы собираемся использовать пакет, который я сделал [indly] по имени Неворотный Отказ

> NPM Установка неворота

import { ok, err, Result } from 'neverthrow'

// we'll keep this simple
type ResponseBody = {}

interface ResponseData {
  statusCode: number
  responseBody?: ResponseBody
}

const makeHttpRequest = async (
  url: string
): Promise> => {
  if (!isUrl(url)) {
    return err(new Error(
      'Invalid string passed into `makeHttpRequest`. Expected a valid URL.'
    ))
  }

  // ...
  // other business logic here
  // ...
  
  return ok({ ... }) // Ok(ResponseData)
}

Поэтому в настоящее время мы находимся в том же месте, что мы были на последнем фрагменте кода, кроме этого времени, мы используем Неворотный упаковка.

Если бы вы прочитали через Неворотный Документация Вы увидите, что Результат имеет .map Метод, который принимает T Значение внутри Результат и преобразует его во все, что вы хотите.

Вот пример:

import { makeHttpRequest } from './http-api.ts'

const run = async () => {
  // unwrap the Promise
  // at this point
  // we have a Result
  const result = await makeHttpRequest('https://jsonplaceholder.typicode.com/todos/1')
  
  result.map(responseData => {
    console.log(responseData)
  })
}

run()

Но подождите, что если переменная результата содержит Е значение? Другими словами, это Err вместо Хорошо Отказ

Ну, опять же, документы для Неворотный Покажите, как справиться с этой ситуацией … просто используйте Maperr !

import { makeHttpRequest } from './http-api.ts'

const run = async () => {
  // unwrap the Promise
  // at this point
  // we have a Result
  const result = await makeHttpRequest('https://jsonplaceholder.typicode.com/todos/1')
  
  result.mapErr(errorInstance => {
    console.log(errorInstance)
  })
}

run()

Самая красивая вещь о Результат с такой Они церемымыми ! Вот вышеуказанный код в более реалистичном примере:

import { makeHttpRequest } from './http-api.ts'

const run = async () => {
  // unwrap the Promise
  // at this point
  // we have a Result
  const result = await makeHttpRequest('https://jsonplaceholder.typicode.com/todos/1')
  
  result
    .map(responseData => {
      // do something with the success value
    })
    .mapErr(errorInstance => {
      // do something with the failure value
    })
}

run()

Есть намного больше, вы можете сделать с Результат Тип (проверьте Документы ), но карта ING – самая важная часть API.

Сделать ваши виды более интуитивными

Если вы начнете использовать Результат Много в ваших возвращаемых типах вы можете заметить две вещи:

  • Значение Результат S не очень ясно

    • Пример: Результат запроса баз данных может быть что-то вроде Обещание <результат > пока Результат сетевого звонка может быть что-то вроде Обещание <результат > Отказ
  • Типы действительно длинные и многословные. Например, выше у нас был Обещание <результат > … Это несколько запугивающим типом!

Чтобы решить обе проблемы, вы можете использовать Тип псевдонимов !

Вот пример. Вместо того, чтобы иметь функцию, вернуть общий Результат с ДЕРЕРРОР Как Е Тип, почему бы не псевдоним этот тип к чему-то гораздому интуитивному?

type DbResult = Result

Теперь ваша функция может просто вернуть Обещание > Отказ Это намного более сжато!

Кроме того, ваш тип сейчас кодирует значение. Вышеуказанный тип говорит, что происходит что-то Async, которое может потерпеть неудачу, и я знаю, что он имеет дело с базой данных. Аккуратный!

Вот реальный пример от одного из моих проектов:

handler: (req: Request, res: SessionManager) => DecodeResult>>

Итак, обработчик это функция, которая делает несколько вещей:

  • Он делает некоторую декодирование/десериализацию входящих данных
  • Затем это делает что-то async, которое генерирует RouterSult С некоторыми данными типа Т

Я точно знаю, что произойдет, только чтение типов. И прекрасная вещь в том, что я никогда не получу ошибки времени выполнения, потому что ни один из моих кодов не бросает (и все 3-й партийные Libs, которые я зависим от, были обернуты к возвращению результат ).

Резюме

  • Избегайте использования бросить если ты можешь.

    • Пользователи ваших API не требуются для поймать (компилятор не применяет его). Это означает, что вы в конечном итоге попадут в ошибку выполнения … это просто вопрос времени
    • Использование бросить заставляет вас поддерживать документацию, которая в конечном итоге станет устаревшей
    • Если вам удастся поддерживать документацию, многие люди не будут полностью начать читать через документацию, и не поймет, что ваши функции бросают в определенные сценарии
  • Закомите потенциал для сбоя в ваши типы, используя Результат с

    • Типы самодоступны, и они не могут стать «устаревшей»
    • Пользователи предоставляются дружелюбные API, которые позволяют им справиться с неудачными значениями безопасным способом (используя Map и Maperr )
  • Там есть полностью проверенная и тип, проверенная типа NPM для этого называется Неворотный … Попробуйте!