Promise.allを使用したAPI並列処理の実装と処理時間の改善

# はじめに

複数のAPIを呼び出す必要がある場合、直列的に処理を行うとそれらの処理時間が各APIの処理時間の合計になってしまいます。本記事では、`Promise.all`を使用して複数のAPIを並列で呼び出し、全体の処理時間を短縮する処理を実装します。


# 現状
- API A（2秒）とAPI B（3秒）を順番に呼び出すと、合計5秒かかる
- APIの実行順序に依存関係がない


# モックAPI
検証用にコールするモックAPIを2つ作成します。

1. **API A (`/api/check`)**
   - 処理時間: 2秒
   - レスポンス: `{ isAvailable: boolean }`


2. **API B (`/api/process`)**
   - 処理時間: 3秒
   - レスポンス: `{ message: "hogeMessage" }`


# 環境
- express：4.21.2
- cors：2.8.5


# 構成
```
├─backend
│      server.js
│
└─frontend
       index.css
       index.html
       index.js
```

# ゴール
2つのAPIを並列で呼び出し、処理時間を5秒から3秒に改善する。


# モックAPIを作成

フロントからコールするためのモックAPIを準備します。

```javascript:index.js
const express = require('express');
const app = express();
const cors = require('cors'); 
app.use(cors());


// API A:(2秒)
app.get('/api/check', (req, res) => {
  setTimeout(() => {
    res.json({
      isAvailable: true
    });
  }, 2000);
});


// API B: (3秒)
app.get('/api/process', (req, res) => {
  setTimeout(() => {
    res.json({
      message: "hogeMessage"
    });
  }, 3000);
});


const port = 8000;
app.listen(port, () => {
  console.log(`Server running on http://localhost:${port}`);
});
```



# フロントエンド実装

## HTML

ボタンをクリックすることで、APIの呼び出しを実行できる簡単なUIを実装します。

```html:index.html
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <title>API並列処理デモ</title>
    <link rel="stylesheet" href="index.css">
</head>
<body>
    <div class="container">
        <button id="actionButton">APIコール</button>
        <div id="loading" class="loading">処理中...</div>
        <div id="timer" class="timer"></div>
    </div>
    <script src="index.js"></script>
</body>
</html>
```

<details><summary>index.css</summary>

```css:index.css
body {
  display: flex;
  justify-content: center;
  align-items: center;
  height: 100vh;
  margin: 0;
}

.container {
  text-align: center;
}

button {
  padding: 10px 20px;
  cursor: pointer;
}

.loading {
  display: none;
  margin-top: 10px;
}

.loading.show {
  display: block;
}

.timer {
  margin-top: 10px;
}
```

</details>


# APIコールするスクリプト

## 直列的にAPIコールする場合



```javascript:index.js
const button = document.getElementById('actionButton');
const loading = document.getElementById('loading');


async function handleClickSequential() {
    const startTime = performance.now();
    button.disabled = true;
    loading.classList.add('show');

    // 直列的にAPIコール
    const responseA = await fetch('http://localhost:8000/api/check');
    const resultA = await responseA.json();
    const responseB = await fetch('http://localhost:8000/api/process');
    const resultB = await responseB.json();
    
    button.disabled = false;
    loading.classList.remove('show');
    const endTime = performance.now();
    const  processedTime = `処理時間: ${((endTime - startTime) / 1000).toFixed(2)}秒`;

    // 実行結果
    console.log('API A結果:', resultA);
    console.log('API B結果:', resultB);
    console.log(processedTime);
}
```


## 並行でAPIコールする場合



```javascript:index.js
async function handleClickParallel() {
    const startTime = performance.now();
    button.disabled = true;
    loading.classList.add('show');

    // 並列にAPIコール
    const [resultA, resultB] = await Promise.all([
        fetch('http://localhost:8000/api/check').then(res => res.json()),
        fetch('http://localhost:8000/api/process').then(res => res.json())
    ]);
    
    button.disabled = false;
    loading.classList.remove('show');
    const endTime = performance.now();
    const  processedTime = `処理時間: ${((endTime - startTime) / 1000).toFixed(2)}秒`;

    // 実行結果
    console.log(processedTime);
    console.log('API A結果:', resultA);
    console.log('API B結果:', resultB);
}
```


# 動作確認

本記事では `live-server` でWebサーバを立てます。
バックエンドのモックAPIは下記のコマンドでサーバを起動しておきます。

```
node server.js
Server running on http://localhost:8000
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1247619/033dd7fb-727b-68e5-e273-c91bd3c6b53f.png)


ボタンをクリックして処理時間を確認してみます。


- **直列処理ver：**
  約5秒（2秒 + 3秒）
```:response
Live reload enabled.
index.js:39 API A結果: {isAvailable: true}
index.js:40 API B結果: {message: 'hogeMessage'}
index.js:46 処理時間: 5.03秒
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1247619/f0c7deed-63c5-21fe-0d97-621afef4dc10.png)


- **並列処理ver：**
  約3秒

```:response
Live reload enabled.
index.js:16 API A結果: {isAvailable: true}
index.js:17 API B結果: {message: 'hogeMessage'}
index.js:23 処理時間: 3.00秒
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1247619/284a3f0e-bcf8-145a-29c1-40c92edd2eba.png)

APIを直列的に呼び出した場合と並列で呼び出した場合で、処理時間に明確な差が出ることを確認できました。


## 参考

- [MDN: Promise.all()](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Promise/all)