throw new Error или throw error в nestjs. Стоит ли вести спор с тим лидом?
Нам необходимо отправлять апи запросы в сторонний сервис через библиотеку graphql-request, которая внутри выполняет запрос используя axios. При обработке ошибок у нас с лидом образовался спор как лучше это реализовать. Лид настаивает на том, что throw new Error является необходим по следующим причинам
- "без throw new Error не будет стека"
- "Это глобальный подход, нивелирующий ошибки в будущем"
- "Писать new Error надо как защиту от да***ов. Писать нужно так, будто все вокруг да***бы и тебе надо от них защищаться"
- "Ошибка из акисоса всегда будет возвращаться как экземпляр Error, но в либе на 50млн загрузок тоже может быть ошибка"
Пример финального результата от лида:
корневой gql запрос
api/api.service.ts
async request(document, variables) {
try {
const response = await this.gqlClient.request(document, variables)
return {
data: response,
error: undefined,
}
} catch (error) {
// ...логика повторного запроса при неудаче
if(error.response){
return {
data: undefined,
error: {
code: error.response.status,
message: error.response.message,
},
}
}
throw new Error(error)
}
}
кастомная ошибка
const errorMessages = {
UNAUTHORIZED: 'Ошибка авторизации',
CAT_NOT_FOUND: 'Кот не найден'
//...остальные ошибки
}
class CastomError extends Error {
constructor(name, message) {
super(message || errorMessages[name])
this.name = name
}
}
каждый отдельный запрос апи использует общий requst и общий обработчик ошибок handleCommonError
async getCat(variables) {
const { data, error } = await this.request(mutaion, variables)
if (error) {
if (error.code === 404) {
throw new CastomError('CAT_NOT_FOUND')
}
throw this.handleCommonError(error)
}
return data
}
handleCommonError(error) {
if (error.code === 401) {
throw new DvizhError(DvizhErrorName.UNAUTHORIZED)
}
//...
throw new Error(error)
}
использование методов апи в сервисе контролера.
resourse/resourse.service.ts
async getCat(body) {
try {
await this.apiService.createCat(body)
} catch (error) {
throw handleApiError(error)
}
//... какой-то запрос в базу
let cat
try {
cat = await this.apiService.getCate(body)
} catch (error) {
throw handleApiError(error)
}
return cat
}
const handleApiError = (error) => {
if (error instanceof CastomError) {
switch (error.name) {
case errorMessages.CAT_NOT_FOUND:
throw new NotFoundException(error.message)
case errorMessages.UNAUTHORIZED:
throw new UnauthorizedException(error.message)
}
}
throw new Error(error)
}
Из-за того что требуется возвращать в handleApiError ошибку обязательно как new Error. На каждый метод приходится дублировать try catсh. Хендлер нельзя использовать глобально для метода, т.к. тогда в handleApiError нужно будет дополнительно проверять ещё и на HttpExeption. Лид считает, что это наоборот дает возможность не оборачивать весь метод в try catch и лучше обработать каждый запрос по отдельности. Для чего возвращается new Error в handleApiError, если из методов апи обработчик handleCommonError возвращает уже всегда new Error?
Лично мне этот подход не нравится. throw new Error только все усложняет. Axios всегда должен возвращать ошибку со стеком. И мы не сможем как-то обезопасить себя от битого кода. Если код пройдет ревью в таком случае можно будет сделать не только throw 'string', но и к примеру убрать где-то авторизацию, валидаторы, да и в принципе сделать что угодно
Как вы считаете есть ли здесь на самом деле необходимость в new Error. И хорошее ли решение возвращать { data, error } из метода request и проверять общие ошибки в каждом методе по-отдельности, вместо того чтобы оформить один глобальный обработчик на все методы внутри request
После данного кейса было решено использовать throw new Error() везде. Какие по-настоящему есть оправданные случае в несте, где нужно использовать throw new Error(error) вместо throw error? И как вы считаете стоит ли в целом вести спор до талого, чтобы довести свою точку зрения когда идея не принимается, либо же проще работать по принципу "работа 8часов, что поручено то и делается"?
Ответы (1 шт):
существуют фильтры в Nest, которые позволяют обрабатывать ошибки и не приходится писать обработку в каждом роуте.
библиотека должна выбрасывать исключение
CatsServiceNotFoundи не привязываться к сущьности, ну или првязываться только сообщением. Если у вас нет прям спицифичной логики под такое решение.Есть такой функционал как
instanceofи как раз в глобальном фильтре можно проверитьif (exception instanceof CatsServiceNotFound) { throw new NotFoundException(error.message) }или вообще наследоватьCatsServiceNotFoundотNotFoundException, что позволит не мапить ошибки в дальнейшем коде. Делать мапинг ошибок по их имени так себе затея, хоть они и enum у вас.
Также в этом коде непонятно зачем каждый кусок кода оборачивать в свой try, полчается много лишних обработок. Ведь throw error останавливает выполнение дальнейшего когда.
P.S. моё лично мнение о вашем подходе...