【もう迷わない】JavaScriptのPromise|基本とメソッドの使い分けをまとめて解説!

こんにちは!Reiです‎(˵ •̀ ᴗ – ˵) ✧


突然ですが、コード内にfetch() や async/await がやらたらと並んでいたり、Promise を扱っているけど何しているのか分かってなかったりしていませんか…?


その原因になりやすいのが、Promiseを扱えていないこと です。
Promiseは、JavaScriptの非同期処理を扱ううえでの土台になる存在。

だけど、Promiseをうまく扱えていないと…

  • エラー処理が適切に対処できてない
  • thencatch のつなぎ方が曖昧になる
  • 並列処理の書き分けで迷いやすくなる

といった状態に陥ってしまいます 💦


逆にPromiseを上手に扱えると、エラーが発生したときの処理を意図した場所にまとめて書けるようになり、同じような分岐や無駄なコードを増やさずに済むようになります。


\\ この記事で学べること //

・Promiseが何を表しているのか
then/catch/finally の役割の違い
all/allSettled/race any の選び分け
resolve/reject/withResolvers の意味

「なんとなく書いていた部分」をひとつずつ理解を深めていきましょう ᥫᩣ ̖́-


Promiseの考え方

ここからは、Promiseの考え方について理解していきます 👀

まずは、「Promiseがどんな役割を持っているのか」を知るところから始めていきましょう!


Promise は「 あとで結果が決まる値 」を扱う仕組み

Promiseは、まだ終わっていない処理の結果を表すオブジェクトです。


たとえば、API通信のような処理は、すぐに結果が返ってきません。

fetch("/api/user");

このような非同期の処理を書いた瞬間には、まだデータは取得できていない状態です。

そのためJavaScriptでは、
あとから結果を受け取る前提で処理を扱う必要があります。


そこで登場するのがPromiseです。

Promiseは、今は結果がなくても、
あとで成功するか失敗するかが決まる値を一時的に持っておくための仕組みです。


Promiseには3つの状態がある

Promiseには、次の3つの状態があります 👀

  • pending → まだ結果が決まっていない状態
  • fulfilled → 処理が成功した状態
  • rejected → 処理が失敗した状態

この状態を使って、処理がどの段階にあるのかを判定します。


pending(取得中) ー取得結果→ fulfilled または rejected

とこのように状態が移り変わります。

一度 fulfilled または rejected になると、そのあと別の状態に変わることはありません。

つまり、

  • 成功したあとに失敗に変わる
  • 失敗したあとに成功に変わる

といったことは起きず、状態が一方向に決まるのです。


Promiseの基本メソッド

ここからは、Promiseの基本メソッドを見ていきます 👀


Promiseでは、「あとで決まる結果」を扱うことが分かりました。

では、その結果はどこで受け取るのか?
そして、成功と失敗はどう分けて書くのか?


その役割を持っているのが、thencatchfinally です。

この3つを使うことで、

  • 成功したときの処理
  • 失敗したときの処理
  • 最後に必ず実行したい処理

を、それぞれ分けて書けるようになります 💡


それぞれがどんな役割を持っているのかを順番に見ていきましょう!

then は成功したときの処理をつなぐ

then() は、Promiseが成功したときの処理を書くためのメソッドです。成功したあとに値を受け取って、次の処理へつないでいきます。

fetch("/api/user")
.then((response) => response.json())
.then((data) => {
console.log(data);
});

このように then() は、前の処理が成功したあとに順番につないで書けるのが特徴です。

then のポイント 💡
・ 処理を順番に並べて書ける
・ 成功したときの結果を受け取れる

また、then()常に新しいPromiseを返すため、チェーンのようにつなげて書くこともできます!


Promiseの流れをつくる中心になるのが、この then() の役割です!


catch は失敗したときの処理を受け取る

catch() は、Promiseが失敗したときの処理を書くためのメソッドです。エラーが発生したときに、その内容を受け取って処理をすることができます。

fetch("/api/user")
.then((response) => response.json())
.catch((error) => {
console.error("エラーが発生しました", error);
});

then() の途中でエラーが発生した場合も、そのまま catch() に流れてきます。そのため、処理の途中で発生したエラーもまとめて受け取ることができます。

catch のポイント 💡
・ 失敗したときのエラーを受け取れる
・ エラー処理をまとめて書ける


then と catch を組み合わせることで、成功の流れと失敗の流れを分けて書けるのがメリットです!


finally は最後に必ず実行したい処理を書く

finally() は、成功でも失敗でも必ず実行したい処理を書くためのメソッドです。処理の結果に関係なく、最後に必ず実行したい処理を書くことができます。

fetch("/api/user")
.then((response) => response.json())
.catch((error) => {
console.error(error);
})
.finally(() => {
console.log("処理終了");
});

成功・失敗どちらの場合でも、この finally() が最後に実行されます。

finally のポイント 💡
・成功・失敗どちらでも実行される
・ 後処理やクリーンアップに向いている

