フリーランスのためのネットビジネス専門学校 ネットで独立開業を目指す人を応援
フリーランスのためのネットビジネス専門学校 ネットで独立開業を目指す人を応援

ドットインストールのHTML入門の動画を見て分かったこと(箇条書き)

最初に

この記事では、JavaScriptのエラーに関する知識やベストプラクティスについて記述しています。

文字列を直接throwしない

throwステートメントを使用してエラーを発生させる際、次のように文字列を直接throwするべきではありません。

throw 'やばいエラー💩';

上記のように文字列をthrowした場合、スタックトレースが取得できないために、問題の発生箇所を特定することができません。
エラーをthrowする場合は、Errorオブジェクトを使用するのが正解です。

throw new Error('やばいエラー💩');

なお、文字列のthrowESLintのルール(no-throw-literal)で禁止させることができるので、ESLintを使用することでこのようなミスを防ぐことが可能です。

非同期のErrorはcatchできない

setTimeoutのように非同期で発生したErrorは、次のコードで示すようにtry-catchステートメントでcatchすることができません。

function throwError() {
  throw new Error('やばいエラー💩');
}

function run() {
  try {
    // 通常通りthrowErrorを実行
    throwError();

  } catch (error) {
    // throwErrorのErrorをcatchできる
    console.error(error);
  }
}

function runAsync() {
  try {
    // setTimeoutで1秒後にthrowErrorを実行
    setTimeout(throwError(), 1000);

  } catch (error) {
    // 非同期に実行すると、throwErrorのErrorをcatchできない
    console.error(error);
  }
}

ちなみに、setTimeout()を使用する場合、Promiseを使用したラッパーを用意することで、Errorをキャッチすることができます。

// setTimeoutのラッパー
function delay(ms) {
  return new Promise(resolve => setTimeout(resolve, ms));
}

function runAsync() {
  delay(1000)
    .then(throwError)
    .catch(error => {
      // Errorをcatchできる
      console.error(error);
    });
}

thenの外側でErrorオブジェクトを生成する

まずは、以下のコードを見てみましょう。

function fetchUser() {
  const err = new Error();
  fetch('https://something.com/user')
    .then(response => {
      if (!response.ok) {
        throw new Error('エラーだよ');
      }
    })
}

function funcA() {
  fetchUser();
}

function funcB() {
  funcA();
}

funcB();

このコードでは、fetchUser()の中でfetch()を使用してユーザデータを取得しています。また、fetch().then()の中では、レスポンスがOKではなかった場合にErrorをthrowしています。この処理自体は決して間違いではありませんが、問題はここで作られたErrorオブジェクトにfetchUser()を呼び出すまでのスタックが生成されないという点にあります。fetchUser()が必ずfuncA()から呼び出されるのであればいいですが、funcY()funcX()からも呼び出されるとした場合、どのような順序でfetchUser()が呼び出されたのかという追跡が困難になってしまいます。

image.png
※ thenの中でErrorを生成した場合のスタック

そこで、コードを以下のようにします。

function fetchUser() {
  // fetch()をコールする前にErrorオブジェクトを用意する
  // これによって、fetchUser()を呼び出すまでのstackが生成される
  const err = new Error();

  fetch('https://something.com/user')
    .then(response => {
      if (!response.ok) {
        err.message = 'エラーだよ';
        throw err;
      }
    })
}

function funcA() {
  fetchUser();
}

function funcB() {
  funcA();
}

funcB();

上記のようにfetch()を呼び出す前にErrorオブジェクトを生成しておくことで、fetchUser()を呼び出すまでのスタックがErrorに生成されます。

image.png
※ thenの外側でErrorを生成した場合のスタック

このfetchのように、非同期処理を伴う場合はErrorオブジェクトを事前に生成しておくことで、エラー発生までの経緯を明確にすることができます。

awaitにはtry-catchを使用する

awaitを使用して非同期処理を実行した場合、呼び出した処理がrejectされるとそれ以降の処理は実行されません。

function throwErrorFromPromise() {
  return new Promise(resolve => {
    throw new Error('やばいエラー💩');
  });
}

async function run() {
  await throwErrorFromPromise();

  // 次のコードは実行されない
  console.log('😟');
}

run();

これは、awaitによって呼び出された処理がrejectされた場合、rejectされた値をthrowしてそれ以降の処理を実行しないために起こります。
そのため、awaitで呼び出した処理がrejectされても処理を継続させる場合は、try-catchでエラーハンドリング処理を記述しておく必要があります。

async function run() {
  try {
    await throwErrorFromPromise();
  } catch(error) {
    // エラーハンドリング処理
  }

  // 次のコードは実行される
  console.log('😀');
}

その他

以下の記事では、捕捉されなかったJavaScriptエラーのデータ収集方法について述べています。こちらも一読することをお勧めします。
ユーザのブラウザで起きた JavaScript のエラーを収集する

参考

[紹介元] HTMLタグが付けられた新着投稿 – Qiita ドットインストールのHTML入門の動画を見て分かったこと(箇条書き)

コメント

記事に戻る

コメントを残す