JavaScriptのPromise

jsコード JavaScript

JavaScriptの非同期処理を扱う際、「Promise」を避けては通れないでしょう。
Promiseは、非同期操作の結果を管理し、複数の非同期操作を連鎖させるための強力なツールです。

従来のコールバック関数では、複雑な非同期処理を記述する際に「コールバック地獄(Callback Hell)」と呼ばれるコードの可読性や保守性の問題が発生していました。
Promiseはこの問題を解決し、より直感的で管理しやすい非同期コードの記述を可能にします。

本記事では、Promiseの基本概念から始まり、実際の使用例、エラーハンドリング、高度な使用方法までを詳しく解説します。
Promiseを理解することで、JavaScriptの非同期処理をより効果的に扱えるようになり、スムーズで効率的なコードを実現するための第一歩となるでしょう。

Promiseの基本概念

Promiseとは

Promiseは、非同期処理の結果を扱うためのオブジェクトであり、非同期処理が完了するまでの間の「約束」を表現します。
Promiseは次に挙げる3つの状態を持ち、これにより非同期処理の進行状況を追跡します。

  • Pending(保留中):
    • 非同期処理がまだ完了しておらず、結果が得られていない状態。
  • Fulfilled(成功):
    • 非同期処理が正常に完了し、結果が得られた状態。
  • Rejected(失敗):
    • 非同期処理が失敗し、エラーが発生した状態。

Promiseの使い方

Promiseの基本的な使い方は、以下のようにresolverejectメソッドを使用して非同期処理の完了状態を示します。

Promiseの作成

Promiseは、「new Promise」 構文を使って作成され、Promiseのコンストラクタには、resolvereject の2つのコールバック関数が渡されます。
この関数は、非同期処理が成功した場合に resolve を呼び出し失敗した場合に reject を呼び出します。

Promise作成のサンプル
const myPromise = new Promise((resolve, reject) => {
    // 非同期処理
    const success = true; // 👈成功例にするためtrueで設定しています。

    if (success) {
        resolve('データ取得に成功しました');
    } else {
        reject('データ取得に失敗しました');
    }
});

Promiseの使用

Promiseが作成されたら、thenメソッドとcatchメソッドを使って結果を処理します。

  • then メソッド:
    • Promiseがfulfilled(成功)状態になったときに実行されるコールバックを設定します。
  • catch メソッド:
    • Promiseがrejected(失敗)状態になったときに実行されるコールバックを設定します。
Promiseの使用サンプル
myPromise
    .then((result) => {
        console.log(result); // "データ取得に成功しました"
    })
    .catch((error) => {
        console.error(error); // "データ取得に失敗しました"
    });

myPromiseが解決された場合にthenメソッドのコールバックが実行され、拒否された場合にcatchメソッドのコールバックが実行されます。

今回のサンプルでは、Promiseの作成時に成功状態になるよう固定で「true」設定しているため、thenメソッドが実行されます。
const success = true; のコードを const success = false; に変更すると、catchメソッドが呼ばれます。

Promiseチェーンについて

Promiseチェーンとは、複数の非同期操作をドット「.」記号で繋げることにより、連続して実行する方法を指します。

Promiseチェーンを使用することで、各非同期操作が完了した後に次の操作を実行するよう設定できます。
これにより、複雑な非同期処理のフローをシンプルに管理でき、コードの可読性が向上します。

簡単なPromiseチェーンのサンプル

できるだけシンプルな中身で、Promiseチェーンを使った非同期処理の例を示します。
次のサンプルプログラムでは、データをフェッチし、その後フェッチしたデータを処理し、最終的に結果を保存するという処理をシミュレートしています。
※実際にフェッチ、データ処理、保存処理を実行しているわけではなく、メッセージを出力させているだけなのでシミュレートです。

Promiseチェーンのサンプル
// 非同期データフェッチ関数
function fetchData() {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            const success = true;
            if (success) {
                resolve('データ取得に成功しました');
            } else {
                reject('データ取得に失敗しました');
            }
        }, 1000);
    });
}

// 非同期データ処理関数
function processData(data) {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            const success = true;
            if (success) {
                resolve(`処理済みデータ: ${data}`);
            } else {
                reject('データ処理に失敗しました');
            }
        }, 1000);
    });
}

// 非同期データ保存関数
function saveData(data) {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            const success = true; // これはデータ保存の結果をシミュレートしたものです
            if (success) {
                resolve(`保存済みデータ: ${data}`);
            } else {
                reject('データ保存に失敗しました');
            }
        }, 1000);
    });
}

