Почему '.catch(err => console.error(err))' обескуражен?
Я использую обещания и имею код, который выглядит следующим образом:
function getStuff() {
return fetchStuff().then(stuff =>
process(stuff)
).catch(err => {
console.error(err);
});
}
Или же:
async function getStuff() {
try {
const stuff = await fetchStuff();
return process(stuff);
} catch (err) {
console.error(err);
}
}
Я делал это, чтобы избежать ошибок при ошибках, но один пользователь сказал, что я не должен этого делать, и это неодобрительно.
- Что не так с
return ….catch(err => console.error(err))
? - Я видел много кода, который делает это, почему?
- Что мне делать вместо этого?
Ответы
Ответ 1
Почему старый код делает это?
Исторически сложилось так, что более старые (до 2013 года) обещают библиотекам "проглоченные" необработанные обещания, которые вы не обрабатывали самостоятельно. С тех пор это не так.
Что происходит сегодня?
Браузеры и Node.js уже автоматически регистрируют отказы обезвреженных обещаний или имеют поведение для их обработки и автоматически регистрируют их.
Более того - добавлением .catch
вы сигнализируете метод, вызывающий возвращаемую функцию undefined
:
// undefined if there was an error
getStuff().then(stuff => console.log(stuff));
Вопрос, который нужно задать самому себе при написании асинхронного кода, как правило, "что бы сделала синхронная версия кода?":
function calculate() {
try {
const stuff = generateStuff();
return process(stuff);
} catch (err) {
console.error(err);
// now it clear that this function is 'swallowing' the error.
}
}
Я не думаю, что потребитель ожидал бы, что эта функция вернется undefined
если возникнет ошибка.
Таким образом, чтобы подвести итог, он нахмурился, потому что он неожиданно удивляет разработчиков в потоке приложений и браузерах.
Что делать:
Ничего такого. Что красота его - если вы написали:
async function getStuff() {
const stuff = await fetchStuff();
return process(stuff);
}
// or without async/await
const getStuff = fetchStuff().then(process);
Во-первых, вы все равно получите всевозможные ошибки :)
Что делать, если я запускаю старую версию Node.js?
Старые версии Node.js могут не регистрировать ошибки или показывать предупреждение об устаревании. В этих версиях вы можете использовать console.error
(или надлежащее протоколирование) глобально:
// or throw to stop on errors
process.on('unhandledRejection', e => console.error(e));
Ответ 2
Что не так с return ….catch(err => console.error(err))
?
Он возвращает обещание, которое будет выполнено с undefined
после того, как вы обработали ошибку.
Собственно, ловушка ошибок и протоколирование их хорошо в конце цепи обещаний:
function main() {
const element = document.getElementById("output");
getStuff().then(result => {
element.textContent = result;
}, error => {
element.textContent = "Sorry";
element.classList.add("error");
console.error(error);
});
element.textContent = "Fetching…";
}
Однако, если getStuff()
делает getStuff()
самой ошибки, чтобы регистрировать ее и не делать ничего, чтобы обрабатывать ее, как предоставление разумного результата возврата, это приводит к undefined
показу на странице вместо "Извините".
Я видел много кода, который делает это, почему?
Исторически сложилось так, что люди боялись, что обетования не будут устранены нигде, что приведет к их исчезновению вообще - "проглотил" обещание. Поэтому они добавили .catch(console.error)
в каждую функцию, чтобы убедиться, что они заметят ошибки в консоли.
Это больше не требуется, поскольку все современные обещания могут обнаружить необработанные обещания и будут предупреждать об опасности на консоли.
Конечно, по-прежнему необходимо (или, по крайней мере, хорошая практика, даже если вы не ожидаете, что что-то не получится), чтобы поймать ошибки в конце цепочки обещаний (когда вы еще не вернете обещание).
Что мне делать вместо этого?
В функциях, которые return
обещание их вызывающему абоненту, не регистрируйте ошибки и не проглатывайте их, делая это. Просто верните обещание, чтобы вызывающий мог поймать отклонение и обработать ошибку соответствующим образом (путем регистрации или чего-то еще).
Это также упрощает код:
function getStuff() {
return fetchStuff().then(stuff => process(stuff));
}
async function getStuff() {
const stuff = await fetchStuff();
return process(stuff);
}
Если вы настаиваете на том, чтобы что-то делать с причиной отклонения (ведение журнала, изменение информации), обязательно повторите ошибку:
function getStuff() {
return fetchStuff().then(stuff =>
process(stuff)
).catch(error => {
stuffDetails.log(error);
throw new Error("something happened, see detail log");
});
}
async function getStuff() {
try {
const stuff = await fetchStuff();
return process(stuff);
} catch(error) {
stuffDetails.log(error);
throw new Error("something happened, see detail log");
}
}
То же самое, если вы обрабатываете некоторые из ошибок:
function getStuff() {
return fetchStuff().then(stuff =>
process(stuff)
).catch(error => {
if (expected(error))
return defaultStuff;
else
throw error;
});
}
async function getStuff() {
try {
const stuff = await fetchStuff();
return process(stuff);
} catch(error) {
if (expected(error))
return defaultStuff;
else
throw error;
}
}
Ответ 3
Причина, по которой вы не должны catch
ошибки, если не требуется абсолютно (что никогда), заключается в том, что
Помимо проглатывания обещаний, уловщик также проглатывает любые ошибки JS, которые происходят в любом последующем коде, выполняемом соответствующим обработчиком успеха.
Последствия
-
Как только ошибка catch
обработчиком catch
, она считается выполненной и обработанной. Все последовательные подписчики обещаний в цепочке обещаний будут называть их обработчиками успеха вместо отказов или обработчиков catch. Это приводит к странным поведением. Это никогда не предназначенный поток кода.
-
Если функция на более низком уровне, такая как метод обслуживания (getStuff
), обрабатывает ошибки в catch
, это нарушает принцип разделения проблем. Ответственность обработчика службы должна быть исключительно для сбора данных. Когда этот вызов данных выходит из строя, приложение, которое вызывает этот обработчик, должно управлять ошибкой.
-
Ловушка ошибки в некоторой функции, которая попадает другой, приводит к странным поведением вокруг и делает очень трудным отслеживать основные причины ошибок. Чтобы отслеживать такие ошибки, мы должны включить Break on Caught Exceptions
в консоли Chrome dev, которая будет ломаться на каждом catch
и может занять несколько часов после отладки.
Это всегда хорошая практика, чтобы справиться обещание отбраковки, но мы всегда должны сделать это с помощью failure
обработчика над catch
обработчик. Обработчик сбоев будет только улавливать Promise rejections
и разрешает приложению прерываться, если возникает какая-либо ошибка JS, как это должно быть.
Ответ 4
ошибка слишком универсальна, это уловка, но есть только так много вещей, с которыми операция завершится неудачно, ошибка - это все errorSomethingSpecific дает детализацию
Ответ 5
Самое общее утверждение здесь, которое применяется в языках за пределами javascript, - это не " ловить " ошибку, если вы не планируете " обрабатывать " эту ошибку. Ведение журнала не обрабатывается.
т.е. в целом, лучшая (только?) причина для улова - это обрабатывать/"обрабатывать" ошибку конструктивным способом, позволяющим выполнять код без каких-либо дополнительных проблем. И опять же, строка ведения журнала, вероятно, никогда не достигает этого...