Мы можем улучшить нашу обработку и композицию ошибок, используя класс результатов и несколько других инструментов из мира функционального программирования.
Вместо того, чтобы бросать ошибки, мы обертываем наши результаты. Либо результатом является значение ошибки, либо значение успеха, в процессе, документируя возможные ошибки. Абоненты должны сначала исследовать и развернуть результат, обрабатывая либо успех, либо сбой. Проложить путь для более функционального программирования и композиции.
Для более полного знакомства с классом результата и ориентированным на железную дорогу программирование:
- https://fsharpforfunandprofit.com/rop/
- https://dev.to/_gdelgado/type-safe-error-handling-in-typescript-1p4n
- https://khalilstemmler.com/articles/enterprise-typescript-nodejs/handling-errors-result-class/
Для проверки реализаций Github #Железнодорожный ориентированное программирование ; ROP во многих языках программирования ( Python/go/java/c#/f# так далее)
В этих сериях я поделюсь своими выводами во время моего (захватывающего) путешествия.
Императивная выборка
const r = doSomeAction() // Resultif (r.isErr()) { // r: Error if (r.error instanceof SomeVariableIsInvalid) { ctx.body = r.error.message ctx.statusCode = 400 } else { ctx.statusCode = 500 } return } // r: Ok ctx.body = r.value ctx.statusCode = 200
Доосомеакция может быть реализовано как:
function doSomeAction(): Result{ if (!someVariableIsValid) { return err(new SomeVariableIsInvalid("some variable is not valid") } if (!isServiceAvailable()) { return err(new ServiceUnavailableError("The service is currently unavailable") } return ok("success response") }
Функциональный образец
doSomeAction() // Result.map(value => { ctx.body = value ctx.statusCode = 200 }) .mapErr(error => { if (error instanceof SomeVariableIsInvalid) { ctx.body = error.message ctx.statusCode = 400 } else { ctx.statusCode = 500 } })
Все «операторы» должны жить на объекте результата, и, следовательно, расширение сложнее. (Это похоже на то, как, например, rxjs начал)
Функциональная композиция
doSomeAction() // Result.pipe( map(value => { ctx.body = value ctx.statusCode = 200 }), mapErr(error => { if (error instanceof SomeVariableIsInvalid) { ctx.body = error.message ctx.statusCode = 400 } else { ctx.statusCode = 500 } }) )
Операторы теперь просто функционируют, легко расширять и катить наши собственные 😉 ( Rxjs v5.5 Пользователи могут увидеть здесь некоторые сходства)
Данные в последний раз
const pipeline = pipe(
map(value => {
ctx.body = value
ctx.statusCode = 200
}),
mapErr(error => {
if (error instanceof SomeVariableIsInvalid) {
ctx.body = error.message
ctx.statusCode = 400
} else {
ctx.statusCode = 500
}
})
)
pipeline(doSomeAction())
Так трубопровод теперь многоразовый. Если только TC39 Предложение-Pipeline-Operator Скоро приземлится, чтобы мы получили синтаксический сахар, который скрывает котелную пластину и синтаксический шум:)
Здание на вершине
Дальнейшее разложение на отдельные функции, так что они становятся повторными или разделять уровни абстракции, чтобы трубопровод становился легче читать.
const writeSuccessResponse = value => {
ctx.body = value
ctx.statusCode = 200
}
const writeErrorResponse = error => {
if (error instanceof SomeVariableIsInvalid) {
ctx.body = error.message
ctx.statusCode = 400
} else {
ctx.statusCode = 500
}
}
const pipeline = pipe(
map(writeSuccessResponse),
mapErr(writeErrorResponse)
)
Дальнейшее разложение:
const writeSuccessResponse = value => {
ctx.body = value
ctx.statusCode = 200
}
const writeDefaultErrorResponse = error => {
ctx.statusCode = 500
}
const writeSomeVariableIsInvalidErrorResponse = error => {
if (error instanceof SomeVariableIsInvalid) {
ctx.body = error.message
ctx.statusCode = 400
}
}
const pipeline = pipe(
map(writeSuccessResponse),
mapErr(writeDefaultErrorResponse),
mapErr(writeSomeVariableIsInvalidErrorResponse),
)
Возможно, другой вариант:
const mapErrIf = (errorHandler: error => void, predicate: error => boolean) =>
error => {
if (!predicate(error)) { return }
errorHandler(error)
}
}
// usage
mapErrIf(_ => ctx.statusCode = 400, error => error instanceOf SomeVariableIsInvalid)
И, конечно, есть много других вариантов и форм композиции, пусть это будет учет читателя 😉
Структура и пример кода
Я работаю над структурой приложений, изучая эти темы, которые широко используют композицию трубопровода, в комплекте приложением примеров!
Исходный код:
- FP-App Framework
- Пример приложения
- NeverThrow Pipe Extensions
- Использует NeverThrow fork (разветвляется от gdelgado14/ineverthrow )
Что дальше
Далее в серии я планирую представить более продвинутые концепции, как плоская карта , totup , футболка и другие:)
дальнейшее чтение
Обязательно также проверьте gcanti/fp-ts ; Ориентированная на программировавшая библиотека, особенно V2, выглядит очень многообещающей из -за аналогичной композиции трубы!
Оригинал: “https://dev.to/patroza/result-composition-and-error-handling-m8i”