こんにちは!Reiです(˵ •̀ ᴗ – ˵) ✧
突然ですが、コード内にfetch() や async/await がやらたらと並んでいたり、Promise を扱っているけど何しているのか分かってなかったりしていませんか…?
その原因になりやすいのが、Promiseを扱えていないこと です。
Promiseは、JavaScriptの非同期処理を扱ううえでの土台になる存在。
だけど、Promiseをうまく扱えていないと…
- エラー処理が適切に対処できてない
thenやcatchのつなぎ方が曖昧になる- 並列処理の書き分けで迷いやすくなる
といった状態に陥ってしまいます 💦
逆に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では、「あとで決まる結果」を扱うことが分かりました。
では、その結果はどこで受け取るのか?
そして、成功と失敗はどう分けて書くのか?
その役割を持っているのが、then・catch・finally です。
この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メソッド
then や catch は、ひとつの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を作るメソッドを見ていきます 👀
これまで見てきた all や race などは、複数の非同期処理をまとめて扱うためのものでしたが、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から取得するような処理で、 を使用できます。.resolve()Promise
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() によって promise と resolve を取得し、setTimeout の中から resolve() を呼び出しています。
その結果、後から promise が成功状態になり、then が実行されます。
Promise.withResolvers のポイント 💡
・ promise と resolve / 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 のメソッドを選べるようになりましょう ᥫᩣ ̖́-


コメント