JavaScriptの非同期処理について

JavaScriptの非同期処理について簡単にまとめました。

## 1. コールバック関数

- コールバック関数は非同期処理の基本的な仕組み
- 長時間かかる処理を実行する際に途中で他の処理を実行できるようにするもの
- 処理が完了する前に次の処理を実行することができる

### コールバック関数の概念

- ある関数が完了した後に呼び出される関数のこと
- 処理が完了する前に次の処理を実行することができる

### 非同期処理におけるコールバック関数の使い方

非同期処理において、コールバック関数を使う場合、以下のような形式で関数を定義します。

```javascript
// calculate 関数の定義
const calculate = (num1, num2, operation, callback) => {
  let result;

  // operation に応じた計算を行う
  if (operation === "add") {
    result = num1 + num2;
  } else if (operation === "subtract") {
    result = num1 - num2;
  } else if (operation === "multiply") {
    result = num1 * num2;
  } else if (operation === "divide") {
    result = num1 / num2;
  }

  // コールバック関数を呼び出して計算結果を渡す
  callback(result);
};

// 計算結果を表示するコールバック関数の定義
const printResult = (result) => {
  console.log(`計算結果は ${result} です。`);
};

// calculate 関数を呼び出す
calculate(5, 10, "multiply", printResult);


```

### コールバック関数のネストによる問題とその解決策

コールバック関数をネストすることでコードが複雑になり、可読性が低下する。
この問題を解決するために、Promiseやasync/awaitを使う。

## 2. Promise

- Promiseは非同期処理の結果を扱うためのオブジェクト
- 非同期処理が成功した場合にはresolve関数を、失敗した場合にはreject関数を呼び出す
- then()メソッドやcatch()メソッドを使って、処理の成功・失敗時にそれぞれ実行する関数を指定することができる
- 複数の非同期処理を連結して実行することもできる。

### Promiseの概念と基本的な使い方

thenメソッドで成功時の処理、catchメソッドで失敗時の処理をする。

```javascript
// 非同期処理を含む Promise オブジェクトを返す関数を定義
const getData = () => {
  return new Promise((resolve, reject) => {
    // 2 秒後にデータを取得する
    setTimeout(() => {
      // 取得したデータを resolve() で解決する
      const data = { id: 1, name: 'John Doe' };
      resolve(data);
    }, 2000);
  });
};

// データ取得成功時の処理を定義するコールバック関数
const onSuccess = (data) => {
  console.log('Data fetched successfully:', data);
};

// データ取得失敗時の処理を定義するコールバック関数
const onError = (error) => {
  console.error('Error occurred while fetching data:', error);
};

// getData() 関数を呼び出し、成功時の処理と失敗時の処理を then() メソッドと catch() メソッドで登録する
getData().then(onSuccess).catch(onError);


```

### Promiseチェーンによる非同期処理のシーケンス化

Promiseチェーンを使うと、複数の非同期処理をシーケンス化することができる。
thenメソッドで次の処理に渡す値を返すことにより次の処理を続けることができる。

```javascript
promise.then(成功時の処理1)
.then(成功時の処理2)
.catch(失敗時の処理);
```

### Promise.allとPromise.raceの使い方

Promise.allを使うと、複数のPromiseが全て成功した場合にのみ、成功時の処理を実行することができる。

```javascript
Promise.all([promise1, promise2])
.then(成功時の処理)
.catch(失敗時の処理);
```

Promise.raceを使うと、複数のPromiseのうち、一番最初に成功または失敗したPromiseを返す。

```javascript
Promise.race([promise1, promise2])
.then(成功時の処理)
.catch(失敗時の処理);
```

## 3. async/await

async/awaitは、Promiseよりもシンプルな書き方で非同期処理を扱うことができる。

### async/awaitの概念と基本的な使い方

async/awaitを使うと、非同期処理の実行を同期的なコードのように書くことができる。

```javascript
// async functionを定義する
async function asyncTest() {
  // 非同期処理（APIからデータを取得するなど）

  // 非同期処理が完了したら、その結果を返す
  return result;
}

// 非同期処理を呼び出し、その結果を変数に代入する
const result = await asyncTest();

```

### async/awaitとPromiseの違い

**Promise**
非同期処理を扱うための基本的な方法で、async/awaitはPromiseをより直感的に扱えるようにした機能。
**async**
Promiseを返す関数
**await**
Promiseが解決されるまで関数の実行を一時停止する。

**1. 書き方の違い**
Promiseは、.then()と.catch()メソッドを使って非同期処理をチェーンするが、
async/awaitはawaitを使って同期的なコードのように非同期処理を実行できる
Promiseをasync/awaitと組み合わせて使用できる。

**2. エラーハンドリングの違い**
Promiseでは、.catch()メソッドを使ってエラー処理を行うが、async/awaitではtry/catch文を使ってエラー処理を行う。

**3. async/awaitはPromiseよりも簡単に理解できる**
async/awaitはコードがより読みやすい。
また、async/awaitを使用することでコールバック関数の使用を避けることができる。

### async/awaitによる非同期処理のエラーハンドリング

try-catch文を使うことで、非同期処理のエラーハンドリングができる。

```javascript
async function asyncTest() {
  try {
    // 非同期処理の実装
    return result;
  } catch (error) {
    // エラーが発生したら新しいエラーオブジェクトを作成してエラーメッセージを表示する
    throw new Error('失敗しました');
  }
}

try {
  // asyncTest関数を呼び出し、その結果をresult変数に代入する
  const result = await asyncTest();
} catch (error) {
  // エラーが発生した場合はエラーをキャッチしてエラーメッセージを表示する
  console.error(error);
}

```

## 4. APIを使った非同期処理の実装例
ボタンを押すとランダムな猫の画像を表示します。

<p class="codepen" data-height="300" data-default-tab="html,result" data-slug-hash="dyqZLzP" data-user="takuma348" style="height: 300px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;">
  <span>See the Pen <a href="https://codepen.io/takuma348/pen/dyqZLzP">
  test_API</a> by takuma348 (<a href="https://codepen.io/takuma348">@takuma348</a>)
  on <a href="https://codepen.io">CodePen</a>.</span>
</p>
<script async src="https://cpwebassets.codepen.io/assets/embed/ei.js"></script>

```html
<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <title>Random Image</title>
    <link rel="stylesheet" href="style.css">
  </head>

  <body>
    <h1>Random Cat Image</h1>
    <button id="btn">Get image</button>
    <div id="cat-image"></div>
    <script src="script.js"></script>
  </body>
</html>
```

```css
#btn {
  margin-bottom: 20px;
}

#cat-image img {
  max-width: 100%;
}
```

```javascript
// APIのURL
const apiUrl = 'https://picsum.photos/200/300';

// HTML要素の取得
const btn = document.getElementById('btn'); // ボタン
const catImageContainer = document.getElementById('cat-image'); // 画像

// ボタンクリック時の設定
btn.addEventListener('click', () => {
  // 画像取得処理の実行
  getCatImage()
    .then(url => {
      // 画像要素の作成と設定
      const img = document.createElement('img');
      img.src = url;

      // 画像表示要素の初期化と画像の追加
      catImageContainer.innerHTML = '';
      catImageContainer.appendChild(img);
    })
    .catch(error => console.error(error));
});

// 画像取得処理
async function getCatImage() {
  try {
    const response = await fetch(apiUrl); // APIから画像を取得
    return response.url; // 画像URLを返す
  } catch (error) {
    throw new Error('Failed to get an image');
  }
}

```