はじめに
今回は、非同期処理プログラムを作成するときに欠かすことのできない「コールバック」について解説します。
<目次>
コールバック関数とは
コールバック関数とは、他の関数に引数として渡され、適切なタイミングで後で呼び出される関数のことです。
非同期処理やイベント処理でよく使われます。
「後で呼び出される」関数のためコールバック関数と呼ばれます。
コールバック関数の例
以下がコールバック関数を使った例です。 3つの関数で構成されています。
① の function executeCallback(callback) は 引数 callback でコールバック関数 myCallback の参照 を引数として受け取る関数です。
② の myCallback() は、① の引数となるコールバック関数です。
③ の executeCallback(myCallback) は ① の関数を呼び出して実行する関数です。
① の function executeCallback(callback) の「callback」がコールバック関数 myCallback の参照にあたります。
そのため、引数「callback」のコールバック関数の実態は ② の myCallback 関数です。
① ②ののコールバック関数を 引数として受け取る関数
function executeCallback(callback) { //cakkbacj は myCallback()関数の参照 console.log(“関数の中でコールバックを実行します…”); callback(); // ここで渡された関数を実行 } |
② ①の executeCallback に渡される コールバック関数
function myCallback() { console.log(“これはコールバック関数です!”); } |
③ ①の executeCallback() を 関数として実行
// `myCallback` を関数として渡す executeCallback(myCallback); // ここでは `myCallback` の関数本体を引数として渡している |
引数にコールバック関数を指定するとき、注意点があります。
function executeCallback(callback) と myCallback関数 の参照を指定するようにしましょう。
もし、executeCallback(myCallback()); と書いてしまうと…
- myCallback() が その場で実行 されてしまい、executeCallback(callback) には関数の結果(undefined など)が渡ってしまいます。
- そのため、コールバック関数を渡すときは 関数名だけ(括弧なし) を渡すのが重要です。
先ほど「コールバック関数とは、他の関数に引数として渡され、適切なタイミングで後で呼び出される関数のこと」と言いましたが、上記のサンプルをこの定義に当てはめるとどうなるでしょうか。
「適切なタイミング」とは「executeCallback関数 の中 でmyCallback関数 を呼び出したとき」を指します。
「後で呼び出される」とは「executeCallback 関数 の実行が開始された後で myCallback() が実行されること」を指します。
教科書に載っているこのようなサンプルを見ても、私には、コールバック関数の有難みが、ほとんど伝わってきませんでした。
「へー、そうなんだ。」くらいの受け止め方でした。
そこで、次に、コールバック関数が実際にどのようなケースで使用されているのかを見ていきます。
非同期処理におけるコールバック関数の実装例
実装におけるコールバック関数は、特に イベントリスナー や 非同期処理 の場面で頻繁に使われます。
実装を紹介した方が、コールバック関数の有難みがよくわかると思いますので、いくつか紹介します。
例1:イベントリスナー
// ボタン要素を取得 const button = document.getElementById(“myButton”); // クリックイベントにリスナーを設定 button.addEventListener(“click”, function() { console.log(“ボタンがクリックされました!”); }); |
function() { console.log(“ボタンがクリックされました!”); } がコールバック関数です。
この記法は 無名関数(匿名関数)といい、その場で関数を定義し、その関数の参照を addEventListener に渡しています。
無名関数の記法でコールバック関数をしているので、addEventListener が クリックイベント発生時にこの関数を実行するため、問題なく動作します。
button.addEventListener(“click”, handleClick()); と書くと、すぐに実行されてしまう ため誤りです。
このコードでは、
- addEventListener(“click”, function() {…}) によって、ボタンの クリックイベント を監視。
- クリックが検知されると、イベントリスナー(function内の処理)が呼び出される。
「適切なタイミング」とは「クリックが検知されたとき」を指します。
「後で呼び出される」とは「クリックイベントの監視の後でイベントリスナー(function内の処理) が実行されること」を指します。
つまり、addEventListener は すぐに関数を実行するのではなく、クリックイベントが起こるまで待機 し、クリックイベントが起こった時に、イベントリスナー(function内の処理)が実行されます。
例2:非同期I/O処理
// 非同期書き込みの関数 function writeFileAsync(filename, data, callback) { console.log(“ファイル書き込みリクエストを受け取りました…”); fs.writeFile(filename, data, “utf8”, (err) => { if (err) { console.log(“エラーが発生しました:”, err); callback(err); return; } console.log(“書き込み完了!”); callback(null); // 成功時のコールバック }); } |
// 書き込み完了後のコールバック関数 function onWriteComplete(err) { if (err) { console.log(“ファイルの書き込みに失敗しました。”); } else { console.log(“ファイルの書き込みが正常に完了しました!”); } } |
// 非同期処理を開始 writeFileAsync(“output.txt”, “こんにちは、非同期ファイル書き込み!”, onWriteComplete); |
コールバック関数の場所は次の2か所あります。
1か所目:fs.writeFile(filename, data, “utf8”, (err) => { … }) の 無名関数
fs.writeFile の非同期処理が完了した後に実行されます。
書き込みが成功したかどうかを判定し、callback(err) や callback(null) を呼び出します。
2か所目:onWriteComplete(err) の 名前付きコールバック関数
writeFileAsync() を呼び出す際に、コールバック関数として onWriteComplete を渡しています。
fs.writeFile の処理結果を受け取って、成功なら “ファイルの書き込みが正常に完了しました!”、 エラーなら “ファイルの書き込みに失敗しました。” のメッセージを表示します。
fs.writeFile(filename, data, “utf8”, (err) => { … }) の 無名関数の
「適切なタイミング」とは「書き込みが終了したとき」を指します。
「後で呼び出される」とは「fs.writeFile の処理(書き込み処理)が終わった後に無名関数の処理 が実行されること」を指します
onWriteComplete(err) の 名前付きコールバック関数の
「適切なタイミング」とは「無名関数から呼び出されたとき」を指します。
「後で呼び出される」とは「無名関数が呼び出され後に無名関数からonWriteComplete(err)が呼び出されて実行されること」を指します。
例3:ネットワーク関連(Web API)
fetch(‘../controller/AuthController.php’, { method: ‘POST’ }) .then(response => response.json()) // JSONレスポンスを取得 .then(data => { console.log(“サーバーレスポンス:”, data); if (data.status === “ok”) { window.location.href = data.redirect; // 成功時のリダイレクト } else { document.getElementById(‘errorMessage’).textContent = data.message; // 失敗時のメッセージ表示 } }) .catch(error => console.error(“Fetchエラー:”, error)); |
then関数に指定した無名関数がコールバック関数です。
catch関数に指定した無名関数もコールバック関数です。
then関数の
「適切なタイミング」とは「Promiseオブジェクトの状態がFulfilledのとき」を指します。
「後で呼び出される」とは「fetch関数でWeb APIを呼び出した後Promiseオブジェクトの状態がFulfilledのときにthen関数が呼び出されて実行されること」を指します
catch関数の
「適切なタイミング」とは「Promiseオブジェクトの状態がRejectedのとき」を指します。
「後で呼び出される」とは「fetch関数でWeb APIを呼び出した後Promiseオブジェクトの状態がRejectedのときにthen関数が呼び出されて実行されること」を指します
非同期処理にコールバック関数を使う理由
3つの実装例でみてきたように、コールバック関数は、非同期処理で使われるケースが多く、かつ、非同期処理をしているタスクと同期をとる必要がある処理でよく使われる技法、です。
つまり、非同期処理の完了を待って、その結果を使って次の処理をするような場合に活用されます。
言葉を変えると、コールバック関数を利用すると、非同期処理が完了したタイミングで実行できる、ということです。
コールバック関数を引数に指定する方法
関数の引数となる関数(コールバック関数)ですが、引数に指定する方法は3つあります。
- コールバック関数の参照
- 無名関数
- アロー関数
それぞれの記法とメリット・デメリットは以下の通りです。
コールバック関数の参照
メリット: 可読性が高く、再利用しやすい
デメリット: 簡単な処理のためだけに関数を定義する必要がある
unction myCallback() { console.log(“コールバック関数が実行されました!”); } function executeCallback(callback) { console.log(“関数の中でコールバックを実行します…”); callback(); } |
無名関数
メリット: シンプルな処理なら、わざわざ別の関数を定義しなくてもよい
デメリット: 可読性が下がり、再利用が難しくなる
executeCallback(function() { console.log(“無名関数(匿名関数)のコールバックが実行されました!”); }); |
アロー関数
メリット: より簡潔な記述が可能で、コードがすっきりする
デメリット: this の動作が通常の関数と異なるため注意
executeCallback(() => { console.log(“アロー関数のコールバックが実行されました!”); }); |
コールバック関数に関しては以上です。
次回は、JavaScriptのタスク管理の仕組み「コールスタック」と「イベントループ」について掲載する予定です。
Fetchを利用した非同期Webアプリシリーズのリンク