Обработка ошибок API в React
19 октября 2022 г.Введение
Недавно я наткнулся на интересный баг, звучал он примерно так: пользователь что-то делает, но ничего не происходит. Немного покопавшись, я понял, что проблема была в том, что бэкенд возвращал ошибку, а со стороны клиента она никак не обрабатывалась. Немного подумав, я решил сделать обработчик ошибок по умолчанию для всего приложения. И сегодня я расскажу, какие форматы для распространенных обработчиков существуют и почему лучше договориться о едином формате с бэкендом, чем городить каждый раз новое решение.
Окончательное решение TL/DR
https://gist.github.com/pivaszbs/b44e534a0fc01efb6a1b8ba55011c40b?embedable=true р>
Пошаговое объяснение
Определить требования (мой пример)
- Ошибка должна быть доступна извне (обработчик не должен ее потреблять). Это важно
потому что каждый раз, когда вы прячете ошибки за абстракциями, у вас потом возникают большие трудности. 2. Обработчик должен уметь работать с Zero-конфигурацией (настроены по умолчанию). Это важно, потому что с ним легче читать и работать
Что нужно сделать
- Согласитесь с бэкэндом по поводу общего формата ошибки, вы можете выбрать один из универсальных описанных ниже
// Backend error format
type ApiError = {
message: string
}
- Создайте общий обработчик запросов, который можно настроить, например:
type Config = { errorHandler: (errors: ApiError[]) => void }
// hook for common usage
const useQuery = ({ errorHandler = defaultErrorHandler, url }: { url: string } & Partial<Config>) => {
const [errors, setErrors] = useState();
const [data, setData] = useState();
const [loading, setLoading] = useState();
useEffect(() => {
setLoading(true);
fetch(url)
.then(data => data.json())
.then(({ statusCode, status, ...data }) => {
if (statusCode === 404) {
const errors = [data.message];
errorHandler(errors);
setErrors(errors);
} else {
setData(data);
}
})
.catch(e => {
setErrors([e])
})
.finally(() => {
setLoading(false)
})
}, [url])
return { errors, data, loading }
}
- Используйте общий обработчик запросов везде и будьте счастливы и радостны тем, что пользователи никогда больше не будут озадачены тем, что происходит с ошибками
машинопись
const {порода} = useParams();
const {данные, ошибки} = useQuery({
URL-адрес: `https://my-random-url`,
обработчик ошибок: console.error
});
Мое объяснение реализации
- Я выбрал внутренний рабочий формат с Both, потому что иногда мне нужно извлечь ошибки с помощью разных функций и легко разложить свое решение на небольшие функции (RequestWrapper — простой пример монады Both). Кроме того, он лучше типизирован, чем метод catch, поэтому вы можете попробовать сделать это таким образом :)
type RequestWrapper<T, E extends ApiError> = [T, undefined] | [undefined, E[]];
const convertToEither = async <T, E extends ApiError>(req: Promise<T>): Promise<RequestWrapper<T, E>> => {
try {
return [await req, undefined];
} catch (e) {
return [undefined, [e]]
}
}
- Мне сейчас лень реализовывать logger и toast, поэтому я просто издеваюсь над этим :)
```машинопись класс Тост { статический showError = console.error;
класс Loggger { статический logError = console.log } ```
3. Я не хочу обрабатывать и извлекать ошибки в одном месте, поэтому решил сделать функцию извлечения
```машинопись
const extractErrors = async
return wrapper;
} ```
4. Я хочу передать свой обработчик ошибок извне, поэтому я настраиваю его фабрикой
```машинопись
const defaultErrorHandler =
const defaultErrorHandlerFabric = (errorHandler = defaultErrorHandler) => async
return wrapper;
} ```
5. Просто соберите все вместе
```машинопись
const handleRequest =
const useQuery = ({ errorHandler = defaultErrorHandler, url }: { url: string } & Partial
useEffect(() => {
setLoading(true);
handleRequest(fetch(url), { errorHandler })
.then(([data, errors]) => {
setErrors(errors);
setData(data);
})
.finally(() => {
setLoading(false)
})
}, [url])
return { errors, data, loading }
} ```
Заключительные мысли
- Всегда старайтесь обрабатывать ошибки, пользователи не знают, как работает ваш сервер)
- Не прячьте некоторые действительно необходимые вещи под абстракцию (например, скрытие ошибок), лучше настройте их снаружи
- Не беспокойтесь, что все ваши запросы будут обрабатываться одним методом, ничего страшного (если страшно, помните, что вы рисуете все приложение с помощью react)
- Используйте любой из них для улучшения типов Typescript
- Попробуйте этот подход в своем приложении :)
Оригинал