JavaScriptの非同期処理をしっかり理解する　〜async/await/Promise〜

JavaScript での非同期処理について、身近な例や具体例を交えながら詳細に解説しています。
最後には練習問題も用意しています！

頑張って書いているので、良いと思ったらコメント・いいね・ストック・共有などしてもらえると嬉しいです！！

# 非同期処理とは

> 非同期処理とは、プログラムの処理が順番に実行されず、ある処理を実行している間に他の処理を並行して実行することができる仕組みです。非同期処理では、あるタスクが完了するのを待たずに次のタスクが実行されるため、効率的に複数の処理を進めることが可能です。

（by ChatGPT）

非同期処理とは、「同期処理」の対義語で、同期処理は「プログラムの処理が順番に実行され、ある処理が終わるまで次の処理を待つ仕組み」です。

非同期処理の最大のメリットは、
**「時間のかかる処理を行っている間に別の処理を行うことができる」** 
ことです！

身近な例では、部下への仕事の配分がわかりやすいです。

同期処理では、あなたがずっと仕事をしていて部下は動いておらず、時間がかかります。
![1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2601515/ee95323c-ad76-3335-7152-c3a775e4f19d.png)

非同期処理では、あなたは指示を出して部下の仕事を待って、最後に確認するだけなので早く終わります！（この上司は待っているだけなのでサボっていますが、実際には別の仕事もできます）
![2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2601515/6499b208-ec19-84a8-7fed-6a3e6496c469.png)

プログラムも同じです。
非同期処理を使って、時間のかかる処理を部下に押し付けてしまいましょう！



# JavaScript における非同期処理
javaScriptでの例を示します。

例えば、以下のように1〜1000000の和を求める処理と1〜10000の積を求める処理を行いたいとします。（実際にはこのようなケースでは非同期処理は適していませんが、説明のため。）

```javascript
// 1〜1000000の和を求める
let sum = 0; 
for(let i = 1; i <= 1000000; i++) {
    sum += i;
}
console.log(sum);

// 1〜10000の積を求める
let product = 1;
for(let i = 1; i <= 10000; i++){
    product *= i;
}
console.log(product);
```

このコードでは、1〜1000000の和を求めている間に他の処理ができません。そこで非同期処理です！


```javascript
function sumAsync() {
    // メインの処理を邪魔せずに別のところで和を求める関数
}

function productAsync() {
    // メインの処理を邪魔せずに別のところで積を求める関数
}

// sumAsync() と productAsync() が両方終わるまで待って、終わったらコンソールに出力する

```
このように書けたらいいわけです！


ちゃんと書くとこんな感じです！（理解しなくてOKです！）

```javascript
// 1〜1000000の和を求める関数
function sumAsync() {
  return new Promise((resolve) => {
    let sum = 0; 
    for(let i = 1; i <= 10000; i++) {
        sum += i;
    }
    resolve(sum);
  });
}

// 1〜10000の積を求める関数
function productAsync() {
  return new Promise((resolve) => {
    let product = 1;
    for(let i = 1; i <= 100; i++){
        product *= i;
    }
    resolve(product);
  });
}

// 和と積を別々に実装し、両方が終わるまで待ってから出力する。
Promise.all([sumAsync(), productAsync()]).then(([sum, product]) => {
  console.log("Sum:", sum);
  console.log("Product:", product);
});

```

## Promise について
上のコードで `Promise` が出てきました。
これは非同期処理をしっかり理解するには重要な概念です。

まず、MDNを引用します。

> プロミス (Promise) は、作成された時点では分からなくてもよい値へのプロキシーです。非同期のアクションの成功値または失敗理由にハンドラーを結びつけることができます。これにより、非同期メソッドは結果の値を返す代わりに、未来のある時点で値を提供するプロミスを返すことで、同期メソッドと同じように値を返すことができるようになります。

https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Promise

意味がわかりませんね。

