コールバックを使った非同期処理の実装例

  • 3
    Like
  • 0
    Comment

コールバックを使った非同期実装について、分解してみるといろいろなエッセンスが詰まっていたのでまとめました。なぜ/どうやってコールバックを使うのか、理解する助けになれば幸いです。

Ajaxでデータを取得する関数getApiData()を作ります。

完成形:

function getApiData(callback) {
  $.ajax('/path/to/api')
    .done(function (data) {
      callback(null, data);
    })
    .fail(function (error) {
      callback(error);
    });
}

getApiData(function (error, data) {
  // エラー時の処理
  if (error) {
    console.error(error);
    return;
  }
  // 成功時の処理
  console.log(data);
});

コールバックと非同期処理を中心に、この実装例について解説します。

1. コールバック

1-0. 普通の引数

これは普通の文字列を関数に渡している例です。
saySomethingの引数valueには、変数helloに入れておいた文字列が渡されます。

function saySomething(value) {
  console.log(value);
}

var hello = 'Hello!!';

saySomething(hello);

1-1. 引数に関数を渡す

今度は変数helloに文字列ではなく、関数を代入してdoSomething()に渡しています。doSomething内の引数callbackは関数なので、callback()と書けば実行することができます。

function doSomething(callback) {
  callback();
}

var hello = function () {
  console.log('Hello!!');
};

doSomething(hello);

このように別の関数の中で実行されるように渡された関数のことをコールバックといいます。

1-2. 無名関数を直接渡す

先ほどは変数helloに関数を格納してdoSomething()を呼び出していましたが、変数化せずに無名関数をそのまま渡すこともできます。実行結果は同じです。

function doSomething(callback) {
  callback();
}

doSomething(function () {
  console.log('Hello!!');
});

1-3. コールバックに引数を渡す

getSomething内のcallbackは関数なので、callback('Hello!!')と書けば実行時に値を渡すことができます。この値は無名関数内の引数valueへと伝わります。

function getSomething(callback) {
  callback('Hello!!');
}

getSomething(function (value) {
  console.log(value);
});

2. 非同期処理

2-0. 同期処理で値を取得する

今までの例では、AjaxやsetTimeoutのような非同期処理を含んでいなかったので、わざわざコールバックを使う必要性はありませんでした。1-3のコードは、こう書いたほうが自然です。

function getSomething() {
  return 'Hello!!';
}

var value = getSomething();
console.log(value);

ですが、もしgetSomethingが非同期でしか処理できない場合、returnを使って値を返すことができません。

うまくいかない例:

function getSomething() {
  setTimeout(function () {
    return 'Hello!!';
  }, 1000);
}

var value = getSomething();   // この方法では値が取れない
console.log(value);

ここでコールバックを使った値の受け渡しが必要になります。
※ECMAScript 6ではコールバックの代わりにPromiseでも非同期処理を扱うことができます。

2-1. コールバックで非同期処理の値を取得する

1-3にsetTimeoutを追加しただけのコードです。呼び出し側(後半のコード)は何も変わっていません。

function getSomething(callback) {
  setTimeout(function () {
    callback('Hello!!');
  }, 1000);
}

getSomething(function (value) {
  console.log(value);
});

コールバックを使うと、getSomething内の処理が非同期処理になっても(もちろん同期処理だったとしても)同じようにvalueへと値を渡すことができます。

2-2. ajaxの結果を取得する

もう少し実践的な例にしましょう。非同期処理の内容をAjaxにしてみます。Ajaxが完了すると、コールバックを通してconsole.log()に取得したデータが渡ります。

function getApiData(callback) {
  $.ajax('/path/to/api')
    .done(function (data) {
      callback(data);
    });
}

getApiData(function (data) {
  console.log(data);
});

2-3. ajaxのエラー処理を追加する

2-2では、Ajaxが成功したときはコールバックが実行されますが、失敗したときは何も起きませんでした。.fail()を追加して、Ajaxが失敗してもcallback()が呼ばれるようにします。

function getApiData(callback) {
  $.ajax('/path/to/api')
    .done(function (data) {
      callback(data);
    })
    .fail(function (error) {
      callback(error);
    });
}

getApiData(function (data) {
  console.log(data);   // dataは取得したデータ?それともエラー?
});

しかし、このままだとエラーが起きたのか、正常にデータが取れたのか判別が難しい状態です。

エラー判定をコールバック側へと伝えるために、callback()の引数を2つに増やして、第1引数にはエラーのオブジェクトを、第2引数には取得したデータを渡すように変更します。正常時は第1引数にnullを渡し、エラーがないことを明示します。

function getApiData(callback) {
  $.ajax('/path/to/api')
    .done(function (data) {
      callback(null, data);
    })
    .fail(function (error) {
      callback(error);
    });
}

getApiData(function (error, data) {
  // エラー時の処理
  if (error) {
    console.error(error);
    return;
  }
  // 成功時の処理
  console.log(data);
});

これにて完成です!
コールバックを活用し、非同期のデータ取得をエラー判定付きで実装することができました。