非同期処理の基本的な制御

##非同期処理の動作を確認
setTimeout関数で簡単な非同期な関数を確認することができます。

```.js

// setTimeout：第一引数の関数を、第二引数のミリ秒後に実行

// 2秒後に'Hello'を出力
const outputHello = () => {
  setTimeout(() => {
    console.log('Hello');
  }, 2000);
}

// 1秒後に'World'を出力
const outputWorld = () => {
  setTimeout(() => {
    console.log('World!');
  }, 1000);
}

outputHello();
outputWorld();

```

---
##実行結果
前ページのコードをブラウザのコンソールに貼り付けて実行します。
出力結果は期待通りだったでしょうか？

先に書いたsetTimeoutのコールバック関数の実行より前に、後ろで書いたsetTimeoutのコールバック関数が実行されています。

---
##非同期処理制御の必要
非同期処理が絡むと、単純に順番に書いただけでは、想定する順番で動かないことがあります。
例えば、サーバAPIでJSONデータをフェッチした後に、ローカル変数に代入する場合は、レスポンスが返ってくる前に、代入の処理に進んでしまいます。

---
##コールバック（非推奨）
非同期処理の古典的な実装にコールバックを段々と積み上げることで順序保障する方法があります。
例外処理などを書いていくと、コード見通しが非常に悪くなるので、現在ほぼ使う機会はないかと思います。

```.js
// 2秒後に'Hello'を出力
const outputHello = (callback) => {
  setTimeout(() => {
    console.log('Hello');
    callback(); // コールバック関数を実行
  }, 2000);
}

// 1秒後に'World'を出力
const outputWorld = () => {
  setTimeout(() => {
    console.log('World!');
  }, 1000);
}

outputHello(outputWorld); // outputWorld関数をoutputHello内で実行する
```

---
##Promise
JavaScript標準でpromiseという非同期処理を制御するためのものが用意されています。
後述するasync/awaitなどもフロントエンドの非同期処理の基盤となっている機能です。

---
##Promiseに書き換え
さきほどのsetTimeoutの関数をPromiseで扱えるようにします。
API通信時はaxiosなどライブラリを使うことが多いので、実業務でPromiseを返す関数を作成することは少ないかと思います。

```.js
// 2秒後に'Hello'を出力
const outputHello = () => {
  return new Promise(function(resolve, reject) {
    setTimeout(() => {

      // 乱数（0〜10）が3未満の場合はreject
      Math.random() *10 > 3
        ? resolve(console.log('Hello'))
        : reject(Error('outputHelloでエラー発生'));
      
    }, 2000);
  });
}

// 1秒後に'World'を出力
const outputWorld = () => {
  return new Promise(function(resolve, reject) {
    setTimeout(() => {

      // 乱数（0〜10）が3未満の場合はreject
      Math.random() *10 > 3
        ? resolve(console.log('World!'))
        : reject(Error('outputWorldでエラー発生'));
      
    }, 1000);
  });
}

outputHello()
  .then(outputWorld)
  .catch((e) => {
    console.log(e.message);
  })
  .finally(()=>{
    console.log('処理終了');
  });
```

---

##Promiseの状態
Promiseを返却する関数は３つ状態を持ちます。


- pending

resolveもrejectも実行していない状態。例えば。APIサーバに投げたりクエストが返ってきていない状態など。

- fulfill

resolveが実行されて、正常に処理が完了した状態。thenでチェインしている場合、次の関数に進む。

- reject

rejectが実行された状態。基本的に例外が発生した場合にrejectとなる。thenでチェインしている場合でも、次の関数に進まず、catchメソッドに飛ぶ。

![スクリーンショット 2021-06-23 10.09.04.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/537165/ab85dbca-288f-6e68-8980-104e77630fb2.png)

---
##便利メソッド(Promise.all)
複数のPromiseを返す関数を一斉に走らせて、全ての関数のPromiseの状態がfullfillになるとthenを実行する。
それぞれの非同期処理の順序はどうでも良いが、全て完了したあとに処理を行いたい際に使う。

※１つでもrejectがあると、catchを実行する。

