【もりけん塾】JavaScript課題10 -ローディング実装 ④ try,catch,finally –

JavaScript

今日は、もりけん塾のJavaScript課題10 を終えて学んだことをアウトプットしていきます。

こんにちは。Webコーダーのはるです。

現在、所属している「もりけん塾」でハンズオン課題 に取り組み、レビューをいただいています。

今日は、課題10 についてアウトプットをしていきます。
(前回までのアウトプットは、こちらです。)

●課題10で学んだこと

・try,catch,finallyについて
・適切な関数名について

【もりけん塾】JavaScript課題10で学んだこと まとめ

今回、取り組んだ課題はこちらです。

(もりけん先生のJavaScriptハンズオン課題より)

課題9ではPromiseのasync/awaitを使用したloadingの実装をしました。

同じコードにtry,catch,finallyを追加していきます。

try,catch,finallyについて

まず、MDNでtry,catch,finallyについて調べました。
構文は以下の通りです。

 

try {
    //例外が起こりうる処理
}

catch {
    //例外が発生した際の処理
}

finally {
    //例外の有無に関係なく最後に行われる処理
}
try,catch文を書くことで、エラー(例外)が発生した時の対応を指示することができます。(エラー発生時に処理が止まらない)

 

最初にPRしたコードはこちらです。

const ul = document.getElementById("js-lists");

const attributes = [
    { to: "bookmark.html", img: "1.png", alt: "画像1", text: "ブックマーク" },
    { to: "message.html", img: "2.png", alt: "画像2", text: "メッセージ" }
];

function addLoading() {
    const li = document.createElement("li");
    const img = document.createElement("img");
    li.id = "js-loading";
    img.src = "loading-circle.gif";
    ul.appendChild(li).appendChild(img);
};

function removeLoading() {
    const li = document.getElementById("js-loading");
    ul.removeChild(li);
};

function fetchListData() {
    addLoading();
    return new Promise(resolve => {
        setTimeout(() => resolve(attributes), 3000);
    });
};

function handlingException() {
    try {
        fetchListData();
    } catch {
        const error = 'エラー:データが取得できません';
        console.error(error);
    } finally {
        removeLoading();
    }
};

handlingException();

async function addList() {
    const fragment = document.createDocumentFragment();
    const values = await fetchListData();
    removeLoading();
    values.forEach(value => {
        const li = document.createElement("li");
        const anchor = document.createElement("a");
        const img = document.createElement("img");

        anchor.textContent = value.text;
        anchor.href = `/${value.to}`;
        img.src = value.img;
        img.alt = value.alt;

        li.appendChild(anchor).insertAdjacentElement("afterbegin", img);
        fragment.appendChild(li);
    });
    ul.appendChild(fragment);
};
addList();

レビュー①:エラーがcatchできていない

handlingException()という関数を作成し、try,catch,finallyの処理を書いてみましたが、これではfetchListData()のエラーがキャッチできていません。
function handlingException() {
    try {
        fetchListData();
    } catch {
        const error = 'エラー:データが取得できません';
        console.error(error);
    } finally {
        removeLoading();
    }
};

handlingException();

もりけん先生(@terrace_tec)が、そのことをとてもわかりやすくレビューしてくださいました。

わかりやすいのは、
handlingExceptionの定義と実行をコメントアウトしてから、

function fetchListData() {
    addLoading();
    a // add 
    return new Promise(resolve => {
        setTimeout(() => resolve(attributes), 3000);
    });
};

このようにするとaを書くと当然aは未定義なのでエラーですが

Uncaught (in promise) ReferenceError: a is not defined

それをキャッチできていません。
前述のように実際はhandlingExceptionは何もしていないので
addList内のfetchListDataに対して施さなくてはいけないです

fetchListDataにエラーが起きた時、そのエラーを取得できるようにコードを考えました。

async function handlingException() {
  try {
    return await fetchListData();
  } catch(e) {
    console.error(e.message);
  } finally {
    removeLoading();
  }
};

async function addList() {
  const fragment = document.createDocumentFragment();
  const values = await handlingException(); (以下略)
tryでfetchListData()をawaitして→最後のaddList()でhandlingException()をawaitするコードに変更しました。
また、catchでエラーオブジェクトを取得して、messageプロパティを使ってそのエラー文を出力するようにしました。

 

先程のように、

function fetchListData() { 
 addLoading(); 
 a // add 
 return new Promise(resolve => { 
  setTimeout(() => resolve(attributes), 3000); 
 });
};

a を追加してみると、’a is not defined’というエラー文を出力することができました。きちんとcatchできています。

レビュー②:removeLoadingの重複

塾生のsenさん(websen5)にレビューをいただきました!

今回、新しくtry,catch,finally文の中でremoveLoading()関数を呼び出しました。


async function handlingException() {
  try {
    return await fetchListData();
  } catch(e) {
    console.error(e.message);
  } finally {
    removeLoading();
  }
};

finallyに書いた処理は、例外が発生しても発生しなくても最後に必ず行われるため、addList()関数内にあるremoveLoading();は不要であることに気付けました。

ありがとうございます!

レビュー③:関数名について

もりけん先生(@terrace_tec)にレビューをいただきました。

とても大切だと思ったので、先生がまとめてくださった部分を引用します。

-名前は実行順にコードを読んでいて「ん?」とならないようにする
-名前は仕事をする内容を書く。名前以外の仕事はしない
-実行する関数から得られる値の名前が不自然じゃないか気を配る
-抽象的な名前か具体的な名前にするべきかちょっと考えてみる

handlingException() = 除外をhandling する関数

なのに、その中でfetchListData()を実行しているのに違和感がある…

確かに…

function handlingException() {
    try {
        fetchListData();
    } catch {
        const error = 'エラー:データが取得できません';
        console.error(error);
    } finally {
        removeLoading();
    }
};

fetchListDataした結果で除外handlingする方が流れがわかりやすい。

さらに、fetchListData()はもっと抽象的な名前であるべき = データを返すだけの関数であるべき

ことからfetchData()という抽象的な名前かつ、addLoading()もtry,catchの書かれている呼び出し元に移動するというコードを先生に書いていただきました😭✨

function fetchData() { 
  return new Promise(resolve => { 
   setTimeout(() => resolve(attributes), 3000);
 });
};
async function fetchListData() {  
 addLoading();
 try { 
  return await fetchData(); 
 } catch(e) { 
  console.error(e.message);
 } finally { 
  removeLoading();
 }
};

async function addList() { 
  const fragment = document.createDocumentFragment();
  const values = await fetchListData(); 

関数名も自然な流れになり、関数の中も関数名に合った処理のみになりました。

勉強になりました。ありがとうございます!

//

学習に使用している本は、もりけん先生推奨の”JavaScript本格入門”です。

あとがき

お忙しい時間を割いて課題を確認してくださったもりけん先生(@terrace_tec)・塾生のsenさん(websen5)ありがとうございました!!

私の課題のリポジトリはこちらから確認できます↓

 

今日は以上です。

//

【もりけん塾で勉強しています】

もりけん先生(@terrace_tec)のHPはこちら