🐷

Promiseの静的メソッド:Promise.all

に公開

はじめに

今私は、JSの非同期処理を100%理解したいと思い、以下の学習ロードマップを参考にして学習を進めています。
JSの非同期処理を理解するために必要だった知識と学習ロードマップ

この記事に

複数の Promise を扱うことのできる静的メソッドを学習することで単純な async/await でやるよりも効率的に目的をはたせることを理解できれば体感できるはずです。

とあったので、実際に体験したいと思います。今回は、Promise.allに特化して実践してきます。

Promise.allとは

Promise.all() は複数の Promise をまとめて扱うための静的メソッドです。

🔹 受け取るもの

Promise インスタンスの配列

Promise.all([promise1, promise2, promise3])

🔹 返すもの

新しい Promise インスタンス

🔹 Fulfilled になる条件

配列内の**すべての Promise が Fulfilled(成功)**したとき

🔹 Rejected になる条件

配列内に**1つでも Rejected(失敗)**した Promise があるとき
👉 最初に reject された理由が返され、それ以降の結果は無視されます

🔹 .then() のコールバックに渡される値

Promise.all([p1, p2, p3]).then(results => {
  // results は [p1の結果, p2の結果, p3の結果]
});

🔹どういうシチュエーションで使用するのか

  • Resource AとB、順番関係なく取得していい場合は、Promise.allでは同時に取得できるため早い時間で処理が完了できる。

使ってみた

今回は、料理ができた順番に表示させていく処理を作りました。

document.addEventListener("DOMContentLoaded", () => {
    const button = document.querySelector("button");

    const makeStew = () => {
        return new Promise((resolve) => {
            setTimeout(() => {
                console.log("ビーフシチュー完成です");
                resolve("ビーフシチュー");
            }, 5000);
        });
    };

    const makeSalad = () => {
        return new Promise((resolve) => {
            setTimeout(() => {
                console.log("ポテトサラダ完成です");
                resolve("ポテトサラダ");
            }, 3000);
        });
    };

    const makeBread = () => {
        return new Promise((resolve) => {
            setTimeout(() => {
                console.log("パン完成です");
                resolve("パン");
            }, 1000);
        });
    };

    const main = () => {
        return Promise.all([makeStew(), makeSalad(), makeBread()]).then((results) => {
            const menu_box = document.getElementById("menu");
            const element = document.createElement("p");
            element.textContent = results.join("・");
            menu_box.appendChild(element);
        });
    };

    button.addEventListener("click", () => {
        main();
    });
});

直列処理と比べてみた

.then()チェーン

    const main = () => {
        let results = []
        return makeStew().then( value => {
            results.push(value);
            return makeSalad().then(value => {
                results.push(value);
                return makeBread().then(value => {
                    results.push(value);
                    const menu_box = document.getElementById("menu");
                    const element = document.createElement("p");
                    element.textContent = results.join('・');
                    menu_box.appendChild(element);
                })
            })
        })
    };

async/await

    const main = async () => {
        let results = []
        const stew = await makeStew();
        const salad = await makeSalad();
        const bread = await makeBread();

        results.push(stew, salad, bread);
        
        const menu_box = document.getElementById("menu");
        const element = document.createElement("p");
        element.textContent = results.join("・");
        menu_box.appendChild(element);
    };

気づき

出力を見ても分かる通り、直列処理では前の処理が終わってから次の処理が行われるため、1番作るのに時間がかかるシチューの完成を待たなければならなくなる。一方、Promise.allでは、同時に処理が行われるため、1番早く終わる料理から完成していく。直列処理では、すべての料理が完成するのに9秒かかっているが、Promise.allで処理をすると5秒で処理を終えることができる。これはとても大きい差だ。

rejectの時の挙動の違いを調べてみた

Promise.allの場合

ポテトサラダを作るのが失敗している。
エラー処理を追加

document.addEventListener("DOMContentLoaded", () => {
    const button = document.querySelector("button");

    const makeStew = () => {
        return new Promise((resolve) => {
            setTimeout(() => {
                console.log("ビーフシチュー完成です");
                resolve("ビーフシチュー");
            }, 5000);
        });
    };

    const makeSalad = () => {
        return new Promise((resolve, reject) => {
            setTimeout(() => {
                console.log("ポテトサラダ完成です");
                reject("ポテトサラダ失敗しました");
            }, 3000);
        });
    };

    const makeBread = () => {
        return new Promise((resolve) => {
            setTimeout(() => {
                console.log("パン完成です");
                resolve("パン");
            }, 1000);
        });
    };

    const main = () => {
        const menu_box = document.getElementById("menu");
        const element = document.createElement("p");
        return Promise.all([makeStew(), makeSalad(), makeBread()]).then((results) => {
            element.textContent = results.join("・");
            menu_box.appendChild(element);
        }).catch((err) => {
            element.textContent = err;
            menu_box.appendChild(element);
        });
    };

    button.addEventListener("click", () => {
        main();
    });
});

ポテトサラダの処理が失敗に終わると、catchに移りエラーハンドリングが行われている。

まとめ

複数の処理を扱う時、それらの結果を取得する順序を重要としない場合、Promise.allを使用すると同時に複数の処理が進行するためより早く処理を終えることができる。並列処理が実現できる。
async/awaitの方がいつも良いという考えは間違っているということに気づくことができました。

Discussion