0
0
お題は不問!Qiita Engineer Festa 2024で記事投稿!
Qiita Engineer Festa20242024年7月17日まで開催中!

よくわからないPromiseとかいうやつを紐解く

Last updated at Posted at 2024-07-15

はじめに

JavaScriptの非同期処理について理解するときに、避けて通れないのが「Promise」です。

今まで雰囲気で何となく実装してきていたので、今回基礎からしっかり理解します。

Promiseとは

Promiseは非同期処理の完了、もしくは失敗を表すオブジェクトです。
処理の完了および失敗に対して、それぞれ処理を紐づけることができます。

非同期処理は結果を返すことができますが、即座に返すことができません。

その代わり、未来のある時点(処理の結果が確定し、成功/失敗が明らかになった時)で値を提供するオブジェクトを返すことで、同期処理と同じように値を返すことができるようになります。

この値を提供するオブジェクトがPromiseになります。

実際のコード例から、さらにPromiseを深堀りします。

補足:Promiseの状態

Promiseオブジェクトは、以下の3つの状態のいずれかを持ちます。

  1. pending(保留中): 初期状態。処理が完了していない状態
  2. fulfilled(成功): 処理が成功して完了した状態
  3. rejected(拒否): 処理が失敗して完了した状態

状態はpendingから始まり、fulfilledまたはrejectedに変化します。
一度確定したら状態が変更されることはありません。

実際にコードを書いていてこの状態を目にすることはありませんが、内部ではこの状態によって挙動が決まっているということは覚えておくとよいです。

Promiseを生成する

基本的に自身でPromiseを生成することはあまりないのですが、実際に処理を書くことでより詳しく中身を理解していきます。

function fetchUserData(userId) {
  return new Promise((resolve, reject) => {
    // APIリクエストをシミュレート
    setTimeout(() => {
      if (userId > 0) {
        // 成功: ユーザーデータを返す
        resolve({
          id: userId,
          name: `User ${userId}`,
          email: `user${userId}@example.com`,
        });
      } else {
        // 失敗: エラーを返す
        reject(new Error("Invalid user ID"));
      }
    }, 1000); // 1秒後に結果を返す
  });
}

fetchUserDataという処理がPromiseを返す関数となるよう設定しました。

コンストラクタ

Promiseの生成にはnewキーワードとPromiseコンストラクタの呼び出しが必要です。

executor関数

コンストラクタには引数として関数を設定します。
この関数はexecutor関数と呼ばれ、この関数内で非同期処理を実行します。

resolveとreject

executor関数は引数として2つの関数を設定します。
第一引数の関数は、非同期処理が成功した場合に呼び出す関数で、慣例的にresolveとすることが多いです。
第二引数の関数は、非同期処理が失敗した場合に呼び出す関数で、慣例的にrejectとすることが多いです。

fetchUserDataの解説

今回はexecutor関数の処理として、仮想のAPI処理を作成しています。
タイムアウトの後、ユーザーデータを返す処理を定義しました。

非同期処理の成功/失敗はuserIdの分岐で決定しています。
0より大きい場合には処理の成功として、resolveにユーザーデータのオブジェクトを設定しています。
それ以外の場合には処理の失敗として、rejectにエラーを設定しています。

この実装からわかること

返却されるPromiseオブジェクトは、処理の成功(resolve)と失敗(reject)のどちらの状態も保持しています。
まだこの時点で処理は実行されていないので、どちらになるかはわかりません。

しかし、成功時にはresolveの引数が、失敗時にはrejectの引数が値として設定されることになります。

このように、未来のある時点で値を提供するオブジェクトがPromiseです。

Promiseを利用する

生成したPromiseを利用する方法を改めて確認し、さらにPromiseについての理解を深めます。

const userData = fetchUserData(1);
userData
  .then((user) => {
    console.log("ユーザーデータを取得しました:", user);
  })
  .catch((error) => {
    console.error("エラーが発生しました:", error.message);
  });

userDataでPromiseオブジェクトを受け取ります。
Promiseオブジェクトにはthencatchfinallyのメソッドが用意されています。
finallyについては今回は割愛します。

then

引数として関数を設定します。
この関数はPromiseオブジェクトがresolveしたときに実行される処理となります。
戻り値として新しいPromiseオブジェクトを返します。

また、この関数は引数としてresolveに指定した値が設定されます。

補足:thenメソッドの第二引数

thenは実際には2つめの引数を設定することができます。

引数設定の例
promise.then(onFulfilled, onRejected)

onFulfilledは上記で解説したresolveしたときに実行される処理です。
onRejectedはrejectされたときに実行される処理となります。

これにより、1つのthenメソッドで成功と失敗の両方のケースを処理できます。
ただし、可読性の観点から、多くの場合は後述のcatchメソッドを使用してエラーハンドリングを行うことが推奨されます。

catch

引数として関数を設定します。
この関数はPromiseオブジェクトがrejectされたときに実行される処理となります。
こちらも戻り値として新しいPromiseオブジェクトを返します。

また、この関数は引数としてrejectに指定した値が設定されます。

この実装からわかること

実行結果の確定していないuserDataというPromiseオブジェクトに対して、rosolveしたときの処理をthenで、rejectされたときの処理をcatchで指定しています。

これが冒頭のPromiseとはで触れていた

処理の完了および失敗に対して、それぞれ処理を紐づける

ということになります。

補足:Promiseの連鎖

thenメソッドは新しいPromiseを返すため、複数の非同期処理を連鎖させることができます。

fetchUserData(1)
  .then(user => {
    console.log("ユーザーデータを取得しました:", user);
    return fetchUserPosts(user.id);  // 新しいPromiseを返す
  })
  .then(posts => {
    console.log("ユーザーの投稿を取得しました:", posts);
    return fetchComments(posts[0].id);  // さらに新しいPromiseを返す
  })
  .then(comments => {
    console.log("最初の投稿のコメントを取得しました:", comments);
  })
  .catch(error => {
    console.error("エラーが発生しました:", error.message);
  });

ポイントは、必ずthenメソッドの中でreturnを使って値を返すことです。
そうしないと、後続の処理で値を使えなくなってしまいます。

この方法により、複数の非同期操作を順序立てて実行し、各ステップの結果を次のステップで利用することができます。

なお、エラーは最後のcatchで一括して処理されます。

まとめ

Promiseについてその実態を理解しないまま、なんとなくthenchachを見様見真似で使ってきていましたが、その挙動を紐解くことで、何をしていたのかをようやく理解することができました。

非同期処理はまだまだ苦手意識があるので、こういったところから少しずつ克服していきたいと思いました。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0