0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

[javascript] コールバック関数と非同期処理について勉強しました

Posted at

こういう記事、何番煎じだよ・・・という感じですが。やっていきます。

コールバック関数とは

Javascriptの中で、関数の引数として関数を用いる際、定義する関数を高階関数、引数として呼び出す関数をコールバック関数というみたいです。

var aisatsu = function(){
  alert("hello!");
};
  
setTimeout(aisatsu, 2000);

ここでいうsetTimeoutは高階関数、aisatsuはコールバック関数といいます。
ちなみに前回の記事の復習ですが、アロー関数を用いて次のようにも書けましたね。

setTimeout(() => alert("hello!"), 2000);

・・・とりあえずコールバック関数の説明はこれだけです。
もっと複雑な書き方も出来そうですが、一旦基本のところだけ抑えられればOKな気がしました。あとは応用ですね。アロー関数については前回の記事から勉強しましょう。
JavaScriptでは非同期処理などにとっても良く使われるみたいなので、非同期処理の書き方とかも見て勉強していきましょう。

非同期処理について

自分の事前知識は以下です。

  • 非同期処理という言葉については理解している(つもり)。
  • APIの呼び出しとかはとりあえず非同期処理使えばええんやろ?
  • Promiseとかasync,awaitとか、単語はかろうじてわかるくらい。

・・・とりあえず、非同期処理周りについて調べて勉強していきましょう。

非同期処理の例

みんな大好きsetTimeoutを例にとって勉強していきます。
ちなみにsetTimeoutは非同期関数です。

例えば以下のように書くと

console.log("あああああ");

setTimeout(() => console.log("いいいいい"),1000)

console.log("ううううう");

あああああ
ううううう
いいいいい

と表示されます。「いいいいい」は1秒後にconsole.logしてるので当たり前っちゃ当たり前ですね。

では以下だとどうでしょう?

console.log("あああああ");

setTimeout(() => console.log("いいいいい"),0)

console.log("ううううう");

じつはこれも、

あああああ
ううううう
いいいいい

と表示されます。「0秒後に表示だからすぐ表示されるんじゃないの?」と思うかもしれませんが、javascriptではこのような動きとなります。
詳細な説明は出来ません省きますが、こちらの記事(JavaScript イベントループの仕組みをGIFアニメで分かりやすく解説)が分かりやすかったので参照されると良いかもしれません。

イベントキューにどんどん溜まっていって、console.log("いいいいい")は、setTimeoutが解釈された後に(つまりタイマー後に)キューに追加される(実行されるわけじゃない!)ので、どちらにしろ遅れるわけですね。

javascriptのタスクキューはシングルスレッドで、ブラウザのメインスレッドというところで実行されます。
ブラウザのメインスレッドということは、UIにも関係します。setTimeoutが非同期ではなく同期関数であった場合は、setTimeoutのタイマー分、ブラウザが読み込み中のようになって、動かなくなってしまいます。なので非同期関数にしているというわけですね。API呼び出しも同様です。

非同期処理の書き方

仮のAPI呼び出しとして、非同期関数による戻り値を使用したいシステムがあったとしましょう。setTimeoutという非同期関数のコールバックで計算した値をログ出力するコードです。

var funcMultiply = function(a_hikisuu, b_hikisuu){
  var res = 0;
  setTimeout( () => {
      res = a_hikisuu*b_hikisuu;
      console.log(res);
      setTimeout( () => {
          res *= 100;
          console.log(res);
      }, 1000);
  },1000);
}

function asyncTest(val_a,val_b){
    funcMultiply(val_a,val_b);
}

asyncTest(100,100);

我ながらなかなか見づらいですが・・・。
funcMultiplyを呼ぶと、1秒待って、引数の積をconsole.logしています。さらにそのまた1秒後、積に100を掛けたものをconsole.logしています。

もうここまでで分かるかと思いますが、なかなか汚いです。非同期関数の性質上、このようにコールバック関数にどんどん処理を追加していくことになります。これがどんどん積み重なって読みづらくなっていったコードをコールバック地獄とも呼ぶそうですね。

もっときれいにかけたりしないのでしょうか・・・。非同期関数の実行部分と、その結果を使用する部分を分けると、もっと簡潔に書けるような気がするのですが。

Promise

Promiseを使ってみましょう。
promiseとは、

  • 待機(pending)
  • 成功(fulfilled)
  • 失敗(rejected)

という3値を持つオブジェクトのようです。
とりあえず使ってみました。以下のように関数が書けます。

var funcMultiply = function(a_hikisuu, b_hikisuu){
  return new Promise((resolve,reject) => {
    var res = 0;
    setTimeout( () => {
        res = a_hikisuu*b_hikisuu;
        resolve(res);
    },1000);
  });
}

function asyncTest(val_a,val_b){
    funcMultiply(val_a,val_b)
    .then(result => console.log(result))
}

asyncTest(100,100);

10000

よくわかんないけど、非同期関数を実行しているところ(呼び出されたfuncMultiply)と、その結果を使用しているところ(then)を分けて書くことが出来ました。これならかなり綺麗に書けそうではないでしょうか。
Promiseというクラスは、関数を引数にしてnewをすることで、その関数(非同期)の結果に応じたふるまいをすることが出来ます。
そしてその関数の引数resolverejectで、成功時の挙動、失敗時の挙動が決められます。
resolveで値を返すと、呼びもとのthenが実行されます。
さらにrejectで返すと、呼びもとのcatchが実行されます。
以下のような感じです。

var funcMultiply = function(a_hikisuu, b_hikisuu){
  return new Promise((resolve,reject) => {
    var res = 0;
    setTimeout( () => {
        res = a_hikisuu*b_hikisuu;
        // resolve(res);
        reject("エラーだよ");
    },1000);
  });
}

function asyncTest(val_a,val_b){
    funcMultiply(val_a,val_b)
    .then(result => console.log(result))
    .catch(result => console.log(result))
}

asyncTest(100,100);

エラーだよ

なかなかわかりやすいのではないでしょうか。エラー処理も簡潔にかけて良いですね。

ちなみにpromiseについてはこちらの記事「今更だけどPromise入門」がとても参考になりました。ありがとうございます。

async,await

promiseとasync,awaitを使用するともっと簡潔に書くこともできそうです。

こんな感じ

var funcMultiply = function(a_hikisuu, b_hikisuu){
  return new Promise((resolve,reject) => {
    var res = 0;
    setTimeout( () => {
        res = a_hikisuu*b_hikisuu;
        resolve(res);
    },3000);
  });
}

async function asyncTest(val_a,val_b){
    var res = await funcMultiply(val_a,val_b);
    console.log(res);
}

asyncTest(100,100);

asyncTest非同期関数をにしています。asyncを先頭につけていますね。これで非同期関数になるようです。この関数はpromiseを返してくれるようです。さらにこの中ではawaitが使用できます。関数の終わりまで待っていてくれます。thenにしばられなくて良くなったのが利点でしょうか。
また、awaitを使用するときは必ずasync関数の中で書かないといけない事にも注意が必要です。必ず非同期関数でラップが必要ということですね。

けっこうスッキリかけているでしょうか。なかなか難しそうですが・・・。もっと勉強して使いこなせるようになりたいですね。例外処理についてはちょっと気を付けなければいけない部分もありそうです。

非同期処理についてはこちら記事(コールバック地獄からの脱出)がとても分かりやすく参考になりました。ありがとうございました。

途中からちょっと難しくなりましたが・・・。
引き続き勉強していきましょう。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?