簡単に言うと、 `Promise` とは「非同期で処理を行って完了したら値を返すよ」という **約束** をしているオブジェクトです。将来、値を返すので、別の言語では `Future` という言葉になっていることもあります。

最もシンプルに書くとこんな感じです。
```javascript
const calculateSum = new Promise((resolve) => {
    const result = 1 + 1; // 実際は時間がかかる処理
    resolve(result);  // 計算できたら計算結果を返すよ〜
});
```

`Promise` はオブジェクトなので `new` を使って宣言します。

`new Promise()` の括弧内には関数が入ります。
関数の第一引数は `resolve` （解決）と言うコールバック関数です。
（※「コールバック関数」が何か分からなくても一旦スルーしてください）

処理が終わって `Promise` で返したい値が完成した時に、 `resolve` の引数にその値を入れて呼ぶことで、値をきちんと **解決** したものとして返すことができます。

その `Promise` で解決された値の受け取り方はいくつかありますが、今回は現在主流の `async/await` について説明します。

※後ほど、エラーが起きた時の `reject` と `then`, `catch` について解説します。


## async/await について
先ほどの `calculateSum` には何が入っているでしょうか？
実は、`1+1` の計算結果ではないんです。

```javascript
const calculateSum = new Promise((resolve) => {
    const result = 1 + 1; // 実際は時間がかかる処理
    resolve(result);  // 計算できたら計算結果を返すよ〜
});

console.log(calculateSum);
```

このように実行しても、2は返ってきません。
`calculateSum` は `1+1` の結果ではなく、 `1+1` の結果を将来返すことを約束しているオブジェクトです。
なので、この場合 `Promise` オブジェクトがコンソールに出力されます。

これを受け取りたい時に、 `await` が出てきます。

```javascript
const calculateSum = new Promise((resolve) => {
    const result = 1 + 1; // 実際は時間がかかる処理
    resolve(result);  // 計算できたら計算結果を返すよ〜
});

console.log(await calculateSum);
```

こうすることで、ようやく計算結果の `2` をコンソールに出力できます。

この `await` とはなんでしょうか？

`await` の英語的な意味は「〜を待つ」です。（`wait` は自動詞なので何かを待つには `wait for` としないといけないので他動詞の `await` にしているのだと思います。）

つまり、「 `calculateSum` の処理が完了するのを待ってその結果を受け取る」という意味になります。

どんなに早く終わる処理でも、 `await` を使うなどして結果を受け取りたいと明示しないと結果は受け取れず、逆に `await` を使えばどんなに時間がかかる処理でもそこで止まって解決を待ちます。（限界はありますがイメージです）


`await` の結果を変数で受け取りたい場合はこうします。
```javascript
const calculateSum = new Promise((resolve) => {
    const result = 1 + 1; // 実際は時間がかかる処理
    resolve(result);  // 計算できたら計算結果を返すよ〜
});

const calculateSumResult = await calculateSum;
```

この時、 
- `calculateSum` は結果（2）を将来返すことを約束している `Promise` オブジェクト
- `calculateSumResult` はそれが解決された結果返された値（2）
と全くの別物であることに気をつけてください。

### 非同期処理を待っている間に別の処理を行う
ここまでの処理では、非同期処理を待っている間に何もしていません。これでは部下に働かせて自分は待っているだけの上司になってしまいます。

自分もちゃんと働きましょう！

```javascript
const heavyJob = new Promise((resolve) => {
    const result = doHeavyJob(); // 時間がかかる重たい処理
    resolve(result);  // 計算できたら計算結果を返すよ〜
});

const anotherHeavyJobResult = doAnotherHeavyJob(); // 時間がかかる別の処理

const heavyJobResult = await heavyJob;

console.log(heavyJobResult);
console.log(anotherHeavyJobResult);
```

このコードは、2つの時間がかかる処理を上司（メインスレッド）と部下（Promise）で分担しています。

