コールバック 【プログラミング発展】

コールバックとは、関数の引数として関数を受け取り、関数実行中にその受け取った関数を実行するような挙動を取るものです。

関数の実引数に関数をつけると、関数の中身を実行中に引数に入れた関数が実行される、つまり一旦関数から戻って(back)呼び出され(call)ているように見えます。

コールバックを行う関数と、コールバックされる関数(コールバック関数)は切り離されています。つまり、一般的にコールバック関数は、コールバックを行う関数とは関係なく好きな関数を呼び出し側が設定できます。 (引数は合わせる必要があります。)

コードはJavascriptですが、原理はどの言語でも同じです。

実装例

しばしば、仮引数に代入された関数を呼び出すようなものになります。

function f(v, callback) {
    callback(v);
}

function callback(arg) {
    console.log(arg);
}

f(50, callback);
50

これを見ると、関数の引数に関数を入れていることがわかります。

挙動としては、まずfを対応する実引数に関数を入れて呼び出し、fが引数に入ってきたcallbackという名前の関数を呼び出しています。このcallbackという名前の関数は、実引数に入れたものです。

関数の実行途中に関数を呼び出すわけですが、その呼出中は関数の外 (fの実行中だが、途中でcallbackが実行される) で実行されていることがわかります。

このパターンでは、しばしば無名関数を直接代入します。

function f(v, callback) {
    // ...
    callback(v);
    // ...
}

f("Hello, Callback World!!", (arg) => {
    console.log(arg);
});
Hello, Callback World!!
callback説明

もちろん、コールバック関数の実行が終わったら、元の関数の続きが実行されます。

使われているところ

例えば、配列の操作や走査に関する関数です。

let a = [1, 4, 10, 2, 9];

let b = a.filter(v => {
    console.log(v);
    return v > 5
});

console.log(b);
1
4
10
2
9
[ 10, 9 ]

ここでは、v => v > 5がコールバック関数に当たります。filterの内部がどうなっているかは分かりませんが、1つ1つの要素をチェックするたびにv => v > 5が実行されています。

これ以外にも、Javascriptの配列の章で紹介している多くのメソッドはコールバックしています。

また、ブラウザでJavascriptを動かすとき、しばしばaddEventListenerしますが、そのときにもコールバックは使います。

dom.addEventListener("click", (event) => { // 実引数に入れている関数がコールバック関数
    // ...
});

コールバックのスコープ

コールバック関数のスコープは、スコープはコールバックされる側にあります。

let x = 500;
function f(cf) {
    cf();
}

(() => {
    let x = 10;
    f(() => {
        console.log(x);
    });
})();
10

この例をみると、fのスコープにあるxではなく、即時関数内にある (つまり実引数に入れた関数の) スコープにあるxが利用されていることが分かります。

そのため、コールバックする側の値を使いたい場合、コールバック関数の実引数に入れる必要があります。

function f(cf) {
    let y = 50;
    cf(y);
}

(() => {
    let x = 10;
    f((arg) => {
        console.log(x + arg);
    });
})();
60

thisを利用する場合、アロー関数とfunctionではthisの扱いが異なることに非常に注意する必要があります。

クラスを扱っていると、謎のエラーにものすごくハマるポイントです。

使い所

コールバックはむやみに乱発するものではありません。そうするとコードの在り処と処理の責任がバラバラになり、大変なことになります。

使うべき部分は、処理の一部を別の場所に権限ごと委譲したい、というような場合などです。

例えば、配列の処理を見てみます。

function arrayMap(ary, callback) {
    const newAry = [];
    for (const index in ary) {
        newAry.push(callback(index, ary[index]));
    }
    return newAry;
}

これは、すべての配列の値に対して何かしらの処理を行い、処理したあとの配列を返すものです。

責任の在り処を見てみましょう。arrayMap関数は、引数に入ってきたすべての配列に対し、何かしらの処理を行い、処理後の値を順番通りに集めた配列を返す、という責任を持っています。

しかし、なんの処理をするかは、場面や用途で大きく変わるため、予測できません。そこで、そのなんの処理をするかの権限を呼び出し側に委譲し、柔軟に使えるようにしているということです。

基本的に権限は狭めるべき (そうするとコードの見晴らしが良くなる) なので、必要が出たときのみ使いましょう。

まとめ

コールバックは、まず仮引数に関数をとり、関数内部でその関数を呼び出します。実引数には、呼んでほしい関数を入れます。

コールバックする際のスコープには注意する必要があります。

役に立ったらシェアしよう!