JavaScriptにおけるPromiseの非同期操作管理

PromiseはECMAScript 2015(ES6)で導入されたクラス(new Promise)であり、JavaScriptにおける非同期プログラミングを管理するために設計されました。これにより、いわゆる「Promiseデザインパターン」が実現されています。

Promiseは非同期問題を解決するためのツールです。Promise自体は同期的ですが、非同期プログラミングを管理するためのパターンとして機能します。

簡単に言えば、Promiseはコンテナ(ラッパー)であり、その中には将来終了するイベント(通常は非同期操作)の結果が保存されています。

Promiseオブジェクトはコンストラクタ関数であり、resolve、reject、allといったメソッドを持ちます。プロトタイプ上にはthen、catchなどのメソッドが存在します。

Promiseには3つの状態があります:

  • pending:準備状態
  • fulfilled:成功状態(resolve)
  • rejected:失敗状態(reject)

最終的には2つの状態しかありません:1. 準備中 2. 成功または失敗

   const futureData = new Promise((resolve, reject) => {
        setTimeout(() => {
            const result = '北海道';
            resolve(result); // 成功時に値を返す
            // reject(new Error("エラーが発生しました")); // 失敗時にエラーを返す
        }, 1000);
   });
   
   futureData.then(
       data => {
           console.log(data);
       },
       error => {
           console.log('エラーが発生しました', error);
       }
   );
   /*
    1. 非同期操作はPromiseに渡された関数内に記述します
    2. Promiseの引数とthenの引数が対応しています
  */

Promiseオブジェクトの特徴

  1. Promiseオブジェクトの状態は外部からの影響を受けません。非同期操作の結果のみが現在の状態を決定し、他の操作ではこの状態を変更できません
  2. 一旦状態が変更されれば、それ以上は変化しません。Promiseオブジェクトの状態変更には2つの可能性しかありません:pendingからfulfilledへの変化、またはpendingからrejectedへの変化

Promiseのexecutor(実行関数)は、new Promiseが呼び出された際にデフォルトで即座に実行されます。

各Promiseインスタンスにはthenメソッドがあり、これを使ってresolved状態とrejected状態のコールバック関数を指定します。

thenメソッドは複数のチェーン(連鎖呼び出し)が可能です(ただし、各thenがPromiseインスタンスを返すことが条件です)。

    const fetchData = new Promise((resolve, reject) => {
    // 非同期操作を実行
    const xhr = new XMLHttpRequest();
    xhr.open('GET', 'data.json', true);
    xhr.onreadystatechange = function() {
        if(xhr.readyState === 4 && xhr.status === 200) {
            const response = xhr.responseText;
            resolve(response); // 成功
        }
        if(xhr.status !== 200) {
            reject(new Error("データ取得に失敗しました")); // 失敗
        }
    }
    xhr.send();
})

fetchData.then(
    data => {
        console.log('データ取得成功');
        // データ処理
        return data;
    },
    error => {
        console.log('エラーが発生しました', error);
    }
).then(() => {
    // 最初のthenの処理完了後に実行
    console.log('次の処理を実行');
}).then(() => {
     // 2番目のthenの処理完了後に実行
     console.log('最終処理を実行');
})

Promiseは複数の並行リクエストをサポートしており、並行処理中のデータを取得できます。

Promiseで取得したデータはnullまたは配列の可能性があります。配列の場合はthen()で処理し、nullの場合はthen()を実行する必要はありません。

Promise.prototype.catch()

このメソッドは.then(null, rejection)のエイリアスであり、エラー発生時のコールバック関数を指定するために使用します。

 fetch('/api/data.json')
  .then(response => response.json())
  .then(data => {
    // データ処理
    console.log(data);
  })
  .catch(error => {
    // getJSONと前のコールバック関数の実行中に発生するエラーを処理
     console.log('エラーが発生しました!', error);
  })

一般的に、thenメソッド内でreject状態のコールバック関数を定義するのは避けるべきです(つまり、thenの第二引数を使用しないでください)。

成功時の処理はthenで、エラー時の処理はcatchで行うのが推奨されます。

  // 非推奨
  somePromise
    .then(function(data) {
      // 成功時の処理
    }, function(err) {
      // エラー時の処理
    });

  // 推奨
  somePromise
    .then(function(data) { 
      // 成功時の処理
    })
    .catch(function(err) {
      // エラー時の処理
    });

Promise.all

Promise.allメソッドは、複数のPromiseインスタンスを新しいPromiseインスタンスでラップするために使用されます。返される各メンバーはPromiseインスタンスです。

すべてのPromiseが成功した場合にthenが実行され、いずれか一つでも失敗すればcatchが実行されます。

Promise.all([promise1, promise2, promise3])
  .then(results => {
    // すべてのPromiseが成功した場合
    console.log('すべての処理が成功しました', results);
  })
  .catch(error => {
    // 少なくとも一つのPromiseが失敗した場合
    console.log('一部の処理が失敗しました', error);
  });

タグ: javascript Promise 非同期プログラミング ES6 コールバック

7月13日 16:59 投稿