// Promiseチェーンの実例
fetchData()
    .then(data => processData(data))
    .then(processedData => saveData(processedData))
    .then(savedData => {
        console.log(savedData); // "保存済みデータ: 処理済みデータ: データ取得に成功しました"
    })
    .catch(error => {
        console.error('Error:', error);
    });

Promiseチェーンの処理の流れ

作成したサンプルプログラムでは、次の順でPromiseチェーンが構成されています。

  1. fetchData 関数が呼び出され、データのフェッチが行われます。
  2. データのフェッチが成功すると、then メソッド内の processData 関数が呼び出され、フェッチしたデータを処理します。
  3. データの処理が成功すると、次の then メソッド内の saveData 関数が呼び出され、処理済みデータを保存します。
  4. 最終的に、保存が成功すると、保存済みデータがコンソールに出力されます。
  5. 途中でエラーが発生した場合は、catch メソッドが呼び出され、エラーメッセージがコンソールに出力されます。

Promiseチェーン以外で複数Promiseを処理する方法

複数のPromiseを処理する方法は、Promiseチェーンのみではありません。
この項では、Promise.allPromise.race、そして Promise.allSettled を紹介します。

この3つのメソッドには「引数にPromiseの配列を渡す」使用上の共通点があります。
これをまず覚えておきましょう。

Promise.all

Promise.all は、複数のPromiseを並行して実行し、すべてのPromiseが完了したときに一度に結果を処理する方法です。

複数の非同期操作を独立して実行し、全ての結果を一気に受け取りたい場合には非常に有用です。

Promise.allのサンプル
const promise1 = fetch('https://not.existing.com/data1').then(response => response.json());
const promise2 = fetch('https://not.existing.com/data2').then(response => response.json());
const promise3 = fetch('https://not.existing.com/data3').then(response => response.json());

Promise.all([promise1, promise2, promise3])
    .then(results => {
        console.log('All data fetched:', results);
    })
    .catch(error => {
        console.error('Error fetching data:', error);
    });

👆上記の例では、Promise.allに引数として渡した配列(promise1, promise2, promise3)の3つのPromiseが並行して実行され、すべてが成功した場合then ブロック内で結果が処理されます。

注意すべき点としては、どれか1つでも失敗すると、catch ブロック内でエラーが処理されことです。

Promise.race

Promise.race は、最初に解決または拒否されたPromiseの結果を取得する方法です。

複数の非同期操作のうち最も早く完了した処理の結果を扱いたい場合に便利です。

Promise.raceのサンプル
const promise1 = new Promise((resolve, reject) => {
    setTimeout(() => resolve('1st Promise resolved'), 1000);
});
const promise2 = new Promise((resolve, reject) => {
    setTimeout(() => resolve('2nd Promise resolved'), 500); // 👈最速
});
const promise3 = new Promise((resolve, reject) => {
    setTimeout(() => reject('3rd Promise rejected'), 800);
});

Promise.race([promise1, promise2, promise3])
    .then(result => {
        console.log('First completed:', result); // "2nd Promise resolved"
    })
    .catch(error => {
        console.error('First rejected:', error);
    });

👆上記の例では、promise2 が最も早く完了し、then ブロック内でその結果が処理されます。
他のPromiseが解決または拒否される前に、最初に完了したPromiseの結果だけを取得できます。

Promise.allSettled

Promise.allSettled は、すべてのPromiseが解決または拒否されるまで待ち、それぞれの結果を含むオブジェクトの配列を返します。

これは、Promise.all と似ている部分もありますが、Promise.all はどれか一つが失敗するとその時点で直ちにcatchブロックが実行され、エラー処理に移ってしまいます。

よって、Promise.allSettled は、各Promiseの結果(成否)に関係なく、すべてのPromiseが完了するのを待ちたい場合に有用です。

Promise.raceのサンプル
const promise1 = fetch('https://not.existing.com/data1').then(response => response.json());
const promise2 = fetch('https://not.existing.com/data2').then(response => response.json());
const promise3 = fetch('https://not.existing.com/data3').then(response => response.json());

Promise.allSettled([promise1, promise2, promise3])
    .then(results => {
        results.forEach((result, index) => {
            if (result.status === 'fulfilled') {
                console.log(`Promise ${index + 1} fulfilled:`, result.value);
            } else {
                console.log(`Promise ${index + 1} rejected:`, result.reason);
            }
        });
    });

