非同期処理を扱う際に欠かせないのが、JavaScriptのPromiseです。でも、Promiseって一体何をしてくれるものなのか、どうやって使えばいいのか、初心者にはなかなか難しいですよね。
そこでこの記事では、JavaScriptのPromiseについて初心者の方にもわかりやすく丁寧に解説します。Promiseの基本的な概念や使い方から、async/awaitの活用方法、エラーハンドリング、そしてPromise.allやPromise.raceの使い方まで、実践的に学べる内容となっています。
記事の中では、読者の理解を助けるためにサンプルコードを数多く用意しました。これらのコードを手を動かしながら試していくことで、Promiseの使い方が自然と身についていくはずです。
ぜひ最後まで読み進めて、JavaScriptのPromiseを完全マスターしましょう!
- 現役のフルスタックエンジニアとして活躍中
- 開発チームリーダーとして複数プロジェクトをリード
- 副業プログラミングスクール講師として数百名以上を指導してきた教育のプロ
- プログラミングスクールのカリキュラム執筆経験あり
Promiseって何?
非同期処理との戦い
JavaScriptでは、非同期処理を使わないと処理が先に進んでしまうという問題があります。例えば以下のようなコードを見てみましょう。
console.log("1番目");
setTimeout(() => {
console.log("2番目(1秒後に実行)");
}, 1000);
console.log("3番目");
このコードの実行結果は次のようになります。
1番目
3番目
2番目(1秒後に実行)
理想とは異なる順番で処理が実行されてしまいました。JavaScriptが非同期で動作するために、このような事態が発生するのです。
ここで、頼れる味方となるのがPromiseなのです。
Promiseが処理の流れを正してくれる
Promiseは、非同期処理の状態を表し、処理が完了した際に結果を返してくれるものです。つまり、処理の順序に「お約束」をつけてくれるのです。
先ほどのコードをPromiseを使って書き換えてみましょう。
console.log("1番目");
new Promise((resolve) => {
setTimeout(() => {
console.log("2番目(1秒後に実行)");
resolve();
}, 1000);
}).then(() => {
console.log("3番目");
});
実行結果は以下のようになります。
1番目
2番目(1秒後に実行)
3番目
Promiseのおかげで、意図した通りの順番で処理を実行できるようになりました。これが、Promiseの力なのです。
Promiseの状態
Promiseには、3つの状態があります。
- pending(初期状態)
- fulfilled(処理が成功した状態)
- rejected(処理が失敗した状態)
処理が成功するとresolve関数が呼ばれ、Promiseの状態がfulfilledに変わります。その後、thenメソッドに登録されたコールバック関数が実行されます。
一方、処理が失敗するとreject関数が呼ばれ、Promiseの状態がrejectedに変わります。その後、catchメソッドに登録されたコールバック関数が実行されるという流れです。
Promiseの使い方
Promiseインスタンスの作成
まずは、Promiseインスタンスの作成方法を見ていきましょう。
const promise = new Promise((resolve, reject) => {
// 非同期処理を記述
});
Promiseのコンストラクタには、resolve関数とreject関数を引数に取るコールバック関数を渡します。このコールバック関数内で、実際の非同期処理を記述するわけです。
作成したpromiseをコンソールログに出力すると、次のようなオブジェクトが表示されます。
Promise {<pending>}
PromiseがpendingつまりまだPromiseの状態は初期状態にあることがわかります。
resolveとrejectの使い分け
Promiseのコールバック関数内で、処理が正常に完了した場合はresolve関数を、そうでない場合はreject関数を呼び出します。
const promise = new Promise((resolve, reject) => {
// 処理が成功したらresolve関数を呼ぶ
resolve("resolveしたよ");
// 処理が失敗したらreject関数を呼ぶ
// reject("rejectしたよ");
}).then((message) => {
console.log(message);
}).catch((message) => {
console.error(message);
});
resolve関数を呼び出すと、Promiseの状態がfulfilledに変わり、thenメソッドに登録した関数が実行されます。
一方、reject関数を呼び出すと、Promiseの状態がrejectedに変わり、catchメソッドに登録した関数が実行されます。
また、resolve関数やreject関数に引数を渡すことで、thenやcatchメソッドでその値を受け取ることができます。これによって、非同期処理の結果を次の処理に渡すことが可能になるのです。
メソッドチェーンで処理を繋げる
Promiseのメソッドをドット(.)でつなげて、処理をチェーンのように記述していくことができます。
promise
.then(() => {
// 処理1
})
.then(() => {
// 処理2
})
.catch(() => {
// エラー処理
});
thenやcatchメソッド内でreturnした値は、次のthenやcatchメソッドに引き渡すことができます。これによって、非同期処理の結果を次々につないでいくことが可能になります。
Promise.allとPromise.race
複数のPromiseを扱う際に、とても便利なのがPromise.allとPromise.raceです。
Promise.allは、渡された全てのPromiseが完了するまで待ち、全てが成功した場合に次の処理に進みます。
Promise.all([promise1, promise2]).then((messages) => {
console.log("全部終わったよ!");
});
一方、Promise.raceは、渡されたPromiseのどれか一つが完了した時点で次の処理に進みます。
Promise.race([promise1, promise2]).then((message) => {
console.log("どれかひとつ終わったよ!");
});
どちらも複数の非同期処理を扱う際に、処理の流れを制御する強力な手段となります。
async/awaitでPromiseをスッキリ書く
ES2017で導入されたasync/awaitを使うと、Promiseをより直感的に記述することができます。
例えば、次のようなPromiseのコードがあるとします。
function resolveAfter2Seconds() {
return new Promise(resolve => {
setTimeout(() => {
resolve('resolved');
}, 2000);
});
}
これをasync/awaitを使って書き換えると、次のようになります。
async function asyncCall() {
console.log('calling');
const result = await resolveAfter2Seconds();
console.log(result);
}
asyncをつけた関数は、必ずPromiseを返します。また関数内でawaitを使うと、Promiseが解決されるまで処理を待機します。
awaitはasync関数の中でしか使えないので注意が必要ですが、これらを活用することでPromiseをよりスッキリと書けるようになります。
エラーハンドリング
Promiseを使う上で欠かせないのが、エラーハンドリングです。
Promiseでのエラーハンドリングには、catchメソッドを使います。
promise.catch((error) => {
console.error(error);
});
async/awaitを使う場合は、try…catch文を使ってエラーをキャッチします。
async function asyncCall() {
try {
const result = await resolveAfter2Seconds();
console.log(result);
} catch (error) {
console.log(error);
}
}
Promise利用時のエラーハンドリングは必須なので、忘れずに実装するようにしましょう。
まとめ
JavaScriptのPromiseについて、その概念から使い方まで詳しく見てきました。ポイントをおさらいしておきましょう。
- Promiseは非同期処理の状態を表し、処理の順序に「お約束」をつけてくれるもの
- Promiseには3つの状態(pending, fulfilled, rejected)がある
- resolve/rejectで処理の成功/失敗を通知する
- thenとcatchを使って処理をチェーンできる
- Promise.allとPromise.raceで複数のPromiseを扱える
- async/awaitを使うとPromiseをよりスッキリ書ける
Promiseを使いこなすことで、複雑な非同期処理もスマートに記述できるようになります。
ぜひ実際のコードでPromiseを活用し、非同期処理に立ち向かっていきましょう!
JavaScriptを効率的に身につける勉強法は?
JavaScript には興味があっても、プログラミング学習をはじめたばかりであれば、以下のような疑問を持つかもしれません。
- JavaScript を身に付けてエンジニアになるには何をすればいい?
- 何から勉強すればいいんだろう?
- 効率よく勉強するには?
JavaScriptは学ぶべき内容が非常に多いため、勉強法の選び方を間違うと時間・労力・お金が無駄になってしまいます…。
そのため、こちらの記事を参考に、ご自身に最適な学習方法を選んでみませんか?