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

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

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

https://qiita.com/sho_U/items/ff82aa576837198097ce

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

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

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

```js
function sleep() {
  setTimeout(function () {
    console.log(0);
  }, 1000);
}
```
1000ミリ秒後に0を出力するプログラムです。

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

```js
function sleep() {
  setTimeout(function () {
    console.log(0);
    sleep(); //1000ミリ待機後、関数sleep自体が呼び出される。
  }, 1000);
}
```
上記のプログラムでは、呼び出された関数sleepが、さらにsleep関数を呼び出し無限ループしてします。

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

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

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

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

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

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

```js
function () { sleep( function(){} ) }
```
無名関数自体を渡していますが、エラープログラムでは

```js
 sleep( function(){} )
```
引数で中で、先にsleep関数を実行させちゃっています。

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

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

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

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

```
TypeError: callback is not a function

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

話を戻します。

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

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

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

```js
function (1) {   sleep(function () { }, 1) }
```
二回目のsleepは、一回目の処理結果nを受けて、その結果を出力します。結果は

```
0
1
```
となります。

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

```js
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を受け渡していきます。これだとあまりにもヘルが過ぎるのでインデントをすると

```js
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」**  という処理があります。

次に続きます。

https://qiita.com/sho_U/items/401540dadd485e270db4

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

https://qiita.com/sho_U/items/f07a4f3e7760a9729f10