Есть 2 типа разработчиков: те, кто никогда не использует try/catch, и те, кто оборачивает каждую строчку кода try/catch.
Я, конечно, преувеличиваю, но нечто подобное действительно происходит. Дело в том, что блок try/catch не влияет напрямую на функциональность вашей системы, и его трудно измерить, разумно ли он используется в исходном коде проекта.
Я не собираюсь писать ультимативное руководство, как правильно использовать try/catch (если бы я только знал это), но я хотел бы поделиться некоторыми идеями о том, что выглядит значимым, а что бессмысленным для меня:
Ленивый разработчик
try { const data = await getDataFromApi() const userInput = getUserInput() const processedData = processTheDataSomehow(data, userInput) await storeProcessedDataToApi(processedData) } catch(e) { console.log(e.error) }
Мы можем ожидать, что каждая функция будет выдавать разные ошибки, и мы сможем идентифицировать их таким образом.
Но это не совсем так — например, есть 2 разных HTTP-вызова, которые будут отправлять одинаковые ошибки — вы не сможете их различить, если вам нужно. Кроме того, блок catch предназначен не только для протоколирования ошибок. Возможно, вы захотите ввести резервное решение для немедленного решения проблемы. Например, когда пользователь потерял подключение к веб-сокету, приложение может попытаться отправить/получить те же данные через API.
Добросовестный разработчик
let data; try { data = await getDataFromApi() } catch(e) { console.log(e.error) } let userInput; try { userInput = getUserInput() } catch(e) { console.log(e.error) } let processedData try { processedData = processTheDataSomehow(data, userInput) } catch(e) { console.log(e.error) } let processedData try { processedData = storeProcessedDataToApi(data, userInput) } catch(e) { console.log(e.error) }
Это просто ужасно!
Хоть здесь и решены проблемы из предыдущего примера, но появились новые — строк кода стало в разы больше, мутируется каждая переменная, читабельность значительно снизилась.
Глобальный уровень
async function doTheJob(userInput){ const data = await getDataFromApi() const processedData = processTheDataSomehow(data, userInput) const await storeProcessedDataToApi(processedData) } try { await doTheJob() } catch(e) { analiticService.send('during doing the job something happened', {error: e.error}) }
Это похоже на первый пример, верно? Нет.
Разница семантическая — здесь у вас есть функция, которая оборачивает какой-то завершенный фрагмент функциональности (с точки зрения пользователя), поэтому перехват исключений функции имеет четкий смысл; например, вы можете сохранить эту аварию в своей аналитической системе.
Низкий уровень
async function httpService() { try{ return await axios.get() } catch (e) { console.log( 'the real place where an error might happen, you may do something with it here and then throw the error again just to notify upper level code' ) throw new Error(e) } } async function getDataFromApi() { return await httpService.get() } async function doTheJob(userInput){ const data = await getDataFromApi() const processedData = processTheDataSomehow(data, userInput) const await storeProcessedDataToApi(processedData) } try { await doTheJob() } catch(e) { analiticService.send('during doing the job something happened', {error: e.error}) }
Я просто указал функции вплоть до того места, где он выполняет внешний пакет. В этом примере совершенно очевидно, что перехват исключений из getDataFromApi или storeProcessedDataToApi не имеет смысла, поскольку ошибка может возникнуть только в гораздо более глубокой функции. Хотя для обеих функций это будет одинаково — при таком подходе вы можете использовать трассировку стека и точно знать, какая из них не удалась.
Заключение
Может быть сложно понять, что обернуть с помощью try catch, особенно если у вас нет определенных требований и бизнес-логика проекта не построена на такого рода событиях; мои подходы не являются «правильным способом сделать это» — я не думаю, что таковой вообще существует.
Я только поделился тем, как я решаю, когда ловить или не ловить.