ここで大事なことを説明します。
**`Promise` は、オブジェクトを作った時点で仕事（処理）が進み始めます。**

例えば、`doHeavyJob()`, `doAnotherHeavyJob()` がどちらも10秒かかる処理だったとします。
1行目の `const heavyJob = new Promise()` の時点で `doHeavyJob()` の処理が非同期で進み始め、その後すぐに `doAnotherHeavyJob()` が同期で進み始めます。

`anotherHeavyJob` が終わる頃には `heavyJob` も終わるので、合計20秒の処理が10秒で終わります。
もし `heavyJob` の方が時間がかかっていたら `await` によって終わるまで待ち、 逆に `heavyJob` の方が先に終わっていたら `await` した瞬間に結果が返ってきます。

こうして、上司と部下で仕事を分担しつつ、両方の処理が終わってから次の処理へ進めるのです。


### じゃあ、asyncとは
ここまでのコードを実行してみたら「あれ、動かないぞ？」と思った方はいませんか？
実は、 `await` は使える場所が限られています。

それが、 **`async` 関数の内部** です。（厳密にはES Modulesのモジュールトップレベルでも使えます。）

`async` は、 `asynchronous` （「非同期の」という形容詞）の略で、関数につけることで「非同期の関数だよ」と宣言することになります。

関数の前に以下のようにつけることができます。
```js
// アロー関数の場合
const asyncFunction = async () => {
    const result = await new Promise((resolve) => resolve(1 + 1));
    return result;
};

// function宣言の場合
async function asyncFunction() {
    const result = await new Promise((resolve) => resolve(1 + 1));
    return result;
}

// オブジェクトやclassのメソッドの場合
const obj = {
    async asyncMethod() {
        const result = await new Promise((resolve) => resolve(1 + 1));
        return result;
    }
};

// 即時実行関数式（IIFE）の場合
(async () => {
    const result = await new Promise((resolve) => resolve(1 + 1));
    console.log(result);
})();
```

:::note
コメントいただきましたので補足します。

実際のユースケースではこのケースでは `Promise` を使うのは冗長なので、以下のように書く方がベターです。
```js
// アロー関数の場合
const asyncFunction = async () => 1 + 1;

// function宣言の場合
async function asyncFunction() {
    return 1 + 1;
}

// オブジェクトやclassのメソッドの場合
const obj = {
    async asyncMethod() {
        return 1 + 1;
    }
};
```
※`Promise` と `async/await` の繋がりを理解してもらうためにあえて上記の書き方をしています。

:::


`async` の役割は主に2つです。

1. 関数内で `await` を使えるようにする
2. 返り値を `Promise` でラップする（返り値が `Promise` になる）

つまり、上の関数の返り値は、2ではなく、 `1+1` の結果を将来返す `Promise` になります。

```js
const asyncFunction = async () => {
    const result = await new Promise((resolve) => resolve(1 + 1));
    return result;
};

const anotherAsyncFunction = async () => {
    const asyncFunctionPromise = asyncFunction(); // この時点ではまだ Promise
    const asyncFunctionResult = await asyncFunctionPromise; // ここで 1+1 の結果が返ってくる

    return asyncFunctionResult + 1;
    // ここで3を返すが、この関数自体も非同期関数なので、別の関数で await をするまでは Promise となる
};
```

このように、`async` のついた関数の返り値は `Promise` になり、別の `async` のついた関数でその関数の結果を使いたい時は `await` でその `Promise` の解決を待ちます。

### 複数の仕事が全て終わってから次に進む（Promise.all）
自分で仕事をするのではなく、全て部下にやって欲しいですよね。

でも、全部終わってから次に進みたい。
そんな時に役立つのが、 `Promise.all()` です。

