为啥不鼓励使用`.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-voidantipattern" 也许? @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,我会一直关注.catches。 (正如您在视频中所说,***/文档的重要性怎么强调都不过分!)也许还可以在控制台中添加一个“获取更多信息”链接,警告节点抛出未处理的承诺拒绝?指向正确方向的路标越多,更多开发人员找到它的机会就越大【参考方案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))`?的主要内容,如果未能解决你的问题,请参考以下文章

为啥不鼓励按钮导航?

为啥我不能在 Promise.catch 处理程序中抛出?

axios catch 不捕获错误

为啥在休眠中不鼓励复合键?

如何在不使用 try/catch(err) 的情况下处理返回“null”的 indexOf?

为啥不鼓励使用单例模式? [复制]