```.js
Promise.all([outputHello(), outputWorld()])
    .then(()=> {
      // all fulfill
    })
    .catch((e)=> {
      // exist reject
    });
```

たいていの処理がPromise.thenとPromise.allで対応できますが、必要に応じて他のメソッドも検討します。

---

##async/await
Promiseは非同期処理をコールバックを使わずに記述できますが、async/awaitを使うことで、さらに簡略化できます。

---

## awaitの役割
awaitを付けるとPromiseの結果が返却されるまで処理を待機します。
thenでチェインする必要なく記述した順序で実行され、Promiseの結果を待ちます。

後述するasyncを付けた関数内でしか使用できません。try-catchで例外を捕捉することができます。


```.js
try {

    await outputHello();
    await outputWorld();

  } catch (e) {
    console.log(e.message)
  } finally {
    console.log('処理終了')
  }
```

---
## asyncの役割
- asyncを付けた関数（async function）がPromiseを返すようになる。
- async function内でreturnすると、resolveを実行する
- async function内でthrowすると、rejectを実行する

要するに非同期処理をあまり意識せずに、returnしたら正常終了、throwしたら例外発生とすることができる。

---
## asyncのサンプルコード
```.js
// 2秒後に'Hello'を出力
const outputHello = () => {
  return new Promise(function(resolve, reject) {
    setTimeout(() => {

      // 乱数（0〜10）が3未満の場合はreject
      Math.random() *10 > 3
        ? resolve(console.log('Hello'))
        : reject(Error('outputHelloでエラー発生'));
      
    }, 2000);
  });
}

// 1秒後に'World'を出力
const outputWorld = () => {
  return new Promise(function(resolve, reject) {
    setTimeout(() => {

      // 乱数（0〜10）が3未満の場合はreject
      Math.random() *10 > 3
        ? resolve(console.log('World!'))
        : reject(Error('outputWorldでエラー発生'));
      
    }, 1000);
  });
}

const sequence = async () => {

  try {

    await outputHello();
    await outputWorld();

  } catch (e) {
    throw e;
  } finally {
    console.log('sequence処理終了')
  }
}

// asyncをつけるとPromiseを返す関数となるので、Promiseのメソッドが使える！
sequence()
  .then(sequence)
  .catch((e)=> console.log(e.message))
  .finally( ()=> console.log('処理終了'));
```

---
##おまけ

---
##課題１
コンソール出力内容は？

```.js
setTimeout(function() {
  console.log('A');
}, 5000);

setTimeout(function() {
  console.log('B');
}, 4000);

setTimeout(function() {
  console.log('C');
}, 3000);

setTimeout(function() {
  console.log('D');
}, 2000);

setTimeout(function() {
  console.log('E');
}, 1000);
```

---
##課題2
コンソール出力内容は？

```.js
setTimeout(function() {
  console.log('A');
  setTimeout(function() {
    console.log('B');
    setTimeout(function() {
      console.log('C');
      setTimeout(function() {
        console.log('D');
        setTimeout(function() {
          console.log('E');
        }, 1000);
      }, 2000);
    }, 3000);
  }, 4000);
}, 5000);
```

---
##課題3
コンソールにHelloと出力されましたが、続いてWorldが出力されません。

```.js
const outputHello = () => {
  return new Promise(function(resolve, reject) {
    setTimeout(() => {
      console.log('Hello');
    }, 2000);
  });
}

const outputWorld = () => {
  return new Promise(function(resolve, reject) {
    setTimeout(() => {
      console.log('World!');
    }, 1000);
  });
}

outputHello()
  .then(outputWorld)
  .catch((e) => {
    console.log(e.message);
  })
  .finally(()=>{
    console.log('処理終了');
  });
```

---
##課題4
asyncの機能の認識を間違って実装している部分があります。指摘してください。

```.ts

// httpリクエストを発行する関数
public async executeRequest ():Promise<any> {

    if (!this.isValid) {

      return new Promise(() => {
        throw new InvalidError('無効なリクエストです。');
      });
    }

    // axiosを使ってJSONデータをリクエスト
    return axios.request(this.config)
      .then((res) => {
        return res.data;
      })
      .catch((e) => {
        throw new Error(e.message);
      });
```