JavaScript Promise

この章の内容を学ぶ前に、非同期プログラミングとは何かを理解しておく必要があります。参考:JavaScript 非同期プログラミング

Promise は ECMAScript 6 で提供されたクラスで、複雑な非同期タスクをよりエレガントに記述することを目的としています。

Promise は JavaScript で非同期操作を処理するためのオブジェクトであり、非同期操作の最終的な完了(または失敗)とその結果値を表します。

簡単に言うと、Promise は「約束」であり、将来のある時点で結果(成功の結果かもしれないし、失敗の理由かもしれない)を返すことを表します。

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

  • pending:初期状態。成功でも失敗でもない状態
  • fulfilled:操作が正常に完了したことを意味します
  • rejected:操作が失敗したことを意味します

例

const myPromise = new Promise((resolve, reject) => {
  // 异步操作代码
 
  if (/* 操作成功 */) {
    resolve('成功した結果'); // Promise の状態を fulfilled に変更する
  } else {
    reject('失敗の原因'); // Promise の状態を rejected に変更する
  }
});

ブラウザサポート

Promise は ES6 で新しく追加されたため、一部の古いブラウザはサポートしていません。Apple の Safari 10 と Windows の Edge 14 以降のブラウザで ES6 機能をサポートし始めました。

以下は Promise のブラウザサポート状況です:

Chrome 58 Edge 14 Firefox 54 Safari 10 Opera 55

Promise の使用方法

then() メソッド

then()このメソッドは、Promise の状態が fulfilled または rejected に変わったときのコールバック関数を指定するために使用します。

例

myPromise.then(
  (result) => {
    // 处理成功情况
    console.log('成功:', result);
  },
  (error) => {
    // 处理失败情况
    console.error('失败:', error);
  }
);

catch() メソッド

catch()このメソッドは、Promise が拒否された場合を処理することに特化しています。

例

myPromise
  .then((result) => {
    console.log('成功:', result);
  })
  .catch((error) => {
    console.error('失败:', error);
  });

finally() メソッド

finally()このメソッドは、Promise の最終的な状態に関係なく実行されます。

例

myPromise
  .then((result) => {
    console.log('成功:', result);
  })
  .catch((error) => {
    console.error('失败:', error);
  })
  .finally(() => {
    console.log('操作完成');
  });

Promise のチェーン呼び出し

Promise の強力な特徴の1つは、複数の非同期操作をチェーン呼び出しできることです。

例

doFirstThing()
  .then((result) => doSecondThing(result))
  .then((newResult) => doThirdThing(newResult))
  .then((finalResult) => {
    console.log('最终结果:', finalResult);
  })
  .catch((error) => {
    console.error('链中某处出错:', error);
  });

Promise の静的メソッド

Promise.all()

すべての Promise が完了するか、任意の Promise が失敗するまで待機します。

例

Promise.all([promise1, promise2, promise3])
  .then((results) => {
    // results はすべての Promise 結果を含む配列です
    console.log(results);
  })
  .catch((error) => {
    // いずれかの Promise が失敗するとここに入る
    console.error(error);
  });

Promise.race()

最初に完了した(成功か失敗かを問わず)Promise の結果を返します。

例

Promise.race([promise1, promise2, promise3])
  .then((result) => {
    // 最初に完了した Promise の結果を使用する
    console.log(result);
  })
  .catch((error) => {
    // 最初に完了した Promise が失敗した場合
    console.error(error);
  });

Promise.resolve() と Promise.reject()

解決済みまたは拒否済みの Promise をすばやく作成します。

例

const resolvedPromise = Promise.resolve('即座に解決される値');
const rejectedPromise = Promise.reject('即時拒否の理由');

実際の応用例

例 1: Promise を使用した AJAX リクエストの処理

例

function fetchData(url) {
  return new Promise((resolve, reject) => {
    const xhr = new XMLHttpRequest();
    xhr.open('GET', url);
    xhr.onload = () => {
      if (xhr.status === 200) {
        resolve(xhr.response);
      } else {
        reject(new Error(xhr.statusText));
      }
    };
    xhr.onerror = () => reject(new Error('网络错误'));
    xhr.send();
  });
}

fetchData('https://api.example.com/data')
  .then((data) => {
    console.log('获取数据成功:', data);
  })
  .catch((error) => {
    console.error('获取数据失败:', error);
  });

例 2: Promise チェーンを使用した複数の非同期操作の処理

例

function getUser(userId) {
  return fetch(`/users/${userId}`);
}

function getPosts(userId) {
  return fetch(`/users/${userId}/posts`);
}

getUser(123)
  .then((user) => {
    console.log('获取用户信息:', user);
    return getPosts(user.id);
  })
  .then((posts) => {
    console.log('获取用户帖子:', posts);
  })
  .catch((error) => {
    console.error('操作失败:', error);
  });

よくある質問とベストプラクティス

1. Promise のネストを避ける

間違ったやり方(コールバック地獄の Promise 版):

例

doFirstThing().then((firstResult) => {
  doSecondThing(firstResult).then((secondResult) => {
    doThirdThing(secondResult).then((thirdResult) => {
      console.log(thirdResult);
    });
  });
});

正しいやり方(チェーン呼び出しを使用):

例

doFirstThing()
  .then(doSecondThing)
  .then(doThirdThing)
  .then((finalResult) => {
    console.log(finalResult);
  });

2. 拒否(reject)を常に処理する

Promise の拒否を処理し忘れると、「未捕捉の Promise 拒否」エラーが発生します。常に使用し、.catch() 或 try/catch(async/await では)エラーを処理してください。

3. Promise はキャンセルできない

Promise は一度作成するとキャンセルできません。キャンセル機能が必要な場合は、AbortController や他のパターンの使用を検討できます。

4. よくある質問(FAQ)に答える

Q: then、catch、finally の順序は入れ替えられますか?

A: できます。効果はまったく同じです。ただし、お勧めしません。then-catch-finally の順序でプログラムを書くのが最善です。

Q: then ブロック以外の2つのブロックは複数回使用できますか?

A: できます。finally は then と同じように順番に実行されますが、catch ブロックは、catch ブロック内で例外が発生しない限り、最初の1つだけが実行されます。そのため、catch と finally ブロックはそれぞれ1つだけ配置するのが最善です。

Q: then ブロックを中断するにはどうすればよいですか?

A: then ブロックはデフォルトで下へ順に実行されます。return では中断できません。throw を使用して catch にジャンプすることで中断できます。

Q: 従来のコールバック関数ではなく Promise を使うべきなのはどのような場合ですか?

A: 複数の非同期操作を順番に実行する必要がある場合です。例えば、非同期メソッドでユーザー名とパスワードを順番に検証したい場合、まず非同期でユーザー名を検証し、次に非同期でパスワードを検証する必要があるケースは Promise に適しています。

Q: Promise は非同期を同期に変換する方法ですか?

A: まったく違います。Promise は単により良いプログラミングスタイルにすぎません。

Q: 現在の then で続けて書くのではなく、もう1つ then を書く必要があるのはどのような場合ですか?

A: 再び非同期タスクを呼び出す必要があるときです。


Promise と async/await

ES2017 で async/await が導入されました。これは Promise に基づいており、非同期操作を処理するためのより直感的な構文を提供します。

例

async function fetchData() {
  try {
    const user = await getUser(123);
    const posts = await getPosts(user.id);
    console.log('用户帖子:', posts);
  } catch (error) {
    console.error('获取数据失败:', error);
  }
}

async/await の方が読みやすいですが、Promise は async/await の基礎であるため、その基本概念を理解することが依然として重要です。

その他の拡張