2
1

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 1 year has passed since last update.

【JavaScript】非同期処理について 2〜非同期処理のチェーン、コールバックヘル〜

Last updated at Posted at 2021-01-01

※当方駆け出しエンジニアのため、間違っていることも多々あると思いますので、ご了承ください。また、間違いに気付いた方はご一報いただけると幸いです。

本記事は下記の記事の続きとなります。

#非同期処理のチェーンとは

非同期処理のチェーンとは、一つの非同期処理が終了した後に、次の非同期処理を開始し、チェーンのようにどんどんつなげていく処理のことです。

今この様な非同期処理を行うプログラムがあるとします。

function sleep() {
  setTimeout(function () {
    console.log(0);
  }, 1000);
}

1000ミリ秒後に0を出力するプログラムです。

非同期処理をつなげるには、内部でさらに非同期処理を呼び出してあげます。

function sleep() {
  setTimeout(function () {
    console.log(0);
    sleep(); //1000ミリ待機後、関数sleep自体が呼び出される。
  }, 1000);
}

上記のプログラムでは、呼び出された関数sleepが、さらにsleep関数を呼び出し無限ループしてします。

そこで引数として関数sleepを呼び出す関数を渡してあげます。

function sleep(callback) {
  setTimeout(function () {
    console.log(0);
    callback();
  }, 1000);
}

sleep(function () { sleep( function(){} ) });

この様にすることで、非同期処理を繋げることができました。
※二回目の関数sleepには何もしない処理をコールバックに渡しています。

話が脱線しますが、下記のプログラムだとエラーになります。

function sleep(callback) {
  setTimeout(function () {
    console.log(0);
    callback();
  }, 1000);
}

sleep( sleep( function(){} ));

理由は、引数として渡される値が、「関数」か、「関数の戻り値」かの違いです。
正しい方のプログラムでは引数として

function () { sleep( function(){} ) }

無名関数自体を渡していますが、エラープログラムでは

 sleep( function(){} )

引数で中で、先にsleep関数を実行させちゃっています。

console.log(function () { sleep( function(){} ) });
console.log(sleep( function(){} ));

上記のプログラムを実行してみたらわかりますが、正しいプログラムの出力結果は[Function (anonymous)]で、エラープログラムの方の結果は、「undefined」となります。(関数sleep自体は戻り値を返すプログラムではないので)

よって、エラーバージョンのプログラムは下記を実行していることになります。

function sleep(callback) {
  setTimeout(function () {
    console.log(0);
    callback();
  }, 1000);
}

sleep( sleep( undefined ));

もちろん、引数に渡したundefinedは、関数ではないので、下記の様なエラーが出力されます。

TypeError: callback is not a function

//本来、仮引数callbackは、関数を受け取るが、それ以外が渡って来てるよという意味

話を戻します。

非同期処理の結果を次の非同期処理に渡すこともできます。

function sleep(callback, n) {
  setTimeout(function () {
    console.log(n);
    n++
    callback(n);
  }, 1000);
}

sleep(function (n) {   sleep(function () { }, n) }  ,   0);

少し分かりづらいですが、一回目のsleepでは、第二引数に0を渡しています。
内部処理でnは1加算され、その結果をコールバック関数に渡しています。
↓ こういうこと

function (1) {   sleep(function () { }, 1) }

二回目のsleepは、一回目の処理結果nを受けて、その結果を出力します。結果は

0
1

となります。

では同じように関数sleepを五回つなげてみます。

function sleep(callback, n) {
  setTimeout(function () {
    console.log(n);
    n++
    callback(n);
  }, 1000);
}

sleep(function (n) { sleep(function (n) { sleep(function (n) { sleep(function (n) { sleep(function (n) { sleep(function (n) { }, n); }, n); }, n); }, n); }, n); }, 0);

この様に、引数の中に再帰的に、sleepを呼び出す関数とnを受け渡していきます。これだとあまりにもヘルが過ぎるのでインデントをすると

function sleep(callback, n) {
  setTimeout(function () {
    console.log(n);
    n++
    callback(n);
  }, 1000);
}
sleep(function (n) {
  sleep(function (n) {
    sleep(function (n) {
      sleep(function (n) {
        sleep(function (n) {
          sleep(function (n) {
          }, n);
        }, n);
      }, n);
    }, n);
  }, n);
}, 0);

これだと多少見やすくなりましたが、それでも分かりづらいですよね。
この様に、コールバックのネストが深くなっていきコードの見通しが悪くなっていくことを 「コールバックヘル」 と呼びます。

このような、コールバックヘルを回避するために 「Promise」 という処理があります。

次に続きます。

また、下記の記事でより詳細に非同期処理を記載いたしました。
もしよければ、下記の記事もご参考ください。

2
1
2

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
2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?