```js:promiseAll.js
const promise1 = new Promise((resolve) => {
    resolve(1 + 1);
});

const promise2 = new Promise((resolve) => {
    resolve(2 + 2);
});

const promise3 = new Promise((resolve) => {
    resolve(3 + 3);
});

const calculateAll = async () {
    const results = await Promise.all([promise1, promise2, promise3]); // 全てのPromiseが解決するまで待つ
    console.log(results);  // [2, 4, 6] と表示される
}
```

複数の `Promise` の配列を `Promise.all()` に引数で与えて、その前に `await` をつけることで、配列内の全ての `Promise` が解決するまで待ってから次の処理に移ることができます。

これで、仕事を全て部下に丸投げしても終わってから次に進めますね！

## 実際にどう使うか
ここまでで、 `Promise` `async` `await` について大体説明できました。
じゃあ、実際にどう使うのか、ここで解説していきます。

### fetch

```js:fetch.js
const fetchData = async () => {
    const response = await fetch('https://api.example.com/data');
    const data = await response.json();
    console.log(data);
}
```

`fetch` は、ネットワーク等を介して、特定のURI（URL等）にHTTPアクセスを飛ばす関数です。（厳密にいうと色々ありますが）

これは非同期処理で実装されており、 `Promise` を返します。

ちゃんとしたWebアプリケーションを実装するとほぼ必ずAPIへのアクセスを使うことになるので、そういう時には `await` を使ってHTTPアクセスが終わるのを待ってからそのデータを活用しましょう。

:::note
本番ではきちんと try catch を使って例外処理を行いましょう
:::

### sleep
ゲームなどいくつかのwebアプリでは、次の処理まで何秒か待ちたい時があります。

そういう時に、以下のように書くのは良くありません。
```js:busyWait.js
const busyWait = (ms) => {
    const start = Date.now();
    while (Date.now() - start < ms) {
        // ここでCPUを使い続ける
    }
}

const main = () => {
    doSomething();    // 何かをする
    busyWait(2000);   // 2000ミリ秒（2秒）待つ
    doAnotherthing(); // 別のことをする
}
```
while文に入っている間に他の処理がブロックされ、さらにCPUなども多く使ってしまいます。

そんな時は `Promise` を使ってこう書きましょう、
```js:sleep.js
const sleep = async (ms) => {
    return new Promise(resolve => setTimeout(resolve, ms));
}

const main = async () => {
    doSomething();     // 何かをする
    await sleep(2000); // 2000ミリ秒（2秒）待つ
    doAnotherthing();  // 別のことをする
}
```

:::note
`setTimeout` 関数は、2つ目の引数のミリ秒後に1つ目の引数の関数を実行する関数です。
与えられた `ms` ミリ秒後に `resolve` を実行することになるので、指定したミリ秒だけ非同期で待つことができます。
:::

:::note warn
`setTimeout` は正確にその秒数待ちたい場合には向いていません。状況に応じてブレが出ることもありますし、1ミリ秒単位の正確性はそもそもありません。
:::

:::note
コメントいただきましたので補足します。
`sleep` 関数はさらに簡潔に以下のように書くことも可能です。
```js
const sleep = ms => new Promise(resolve => setTimeout(resolve, ms));
```
:::

シンプルな上にメインスレッドに負担をかけない書き方ができます。


### DBやファイルへのアクセス

サーバーサイドのNode.jsでの活用ですが、DBやファイルへのアクセスでも `async/await` は活用されることがあります。

DB（MySQL）からの読み取り
```js:mysql.js
const mysql = require('mysql2/promise');

async function fetchFromDatabase() {
    const connection = await mysql.createConnection({
        host: 'localhost',
        user: 'root',
        database: 'test'
    });

    const [rows, fields] = await connection.execute('SELECT * FROM users');
    console.log(rows);
    connection.end();
}
```


ファイルの読み込み
```js:readFile.js
const fs = require('fs').promises;

async function readFile() {
    const data = await fs.readFile('example.txt', 'utf8');
    console.log(data);
}
```