たとえば、

  • ローディング表示を消す
  • 共通の後処理を実行する

といった場面で finally() を使います。


ただし、finally() はあくまで最後に必ず実行する処理を書くためのものなので、成功結果やエラーの値を受け取る処理は書けません。


Promiseのstaticメソッド

thencatch は、ひとつのPromiseに対して処理をつなぐものでしたが、Promiseの本領は、複数の非同期処理をまとめて扱えるところにあります!


そんなときに使うのが、Promiseのstaticメソッドです。

複数の非同期処理を

  • すべての処理を待つのか
  • どれかひとつを使うのか
  • 最初に終わったものを採用するのか

といった形で、処理の待ち方をコントロールすることができます。


まずは、それぞれの違いを順番に見ていきましょう!


Promise.all は全部成功してほしいときに使う

Promise.all() は、複数のPromiseがすべて成功したときに結果をまとめて受け取るメソッドです。

Promise.all([fetchUser(), fetchPosts(), fetchComments()])
.then(([user, posts, comments]) => {
console.log(user, posts, comments);
})
.catch((error) => {
console.error(error);
});

このコードでは、fetchUser()fetchPosts()fetchComments() を同時に実行し、すべて成功した場合に then が実行されます。ひとつでも失敗した場合は、で処理が中断され、catch が実行されます。

Promise.all のポイント 💡
・ すべての処理が終わるまで待つ
・ 成功した場合は、結果が配列でまとめて返る
・ ひとつでも失敗すると、その時点で全体が失敗になる

たとえば、

  • ユーザー情報
  • 投稿一覧
  • コメント一覧

のように、すべてのデータがそろってから表示したい場合に使います。

逆に、どれかひとつでも失敗したら全体が止まるので、部分的に表示したいケースには向きません。


Promise.allSettled は全件の結果を確認したいときに使う

Promise.allSettled() は、すべてのPromiseの結果が出そろうまで待ち、成功・失敗それぞれの結果をまとめて受け取れるメソッドです。

Promise.allSettled([fetchUser(), fetchPosts(), fetchComments()])
.then((results) => {
results.forEach((result) => {
if (result.status === "fulfilled") {
console.log("成功:", result.value);
} else {
console.log("失敗:", result.reason);
}
});
});

このコードでは、fetchUser()fetchPosts()fetchComments()すべての処理が完了するまで待ちます。そして、それぞれの結果が results に配列として渡されます。

Promise.allSettled のポイント 💡
・ 成功・失敗どちらも含めて結果を受け取れる
・ 途中で失敗しても止まらない
・ 各結果は { status, value / reason } の形式で返る

それぞれの結果には、

  • status(成功 or 失敗)
  • value / reason

が入っているため、status を見て、成功した処理と失敗した処理を分けて扱うことができます。

受け取れる値のイメージ↓

[
{ status: "fulfilled", value: user },
{ status: "rejected", reason: error }
]

Promise.race は最初に決まった結果を採用する

Promise.race() は、複数のPromiseのうち、最初に結果が決まったものを採用するメソッドです。

Promise.race([fetchUser(), timeoutPromise(3000)])
.then((result) => {
console.log(result);
})
.catch((error) => {
console.error(error);
});

このコードでは、fetchUser() と timeoutPromise(3000) を同時に実行し、先に結果が決まった処理の結果だけthen または catch に渡されます。
どちらが先に結果を返すかによって、処理の流れが決まり、後から完了した処理の結果は無視されます。

Promise.race のポイント 💡
・ 最初に「成功 or 失敗」が決まったものが採用される
・ ほかの処理の結果は待たない
・ 後から完了した処理の結果は使用されない

たとえば、

  • API通信
  • タイムアウト処理

を同時に走らせて、どちらが先に結果を返すかで分岐したいときや、一定時間以内に終わらなければ失敗扱いにしたいといったケースで使われます。


Promise.any は最初に成功した結果を採用する

Promise.any() は、複数のPromiseのうち、最初に成功したものだけを採用するメソッドです。

Promise.any([fetchFromA(), fetchFromB(), fetchFromC()])
.then((result) => {
console.log(result);
})
.catch((error) => {
console.error(error);
});

このコードでは、複数の取得処理を同時に実行し、その中で、最初に成功した処理の結果だけthen に渡されます。後から成功した結果は無視され、すべて失敗した場合のみ catch() が実行されます。

Promise.any のポイント 💡
・ 成功したものが出るまで待ち続ける
・ 最初に成功した結果だけが採用される
・ すべて失敗したときだけエラーになる

たとえば、

  • 複数のサーバー
  • 複数のデータ取得元

の中から、どれかひとつでも成功すればOKな場合に使います。


race() との違いはここ↓

  • race → 最初に決まったもの(成功・失敗どちらも)
  • any → 最初に成功したものだけ

「成功を待つのか、最初の結果を見るのか」で使い分けます 👀


すぐに結果を持つPromiseを作る

ここからは、すぐに結果を持つPromiseを作るメソッドを見ていきます 👀


