Как стать автором
Обновить

Комментарии 62

let [users, err] = await getUsers();
if (err !== null) {
    switch (err) {
    case serviceUnavailable:
        // сервис недоступен
    case notFoundError:
        // пользователи не найдены
    default:
        // действие при неизвестной ошибке
    }
}

мы гарантированно поймаем все ошибки

… и что гарантирует, что возвращенная ошибка действительно будет обработана, а не будет так, что вызывающий код просто ее отбросит и вернет users?

Тут может помочь TS: пока не будет проверен тип err, тип users будет Users | null Например

Действительно, ничто не может заставить разработчика обязательно обрабатывать все ошибки. Более того, такой способ позволяет не извлекать ошибку вообще. Но, такой код стимулирует разработчика озаботиться обработкой ошибки. В случае с try…catch, по опыту, почти все кейсы будут опущены…(

Но, такой код стимулирует разработчика озаботиться обработкой ошибки.

Не понимаю, каким образом.


В случае с try…catch, по опыту, почти все кейсы будут опущены…

Что значит "опущены"? В случае с эксепшном, если нет catch, разве ошибка не будет брошена в вызывающем коде?

Такой подход позволят узнать об ошибке «не отходя от кассы», как говориться. Более того, это банально лучше читается да и отладка такого кода, будет куда приятнее, без прыжков по коду, линейно.

Такой подход позволят узнать об ошибке «не отходя от кассы», как говориться.

Позволяет узнать — да. Но как это стимулирует их обрабатывать?


Более того, это банально лучше читается

А точно это лучше читается? Я вот предпочитаю видеть в первую очередь обработку основного сценария, а не ошибок.

Обработка ошибок должна быть неотъемлемой частью сценария, иначе, как всегда, обработка будет пропущена, как и написание тестов, я полагаю…

Обработка ошибок должна быть неотъемлемой частью сценария

Почему обработка ошибок вида "у меня тут сеть недоступна" должна быть неотъемлемой частью бизнес-сценария?


Есть сильно больше одного случая, когда достаточно прервать выполнение, отрапортовать ошибку наверх, а супервизор сам разберется.

Только проблема в раскрытии имплементации бизнес логики... если планируется обрабатывать "у меня тут сеть недоступна" не как базовый Exception, то стоит делать обертку и это будет неотъемлемой частью логики. Убирать неявное - хорошая практика.

Если планируется — то будет. А если не планируется, то не будет, и можно просто не писать лишнего кода.

ФП программисты ох как с вами не согласны, но ок...

Вариант с Go'шной обработкой ошибок, заставляет нас проверить на ошибки результат. Но с той же легкостью мы можем этого и не делать)

Ленивые разработчики будут писать что-то типа
[chats] = await httpGET('https://api.example.com/chats');

Но на CodeReview такие косяки будет заметнее, и это хорошо.
А вот что плохо, так это загрязнение кода от этих постоянных проверок.

Есть ФП подход к обработке ошибок, он сложнее, но если привыкнуть то гуд.
https://habr.com/ru/post/457098/

Да, такой подход позволяет опускать ошибки (как и в го, кстати), но позволяет меньше отвлекаться на этапе прототипирования.

По поводу «плохо» - если может возникнуть ошибка, то она возникнет) Это первое. Если мы не хотим проверять ошибки(загрязнять код(c)), то можно как в случае с try…catch замести все под коврик и просто вывести пользователю “sorry…”

Для начала, давайте вспомним, а как вообще ловят ошибки в js, будь то браузер или сервер. В js есть конструкция try...catch.

Несмотря на то, что даже в MDN фигурирует именно слово "ошибки", мне кажется более уместным всё-таки называть это исключениями. В англ. оригинале как раз используется слово "exception".

UPD: Имею в виду, что ошибки бывают восстановимыми и невосстановимыми. Во втором случае, если не делать try/catch, то программа падает и это называется исключением. А в конце этой статьи как раз приводится пример работы с восстановимыми ошибками, что в последнее время считается более правильным.

На самом деле, в реальном (более низком уровне) мире, граница между ошибкой и исключением очень размыта.

Позанудствую.

if (users.length === 0) {
  return Promise.resolve([null, notFoundError]);
}

Нулевая длина списка вряд ли должна описываться ошибкой notFoundError. Ведь коллекция `users` есть. Если бы пользователь пошёл по пути `/users/1/`, а пользователя c `id = 1` нет, то тогда можно отдать notFoundError.

Из async-функции разве не достаточно возвращать просто массив `[data, error]` без оборачивания в Promise.resolve ?

Достаточно

Так написано для большей наглядности. Да и мне так больше нравится (субъективно).

Что касается ‘not found’ , действительно, это притянутый за уши пример, тут может быть любая другая ошибка.

