web-dev-qa-db-ja.com

なぜ `.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))の何が問題になっていますか?
  • これを行うコードをたくさん見ましたが、なぜですか?
  • 代わりに何をすべきですか?
58

古いコードがこれを行うのはなぜですか?

歴史的に、古い(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's clear that this function is 'swallowing' the error.
  }
}

エラーが発生した場合、消費者がこの関数がundefinedを返すとは思わないでしょう。

要約すると、今日のアプリケーションフローで開発者を驚かせ、ブラウザが未確認のプロミスエラーをログに記録するため、眉をひそめています。

代わりに行うこと:

Nothing。それがその美しさです-あなたが書いた場合:

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(または適切なロギングインストルメンテーション)globallyを使用できます。

// or throw to stop on errors
process.on('unhandledRejection', e => console.error(e));
53

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()がエラー自体をキャッチしてそれをログに記録し、賢明なフォールバック結果を提供するなど、他に何も処理しない場合、「すみません」ではなくundefinedがページに表示されます。

これを行うコードをたくさん見ましたが、なぜですか?

歴史的に、人々は約束エラーがどこでも処理されないことを恐れていました。そこで、すべての関数に.catch(console.error)を追加して、コンソールでエラーに気付くようにしました。

現代のプロミスの実装はすべて、未処理のプロミスの拒否を検出でき、コンソールで警告を発するため、これはもはや必要ありません。

もちろん、(さらに約束を返さない場合)約束連鎖のエラーをキャッチするために、最後にをエラーをキャッチする必要があります(または、少なくとも良い習慣です。

代わりに何をすべきですか?

呼び出し元に対してreturn a promiseである関数では、エラーをログに記録せず、それを実行してエラーを飲み込みます。約束を返すだけで、呼び出し側が拒否をキャッチし、エラーを適切に処理することができます(ログ記録などによって)。

これにより、コードも大幅に簡素化されます。

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;
  }
}
13
Bergi

絶対に必要でない限り(絶対に)catchエラーをすべきではない理由は、

約束の拒否を飲み込むこととは別に、キャッチハンドラーは、対応する成功ハンドラーによって実行される後続のコードで発生するJSエラーも飲み込みます。

影響

  1. エラーがcatchハンドラーによってキャッチされると、完了したと見なされ、処理されます。 promiseチェーン内の連続するすべてのpromiseサブスクライバーは、失敗ハンドラーまたはキャッチハンドラーの代わりに成功ハンドラーを呼び出します。これは奇妙な振る舞いにつながります。これは意図したコードフローではありません。

  2. サービスメソッド(getStuff)などの下位レベルの関数がcatchのエラーを処理する場合、Separation of Concerns。サービスハンドラーの責任は、データの取得のみです。そのデータ呼び出しが失敗すると、そのサービスハンドラーを呼び出しているアプリケーションがエラーを管理する必要があります。

  3. ある関数でエラーをキャッチすると、別の関数でキャッチされるため、あらゆる場所で奇妙な動作が発生し、バグの根本原因を追跡することが非常に難しくなります。このようなバグを追跡するには、Chrome devコンソールでBreak on Caught Exceptionsを有効にする必要があります。これはcatchごとに中断し、デバッグに数時間かかる可能性があります。

約束の拒否を処理することは常に良い習慣ですが、failureハンドラーを使用してcatchハンドラーを使用することを常に行う必要があります。障害ハンドラーはPromise rejectionsのみをキャッチし、JSエラーが発生した場合にアプリケーションを中断させます。

3
yeshashah

エラーは非常に一般的であり、すべてをキャッチしますが、操作が失敗するのは非常に多くのことだけであり、エラーはすべてですerrorSomethingSpecificは粒度を与えます

2
mmx

ここで最も一般的なステートメントは、javascript以外の言語に適用されますが、 'catch'ではありませんエラーを「handle」にする予定がない限り、エラーになります。ロギングはnot処理です。

すなわち、一般的に、キャッチの最善の(唯一の?)理由は、constructiveさらに問題なくコードを実行できるようにする方法。繰り返しになりますが、一連のロギングではおそらくそれを達成できません...

0
spechter