:::note
本番ではきちんと try catch を使って例外処理を行いましょう
:::

## then, catch, rejectについて
ここまであえて説明していない事項についての説明です。

### then
実は `async/await` は比較的新しい記法で、以前は `then` を使っていました。（今でも使えます）

```js:then.js
doSomething()
    .then(result => doSomethingElse(result))
    .then(newResult => doMore(newResult))
    .then(finalResult => console.log(finalResult));
```
`then` を使うと、 `Promise` の `resolve` された値が次の `then` の関数の第一引数になり、これをチェーンで繋げていけます。

具体的に書くと、
```js:addNumbers.js
const addNumbers = (a, b) => {
    return new Promise((resolve) => {
        resolve(a + b);
    });
}

addNumbers(1, 2)
    .then(result => { // addNumbers(1, 2)の resolveの引数がresultに入る
        console.log('First result:', result);  // 1 + 2 の結果を表示
        return addNumbers(result, 3);  // 先の結果に3を足す
    })
    .then(newResult => { // addNumbers(result, 3)の resolveの引数がnewResultに入る
        console.log('Second result:', newResult);  // (1 + 2) + 3 の結果を表示
        return addNumbers(newResult, 4);  // さらに4を足す
    })
    .then(finalResult => { //  addNumbers(newResult, 4)の resolveの引数がfinalResultに入る
        console.log('Final result:', finalResult);  // (1 + 2 + 3) + 4 の結果を表示
    });
```

このコードの結果は以下のようになります。
```
First result: 3
Second result: 6
Final result: 10
```

同じコードを `async/await` で書くとこうなります。
```js:addNumbersAsync.js
const addNumbers = (a, b) => {
    return new Promise((resolve) => {
        resolve(a + b);
    });
};

const calculate = async () => {
    const result = await addNumbers(1, 2);
    console.log('First result:', result);  // 1 + 2 の結果を表示

    const newResult = await addNumbers(result, 3);
    console.log('Second result:', newResult);  // (1 + 2) + 3 の結果を表示

    const finalResult = await addNumbers(newResult, 4);
    console.log('Final result:', finalResult);  // (1 + 2 + 3) + 4 の結果を表示
};

calculate();
```

:::note
コメントいただきましたので補足します。

`async/await` を使うならば、 `addNumbers` 関数もこのように書く方がベターです。
```js
const addNumbers = async (a, b) => a + b;
```
:::

この方がわかりやすいとは思いませんか？
現在では同期の表記に近いため書きやすく分かりやすい `async/await` が主流というわけです。

:::note
ちなみに、 `then` より前（`Promise` ができる前）はコールバック関数で全て管理していました。

```js:callbackHell.js
doSomething(function(result) {
    doSomethingElse(result, function(newResult) {
        doMore(newResult, function(finalResult) {
            console.log(finalResult);
        });
    });
});
```

このように何重にもネストしたコールバック関数たちは、 **コールバック地獄** と呼ばれており、読みにくく書きにくくてエラーの温床となってしまい、大きな問題でした。
:::

### catch, reject について
`Promise` の中でのエラーハンドリングを行うために、 `reject` と `catch` があります。

例えば、先ほどの `addNumbers` で 負の数があったらエラーにしたいとします。
すると、以下のように書きます。

```js:addNumbersReject.js
const addNumbers = (a, b) => {
    return new Promise((resolve, reject) => {
        if (a < 0 || b < 0) {
            reject('Error: Negative numbers are not allowed');
        } else {
            resolve(a + b);
        }
    });
};

addNumbers(1, 2)
    .then(result => {
        console.log('First result:', result);  // 1 + 2 の結果を表示
        return addNumbers(result, -3);  // ここで負の数を渡す。するとcatchまで処理が飛ぶ。
    })
    .then(newResult => {
        console.log('Second result:', newResult);  // (1 + 2) - 3 の結果
        return addNumbers(newResult, 4);
    })
    .then(finalResult => {
        console.log('Final result:', finalResult);  // (1 + 2 - 3) + 4 の結果
    })
    .catch(error => {
        console.error(error);  // エラーが発生した場合の処理
        // ここでは 'Error: Negative numbers are not allowed' とコンソールに表示される。
    });

```