Далеко не всегда нужно обрабатывать ошибки и очень часто ошибка, брошенная через throw вполне себе может привести к 500 ошибке и записана куда-нибудь в лог и это будет правильно.

Все удобство в try..catch в том, чтобы отловить только те ошибки, на которые ты должен как-то специфически отреагировать. А остальные - ну а как ты их нормально обработаешь? Пусть себе ловит какой-то общий обработчик, который запишет в лог, а юзеру скажет сорян. Увидел в логе необработанную ошибку, понял, что такой ситуации можно избежать — делаешь catch и именно ее и ловишь, как-то так 🤷‍♂️

Для описанного вами случая можно использовать “switch default”

А зачем, когда я просто не хочу смотреть на ошибку ни на одном из уровней, которых может быть дофига

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

Прекрасно представляю, если жить без сервиса можно - вполне себе ок обработать ошибки от него. Если важен и без него никак (обычно это как раз такие) - то сорян, никакой обработки делать не нужно, пусть ловится глобально.

Все это надо держать в балансе, заставлять разработчика проверять ошибки всегда — неправильно

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

… а обычный catch не позволяет?

Позволяет!

Судя по всем показателям (комменты, карма, рейтинг), люди восприняли мой текст, как призыв все бросить и безоговорочно начать использовать такой подход вообще везде. Вероятно, виноват мой стиль неопытного повествователя (первая публикация). Но идея такого подхода, фактически, не заменить, но дополнить стандартный подход. Всего навсего, ещё один инструмент, для некоторых ситуаций. Действительно, если логика приложения не требует такого подхода, то и использовать его не обязательно.

Но идея такого подхода, фактически, не заменить, но дополнить стандартный подход

Когда в системе два подхода к обработке ошибок, программисты начинают их путать.

Я выше приводил примеры такого как ошибки обрабатываются в случает XMLHttpRequest и readFile, в первом случае - подписка на событие ошибки, второй - ошибка передаётся первым аргументом в колбеке. Вообще забавна такая реакция сообщества, такое ощущение, что никто не знает про вышеописанные способы/приёмы. Вариант который предлагаю я, по сути, синтаксический сахар для ErrorFirstCallback. С трудом верится, что люди не знают такие вещи, видимо, такие люди просто более активны в комментариях…

Я выше приводил примеры такого как ошибки обрабатываются в случает XMLHttpRequest и readFile, в первом случае — подписка на событие ошибки, второй — ошибка передаётся первым аргументом в колбеке.

Это называется "легаси". И это как раз и неудобно, что для обработки ошибок используются разные подходы.

Подход у XMLHttpRequest - показывает, что жизнь может быть чуть сложнее. С readFile, действительно, сейчас есть возможность использовать вариант с промисом. Но старый вариант позволял выполнить определенный код не теряя "контекст".

Подход у XMLHttpRequest — показывает, что жизнь может быть чуть сложнее.

Так это неудобно же. Наличие нескольких разных способов обработать ошибку — неудобно (потому что лишает код консистентности).

И да, и нет. XMLHttpRequest имеет много разных событий: error, abort, timeout... Это как раз пример того, что ошибок может быть много и разных, и специфика этих ошибок в том, что их нужно по разному обрабатывать. Нас же не смущает, то, что мы используем колбеки для подписки на события. Timeout, abort, error - тоже события. С другой стороны, с "религиозной" точки зрения - "Это другое!".

Это как раз пример того, что ошибок может быть много и разных, и специфика этих ошибок в том, что их нужно по разному обрабатывать.

Нет, это пример того, что бывают события, которые отражают ошибку.


специфика этих ошибок в том, что их нужно по разному обрабатывать

Ну так ошибки и в catch можно по-разному обрабатывать.


Нас же не смущает, то, что мы используем колбеки для подписки на события.

Если эти события нужны только для поддержания асинхронии — смущает.


Надо разделять ошибку как результат процесса (тогда ошибок больше возникнуть не может, можно ее вернуть из метода, как в вашем примере, или бросить как в try-catch), или ошибку как событие в процессе (они могут возникнуть еще потом, процесс не остановился, тогда ее нельзя вернуть, не годится ни try-catch, ни ваш подход).

Ну так ошибки и в catch можно по-разному обрабатывать.

Да, но тут ты на уровне интерфейса (названия событий) понимаешь какие могут быть ошибки.

Возвращаемся к try...catch

try {
  ...
	let data = await getDataFromCache();
	if (!data) {
    // если в кэше этих данных нет
  	data = await getDataFromDB(); 
  }
	...
} catch(e) {
	// здесь может быть ошибка из кэша или db
}

Иначе

let data, err;
[data, err] = await getDataFromCache();
if (err !== null) {
	// например, кэш не успел подняться после 
  // перезагрузки, это не повод идти в catch, 
  // можно побробовать взять из базы или что то еще 
}

Да, можно использовать только try...catch , но...

