为啥不鼓励使用`.catch(err => console.error(err))`?
Posted
技术标签:
【中文标题】为啥不鼓励使用`.catch(err => console.error(err))`?【英文标题】:Why is `.catch(err => console.error(err))` discouraged?为什么不鼓励使用`.catch(err => console.error(err))`? 【发布时间】:2018-11-26 12:44:22 【问题描述】:我正在使用 Promise,并且代码如下所示:
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))
有什么问题?
我见过很多这样的代码,为什么?
我应该怎么做?
【问题讨论】:
"catch
-void
antipattern" 也许?
@David784 很抱歉! (那是我的错)。措辞是不幸的,自从我们在 7.7 中发布它以来,我一直想改变它。 Madara 在 v10 中使错误变得更好,我们将很快解决该问题 - 如果未处理的拒绝在 v11 和 v12 中默认收集垃圾并且弃用警告将消失,则将终止该过程。
稍微削弱这个问题,我会高度怀疑有人告诉你不要做某事,但又没有解释为什么会出现这种情况。如果该用户有反对使用它的有效论据,他们应该提及它。或者你应该先让他们澄清;因为我们无法知道确切的联系方式(或他们的意见)。
@Flater 注意我写了问题和答案,我这样做是因为当我们为 Node.js 峰会分析存储库和“野外”代码时,这种事情经常出现。事实上,我这样做正是为了让我们可以指出“为什么”。
【参考方案1】:
为什么旧代码会这样做?
从历史上看,较旧的(2013 年之前)承诺库会“吞下”您自己没有处理的未处理的承诺拒绝。从那以后写的任何东西都不是这种情况。
今天发生了什么?
浏览器和 Node.js 已经自动记录未捕获的 Promise 拒绝或具有处理它们的行为并将自动记录它们。
此外-通过添加.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's clear that this function is 'swallowing' the error.
如果发生错误,我认为消费者不会期望此函数返回 undefined
。
总而言之 - 它不受欢迎,因为它让应用程序流程中的开发人员感到惊讶,并且浏览器今天无论如何都会记录未捕获的 promise 错误。
该怎么做:
没什么。这就是它的美妙之处——如果你写的话:
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));
【讨论】:
当我记录这样的错误时(假设返回值未定义没有问题或者我正在重新抛出错误),我添加了文件名、行号等信息。或提供上下文的简单消息。如果我只是依赖“unhandledRejection”事件,则此信息不存在,并且很多时候这意味着不知道实际失败的操作(即没有上下文)。 @Itay 感谢您的评论。浏览器和 Node 会像异常一样记录错误 - 包括文件号、文件名等。请注意,重新抛出(即 catch(e) attachMetadata(e); throw e;
)以附加元数据而不是console.error
是完全可以的。这能解决您的问题吗?
@Itay 请注意,这在最近版本的浏览器和 Node.js 中发生了变化,而且如您所知 - 我们(Node 和 V8)将为每个人提供用于生产的异步堆栈跟踪。跨度>
process.on('unhandledRejection', ...)
...从来没有想过要寻找它的存在。谢谢!!!!
@BenjaminGruenbaum 似乎主要障碍只是知道它的存在。如果我今天没有看到你的 Q/A,我会一直关注.catch
es。 (正如您在视频中所说,***/文档的重要性怎么强调都不过分!)也许还可以在控制台中添加一个“获取更多信息”链接,警告节点抛出未处理的承诺拒绝?指向正确方向的路标越多,更多开发人员找到它的机会就越大【参考方案2】:
return ….catch(err => console.error(err))
有什么问题?
它返回一个承诺,在您处理错误后将通过undefined
实现。
就其本身而言,在 Promise 链的末尾捕获错误并记录它们是很好的:
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()
确实捕获了错误本身并记录它并且不采取任何其他措施来处理它(例如提供合理的回退结果),它会导致undefined
出现在页面中,而不是“对不起 em>”。
我见过很多这样的代码,为什么?
从历史上看,人们害怕承诺错误没有得到处理,导致它们完全消失——被承诺“吞噬”。所以他们在每个函数中添加了.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;
【讨论】:
是否可以使用 catch 从错误状态中恢复承诺,即返回由回退值实现的承诺? @Bergi 捕捉错误是一个很好的做法,但我们永远无法确定承诺链何时结束。未来,总是可以在当前链上延伸。捕捉错误而不仅仅是 Promise 拒绝对我来说似乎是一种过度杀戮。它总是导致应用程序中出现奇怪的流程。 @bigless 是的,只要promise.catch(error => if (expected(error)) return fallback; else throw error; )
@yeshashah 不确定“一个人总是可以在当前链上扩展”是什么意思。看看我回答中的 main
示例:这绝对是承诺链的结尾,main
不会返回任何内容。
@Bergi IMO,您在此处的评论应该是您答案的一部分,因为该模式很常见并且不提及它可能意味着您不应该捕捉到您期望/可以处理的错误/可以从中恢复,同时通过throw
为您实际上处理的那些错误生成拒绝。【参考方案3】:
你不应该catch
错误,除非绝对需要(这是永远不会)的原因是
除了吞下 Promise 拒绝之外,catch 处理程序还吞下任何 JS 错误 发生在相应成功处理程序运行的任何连续代码中。
含义
一旦错误被catch
处理程序捕获,它就会被视为已完成并已处理。 Promise 链中的所有后续 Promise 订阅者都将调用其成功处理程序,而不是失败或捕获处理程序。这会导致奇怪的行为。这绝不是预期的代码流。
如果像服务方法(getStuff
)这样的底层函数处理catch
中的错误,则违反了关注点分离的原则。服务处理程序的职责应该仅仅是获取数据。当该数据调用失败时,调用该服务处理程序的应用程序应管理该错误。
某个函数中的错误被另一个函数捕获,导致周围出现奇怪的行为,并且很难跟踪错误的根本原因。要跟踪此类错误,我们必须在 Chrome 开发控制台中启用 Break on Caught Exceptions
,这将在每个 catch
上中断,并且可能需要数小时才能完成调试。
处理承诺拒绝总是一个好习惯,但我们应该始终使用failure
处理程序而不是catch
处理程序。失败处理程序只会捕获Promise rejections
并让应用程序中断,如果发生任何 JS 错误,它应该是这样的。
【讨论】:
这和 try/catch 处理程序完全一样 :) 什么是故障处理程序?我在mdn中没有看到。就在那时,抓住,最后【参考方案4】:错误太笼统了,它是一个包罗万象的东西,但只有这么多事情会导致操作失败,错误就是一切 errorSomethingSpecific 给出了粒度
【讨论】:
【参考方案5】:此处最通用的声明适用于 javascript 以外的语言,除非您计划“”,否则不要“捕获”错误处理'错误。日志记录不是处理。
即通常,捕获的最佳(唯一?)原因是以 建设性 方式处理/“处理”错误,允许代码在没有任何进一步的问题。再说一次,一行日志记录可能永远无法做到这一点......
【讨论】:
以上是关于为啥不鼓励使用`.catch(err => console.error(err))`?的主要内容,如果未能解决你的问题,请参考以下文章