これまで見てきた allrace などは、複数の非同期処理をまとめて扱うためのものでしたが、Promiseには、

  • 成功した状態のPromiseを返したい
  • 失敗した状態のPromiseを返したい

といった場面で使用できる、あらかじめ結果が決まっている状態のPromiseを作るためのメソッドも用意されています。


そんなときに使うのが、

  • Promise.resolve()
  • Promise.reject()

です。
これらを使うことで、意図した状態のPromiseをその場で作ることができます。


Promise.resolve は成功したPromiseを作る

Promise.resolve() は、成功した状態のPromiseを作るメソッドです。

Promise.resolve("OK")
.then((value) => {
console.log(value);
});

このコードでは、"OK" を結果として持つPromiseを作成し、すぐに then が実行されます。

Promise.resolve のポイント 💡
・ 成功した状態(fulfilled)のPromiseを作れる
・ 渡した値がそのまま then に渡される
・ 非同期処理を使わずにPromiseの形を作れる

たとえば、

  • すでにある値をPromiseとして扱いたいとき
  • 条件によってPromiseを返したいとき

のような、処理の結果を統一してPromiseで返したいときに使います。


例として、キャッシュがある場合はそのまま返し、ない場合はAPIから取得するような処理で、Promise.resolve() を使用できます。

function getData(isCached) {
if (isCached) {
return Promise.resolve("キャッシュデータ");
}
return fetch("/api/data");
}

このように書くと、どちらの分岐でもPromiseが返るため、呼び出し側は同じように then で扱うことができます。


Promise.reject は失敗したPromiseを作る

Promise.reject() は、失敗した状態のPromiseを作るメソッドです。

Promise.reject("エラーが発生しました")
.catch((error) => {
console.error(error);
});

このコードでは、"エラーが発生しました" をエラーとして持つPromiseを作成し、すぐに catch() が実行されます。

Promise.reject のポイント 💡
・ 失敗した状態(rejected)のPromiseを作れる
・ 渡した値がそのまま catch() に渡される
・ 非同期処理を使わずにエラーの流れを作れる

たとえば、

・条件によってエラーとして扱いたいとき
・意図的に処理を失敗させたいとき

のような場面で使います。


Promise を外から制御する

ここからは、Promiseを外から制御する方法を見ていきます 👀


これまでのPromiseは、

  • then で結果を受け取る
  • resolve / reject で状態を決める

といったように、Promiseの中で処理が完結する形でした。


では、外側から好きなタイミングで成功・失敗を決めたい場合はどうするのか?


そんなときに使えるのが、Promise.withResolvers() です。


Promise.withResolvers は外から制御できるPromiseを作る

Promise.withResolvers() は、外から状態を決められるPromiseを作るメソッドです。

const { promise, resolve, reject } = Promise.withResolvers();
setTimeout(() => {
resolve("完了!");
}, 1000);
promise.then((value) => {
console.log(value);
});

このコードでは、Promise.withResolvers() によって promiseresolve を取得し、setTimeout の中から resolve() を呼び出しています。
その結果、後から promise が成功状態になり、then が実行されます。

Promise.withResolvers のポイント 💡
promiseresolve / reject をまとめて取得できる
・ 外からPromiseの成功・失敗を決められる
・ 処理のタイミングを分けて制御できる


単純な非同期処理であれば、new Promise() でも同じように書くことができます。


new Promise() は、Promise を作る場所と resolve / reject を呼び出す場所が同じになるため、処理の流れがひとまとまりになります。

一方で Promise.withResolvers() は、Promise の作成と resolve / reject を呼び出すタイミングを分けて扱えるのが特徴です。


そのため、イベントやタイマーなど、あとから任意のタイミングで処理を完了させたい場合にPromise.withResolvers() が適しています。


今回のポイントおさらい !!

最後に、今回出てきた用語と学んだ内容をぎゅっと凝縮しました!

Promise
・非同期処理の未来の結果を表すオブジェクト
pending fulfilled rejected の状態を持つ


then / catch / finally
then() は成功時の処理をつなぐ
catch() は失敗時の処理を受け取る
finally() は最後に必ず実行したい処理を書く


all / allSettled / race / any
all() は全部成功したときにまとめて受け取る
allSettled() は全件の成否を確認する
race() は最初に決まった結果に従う
any() は最初に成功した結果を採用する


resolve / reject / withResolvers
Promise.resolve() は成功したPromiseを作る
Promise.reject() は失敗したPromiseを作る
Promise.withResolvers() は resolve と reject を外で扱いやすくする


最後に

Promise は、名前が似ているものが多く、役割も似ているので、文法を丸ごと覚えるよりも、
「この処理は成功を受け取りたいのか」
「全部終わるのを待ちたいのか」
といった目的から何を使うのか選ぶのが良いです。

API通信や並列処理を書く場面で、適切な Promise のメソッドを選べるようになりましょう ᥫᩣ ̖́-

コメント

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