try {
    let x = 5;
    throw new Error();
} catch(e) {
    console.log(x);
		// Uncaught ReferenceError: x is not defined
}

В вашем первом примере общая обработка для двух источников ошибки — это преимущество. А если надо разделить обработку — никто не мешает написать два разных блока try…catch


Что же до ReferenceError — да, не вполне удобно, но способ обхода общеизвестен. Всего-то надо объявить переменную блоком выше.

То есть, все таки, лишний код можно писать, но только тогда, когда его ты сам благословил?

В каком смысле "благословил"?

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

Благословил - я имел ввиду, что когда другие пишут 'лишний' код, это очень плохо, нужно минусовать. Если я пишу - это правильно, я знаю как лучше.

Вот так, грубо говоря.

Повторюсь:


общая обработка для двух источников ошибки — это преимущество

Согласно моему опыту, гораздо чаще приходится обрабатывать ошибки совместно, а не раздельно. Потому и оптимизируется именно совместная обработка ошибок.

Чаще / реже, не значит, что нужно использовать подход обозначенный в статье, так же как и не значит, что не нужно использовать общий подход. Это просто еще один способ (по моему мнению более прогрессивный), не более того. Ну и "субъективно" для меня, читаемость такого кода лучше

Да, но тут ты на уровне интерфейса (названия событий) понимаешь какие могут быть ошибки.

Нет. Я понимаю, какие могут быть события. А все ошибки — это все так же error. Вы можете сказать, какие ошибки могут там быть?


Да, можно использовать только try...catch, но...

… а что вам мешает использовать больше одного try-catch?

читай мой коммент выше

Не отвечает на мои вопросы.

Разная специфика ошибок, требующая по-разному реагировать.

Кажется, что я столкнулся с 'религиозной' догмой...

На религиозную догму больше похож как раз ваш вариант.

Серьезно? Я говорю, что можно применять разные подходы в зависимости от ситуации. Я не ограничиваюсь лишь своим подходом, меня не пугает ни try...catch, не onError, ни Error-First-Callback. Пока мне не слили рейтинг с кармой я не минусовал людей с другой точкой зрения, а общался. Но в тоже время, меня решили заминусовать. Так где же у меня догма?

Разная специфика ошибок, требующая по-разному реагировать.

Так как раз это прекрасно покрывается разными try-catch и дифференцированной обработкой ошибок в них. В чем проблема?

Как я писал выше, способ описанный в статье, позволяет обрабатывать ошибки по мере их поступления (возможного). Что касается catch, действительно, он ловит все ошибки и мы спокойно их все разгребем. Проблема лишь в том, что мы все это делаем в одной куче.

Как я писал выше, способ описанный в статье, позволяет обрабатывать ошибки по мере их поступления (возможного).

А я думал, он их обрабатывает после возврата из метода?...


Проблема лишь в том, что мы все это делаем в одной куче.

Ну так не делайте в одной куче, делайте столько try-catch, сколько вам надо.

Ну вот мое мнение, как раз в том и состоит, что по отдельности обрабатывать ошибки удобнее (по моему мнению, два try-catch в одном блоке уже перебор ) способом из статьи, если нужно все вместе - catch

по моему мнению, два try-catch в одном блоке уже перебор

Но почему?


А еще понимаете ли в чем дело, вызываемый код не знает, как его будут использовать. И как ему возвращать ошибки?..

Естественно, что не знает. Точно также, как и код возвращающий объект типа.

type Result = {
    status: boolean;
    message: string;
}

Так и как же вы предлагаете вызываемому коду возвращать ошибки, учитывая, что у вас две разных парадгмы в зависимости от того, как код вызывается?

Кстати, если попытаться отвлечься от войны, можно увидеть, что наш разговор превратился в спор похожий на ситуацию "табуляция против пробела"... =)

И вот я вам по личному опыту могу сказать, что проект в котором в одном файле табы, а в другом — пробелы, намного хуже того, где только табы, или только пробелы (и не важно, какой стиль мне больше нравится).

Как жаль, что я уже не могу поставить вам плюс.

@lair, друг, извини, что я так сильно тебя обидел, и что из-за этого тебе пришлось лепить минусы на все мои сообщения.

Предположу, что лепит не он

В заголовке указан JavaScript, а в примерах TypeScript. Наверное, не стоит подразумевать, что все JavaScript разработчики пишут на последнем. Тем более, что эту статью могут найти через много лет, когда TS может быть уже не таким популярным, и это введёт в заблуждение.

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

А через много лет кто-нибудь напишет новую статью.

Вероятно также думали разработчики, которые писали на jQuery в своё время (не смотря на всю разницу между jQuery и TS). Предлагаю все таки придерживаться каких-то правил и указывать TS, если речь идёт о TS, и JS, если речь идёт о чистом JS. Иначе многие начинающие разработчики могут не понять неожиданный синтакс.

Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.