【JavaScript】コールバック関数について理解を深める(高階関数・非同期処理・コールバック地獄)

JavaScript

JavaScriptのコールバック関数とは

JavaScriptでは、関数の引数に変数などの値を指定することがありますが、実は関数の引数に関数を指定することもできます。

関数の引数に指定される関数のことをコールバック関数と呼び、JavaScriptでは非同期処理を実装するときによく使われます。

簡単なサンプルコードを見てみましょう。

function greet(name) {
  console.log(`こんにちは、${name}さん。`);
}

function getName() {
  return '太郎';
}

greet(getName()); // こんにちは、太郎さん。

greet()関数の引数に対してgetName()関数を渡しています。

getName()関数では、文字列の「太郎」をreturnしているため、greet()関数の引数にはreturnされた文字列の「太郎」が渡されます。

このように、コールバック関数では何かしらの値をreturnして、returnされた値を他の関数で利用することが多いです。

また、コールバック関数は「他の関数に呼び出してもらうための関数」などと呼ばれることもあります。

JavaScriptのコールバック関数と高階関数

JavaScriptでは、関数の引数に指定されるコールバック関数に対して、コールバック関数を引数に受け取る側の関数のことを高階関数と呼びます。

コールバック関数と高階関数は引き離せないもので、セットで覚えておく必要があります。

先ほどのサンプルコードを見てみましょう。

function greet(name) {
  console.log(`こんにちは、${name}さん。`);
}

function getName() {
  return '太郎';
}

greet(getName()); // こんにちは、太郎さん。

この場合、greet()関数が高階関数というわけです。

JavaScriptでコールバック関数を使うメリット

コールバック関数を使うことで、以下のようなメリットがあります。

  • 関数を使い回すことができるため、コードの汎用性が高まる。
  • 不具合発生時や仕様変更の際、修正するべきコードが少なくて済む。
  • 非同期の処理を記述することができる。

①関数を使い回すことができるため、コードの汎用性が高まる

コールバック関数を利用することで、同じようなコードを一つにまとめることができるようになるため、1つの関数の使い回しができるようになりコードの汎用性が高まります。

コードの汎用性が高まることで、コーディング時間の短縮ができたり、可読性の高いコードを記述できたりしますが、コールバック関数を使いすぎると後に解説するコールバック地獄に落ちる危険もあるため注意が必要です。

②不具合発生時や仕様変更の際、修正するべきコードが少なくて済む。

もし何かしらの不具合があった場合や突然の仕様変更があったとき、コールバック関数を利用しコードが簡潔であれば修正するべき箇所が少なく済むというメリットもあります。

コードは一度書いて終わりではなく、作った後に運用され続けるものであるため、その点も考慮したコーディングはとても大事です。

コールバック関数を利用することで保守性やメンテナンス性が向上し、より大規模な開発を行う際や複雑な処理を実装する際に大きな効果を発揮します。

③非同期の処理を記述することができる。

特にJavaScriptの非同期処理において、コールバック関数が使われることが多いです。

Promiseやasync/awaitを利用して非同期処理を行うケースもありますが、簡単な処理やsetTimeout()メソッドなどではコールバック関数が活用されています。

コールバック地獄について

コールバック関数を利用することで、複雑な処理を簡潔なコードで記述することができます。

ただし、便利だからといってコールバック関数を多用して良いというわけではなく、むやみにコールバック関数を乱用してしまうとコールバック地獄と呼ばれる状態に陥ってしまいます。

試しに、1秒ごとに整数をコンソールに表示するコードを見ていきましょう。

console.log(0);
setTimeout(() => {
  console.log(1);
  setTimeout(() => {
    console.log(2);
    setTimeout(() => {
      console.log(3);
      setTimeout(() => {
        console.log(4);
      }, 1000)
    }, 1000)
  }, 1000)
}, 1000)

数が多くなればなるほどコールバック関数のネストが深くなり、処理の流れを追いにくくなっていくのがわかるかと思います。これをコールバック地獄と呼びます。

コールバック地獄に陥ってしまうと、コールバック関数の散乱により、逆に可読性の低いコードになってしまうことがあり、保守性やメンテナンス性が著しく低下してしまうため注意が必要です。

JavaScriptのコールバック関数と非同期処理

JavaScriptでは、非同期処理を行う際によくコールバック関数が利用されます。

プログラムは通常上から順番に処理されていく(同期処理)のが一般的ですが、JavaScriptはブラウザで動作する言語であるため、ユーザーのアクション(クリックなど)をトリガーとして処理を実行するというケースに対応する必要があります。

そこで利用されるのが非同期処理で、上から順番に処理するのではなく、何かしらの合図を待ってから処理を実行します。そこでコールバック関数の出番というわけです。

// コールバック関数を定義
function greet(){
  console.log('こんにちは');
}

// setTimeout()メソッドを利用し、2秒後にコールバック関数greet()を実行
setTimeout(greet(), 2000);

// addEventListener()で、クリック時にコールバック関数greet()を実行するようにする
const target = querySelector('#target');
target.addEventListener('click', greet());

その他、JavaScriptのmap()メソッドやfilter()メソッド、forEach()メソッドでも引数にコールバック関数を受け取ります。

const numbers = [1, 2, 3];

const mapedNumbers = numbers.map((number) => { return number * 2 });
console.log(mapedNumbers); // [2, 4, 6]

const filteredNumbers = numbers.filter((number) => { return number % 2 != 0 });
console.log(filteredNumbers); // [1, 3]

numbers.forEach((number) => { console.log(number) });
// 1
// 2
// 3

コメント

  1. 通りすがり より:

    return number % 2 = 0 と、
    number.forEach((number) => { console.log(number) });
    は意味不明です。

    number % 2 != 0
    numbers.forEach・・・
    ではないですか?

タイトルとURLをコピーしました