`Promise` に渡す関数の第二引数の `reject` に注目です。
エラーが起きたりして値を **解決** できなかった場合には `resolve()` の代わりに `reject()` を呼び出します。
これによって、エラーが起きたことを伝えるとともに、エラーの内容を `reject()` の引数に入れて伝えます。

`then` のチェーンの中でエラーが起きて `reject()` が呼ばれた時、その後の `then` を飛ばして `catch` まで処理が飛びます。
この `catch` に与えられる関数の第一引数で `reject()` の引数の値を受け取り、それを元にエラー時の処理を行います。

この処理を `async/await` で書くとこうなります。

```js:addNumbersRejectAsync.js
const addNumbers = (a, b) => {
    return new Promise((resolve, reject) => {
        if (a < 0 || b < 0) {
            reject('Error: Negative numbers are not allowed');
        } else {
            resolve(a + b);
        }
    });
};

const calculate = async () => {
    try {
        const result = await addNumbers(1, 2);
        console.log('First result:', result);  // 1 + 2 の結果を表示

        const newResult = await addNumbers(result, -3);  // ここで負の数を渡す。するとcatchまで処理が飛ぶ。
        console.log('Second result:', newResult);  // (1 + 2) - 3 の結果

        const finalResult = await addNumbers(newResult, 4);
        console.log('Final result:', finalResult);  // (1 + 2 - 3) + 4 の結果
    } catch (error) {
        console.error(error);  // エラーが発生した場合の処理
        // ここでは 'Error: Negative numbers are not allowed' とコンソールに表示される。
    }
};

calculate();
```

`async/await` だとこのように同期処理と同じ記法で書けるわけです。


# 練習問題

ここまでを振り返って問題を解いてみましょう。
解けたらぜひ、コメント欄やXで教えてください！


## 問題1
**非同期処理** とは何か、「上司」と「部下」という言葉を使って身近な例に当てはめて説明しなさい。

## 問題2
以下のコードの明らかな誤りを訂正しなさい。
```js:wrong.js
const fetchData = () => {
    return new Promise((resolve) => {
        setTimeout(() => {
            resolve({message: "Hello!"});
        }, 2000);  // 2秒後にデータが返ってくる
    });
};

const loadData = () => {
    const dataPromise = fetchData();
    console.log(dataPromise.message);
};

loadData();
```

## 問題3
以下のコードでコンソールのログはどの順番に並ぶか答えなさい。

```js
console.log("Start");

setTimeout(() => {
    console.log("Timeout");
}, 1000); // 非同期で1秒待つ

const promise = new Promise((resolve) => {
    resolve(1 + 1);
});

promise.then(() => {
    console.log("Promise resolved");
});

console.log("End");
```



## 問題4
`Promise` を使って非同期で `1+1` を計算する処理を何も見ずに書いてみなさい。

## 問題5
`promise` と `async/await` を使って指定したミリ秒の間待つsleep関数、及びそれを利用するサンプルコードを書いてみなさい。

## 問題6
2数の足し算を非同期で行う関数を作り、その関数を3個同時に呼び、その結果が全て出たらそれらの合計を返すコードを、 `Promise.all` を使って書きなさい



# ちょっと宣伝
Hi!story【ハイスト】チーム（株式会社Highsto）ではメンバーを募集しています。

`async/await` なども活用しながらwebやモバイルのアプリ、ゲームをたくさん作っています！

https://highsto.net/join-us/

もし興味があったら覗いてみてください！


Xのフォローもよろしくです！

https://x.com/zuisho_tech