👆上記の例では、promise1, promise2, promise3 のすべてのPromiseが解決または拒否されるまで待ち、それぞれの結果が配列として返されます。

各Promiseの状態と結果は、個別に処理されます。

Promiseのエラーハンドリング

Promiseでは、catch メソッドを使用することで、非同期操作中に発生したエラーをキャッチし、適切に処理することができます。

エラーハンドリング例

ここまでのサンプルプログラムでも使用してきましたが、改めてサンプルを2種類掲載します。

  • 基本的なエラーハンドリングサンプル
  • Promiseチェーンでのエラーハンドリングサンプル

Promiseのエラーハンドリングは、主に catch メソッドを使用して行います。catch メソッドは、Promiseが rejected 状態になったときに実行されるコールバック関数を登録します。

基本的なエラーハンドリングサンプル
function fetchData() {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            const success = false; // 失敗のシミュレーション
            if (success) {
                resolve('データ取得に成功しました');
            } else {
                reject('データ取得に失敗しました');
            }
        }, 1000);
    });
}

fetchData()
    .then(data => {
        console.log(data);
    })
    .catch(error => {
        console.error('Error:', error); // "Error: データ取得に失敗しました"
    });
Promiseチェーンでのエラーハンドリングサンプル
function fetchData() {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve('データ取得に成功しました');
        }, 1000);
    });
}

function processData(data) {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            reject('データ処理に失敗しました');
        }, 1000);
    });
}

fetchData()
    .then(data => {
        console.log(data); // "データ取得に成功しました"
        return processData(data);
    })
    .then(processedData => {
        console.log(processedData);
    })
    .catch(error => {
        console.error('Error:', error); // "Error: データ処理に失敗しました"
    });

Promiseチェーンを使用する際は、各 then メソッドで発生したエラーをキャッチするために catch メソッドを追加できます。
これにより、チェーン内のどの部分でエラーが発生しても、一貫してエラーハンドリングを行うことができます。

finally メソッド

Promiseには finally メソッドもあり、Promiseが解決されたか拒否されたかに関係なく、最終的に必ず実行されるコールバック関数を設定します。

finallyメソッドの使用サンプル
function fetchData() {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            reject('データ取得に失敗しました');
        }, 1000);
    });
}

fetchData()
    .then(data => {
        console.log(data);
    })
    .catch(error => {
        console.error('Error:', error);
    })
    .finally(() => {
        console.log('成否に関わらない必須の処理を実行!');
    });

👆上記の例では、fetchData が失敗した場合でも、finally メソッドのコールバックが実行され、finallyメソッドのコールバック関数が動作し、処理が行われます。

まとめ

この記事では、JavaScriptのPromiseについて詳しく解説しました。
Promiseは、非同期処理を効果的に管理するための強力なツールであり、複雑な非同期操作をシンプルに扱うことができます。

以下に、この記事で取り上げた主要なポイントをまとめます。

  1. Promiseの基本概念
    • Promiseは、非同期処理の結果を管理するオブジェクトであり、3つの状態(Pending、Fulfilled、Rejected)を持ちます。基本的な使用方法として、resolvereject メソッドを使って非同期操作の結果を処理しました。
  2. Promiseチェーン
    • 複数の非同期操作を連続して実行するためのPromiseチェーンについて解説しました。Promiseチェーンを使用することで、各非同期操作をシンプルに連結し、エラーハンドリングも一貫して行うことができます。
  3. Promiseチェーン以外で複数Promiseを処理する方法
    • Promise.allPromise.race など、複数のPromiseを並行処理する方法について説明しました。これにより、複数の非同期操作を効率的に管理し、処理時間の短縮や最初に完了した結果の取得が可能になります。
  4. Promiseのエラーハンドリング
    • 非同期処理中に発生するエラーを効果的に処理するためのエラーハンドリング方法について解説しました。catch メソッドや finally メソッドを使用することで、信頼性の高い非同期コードを実現できます。

Promiseを理解し、正しく利用することで、JavaScriptの非同期処理を効果的に管理することができます。

この記事が皆さんのJavaScriptの非同期処理の理解を深める一助となれば幸いです。


本記事についての質問、誤りの指摘、ご意見ご感想などありましたら、ぜひコメント頂ければ幸いです。

最後までお読みいただき、ありがとうございます。

『詳説 JavaScript』メニューに戻る

コメント

タイトルとURLをコピーしました