JavaScriptを使っていると「非同期処理」「Promise」「then」「catch」という言葉に目にすることがあります。
コードが複雑ですし、耳慣れない言葉で難しそうだと感じるかもしれません。
しかし、この非同期処理こそが、データを取得したり、動画を読み込みながらも、画面がフリーズせずに動き続けるための重要な仕組みです。
この記事では、Promiseや非同期処理が何を意味するのか、同期処理との違いは何なのか?実例で解説しています。
さらに、非同期処理の結果を処理するための重要なメソッドである .then()、.catch()、.finally() の役割も、具体的なコードと実例で解説しています。
この記事を読めば、非同期処理が何なのか?やその仕組みを理解することができます。
Promiseと何か?
Promise(プロミス)は JavaScript における非同期処理を扱うためのオブジェクトです。
Promiseは「将来の処理の完了を約束する」という意味で、必ず処理結果として「成功」または「失敗」を返します。
時間がかかる処理(非同期処理)をするときに使う、JavaScriptの便利な仕組みです。
非同期処理とは何か?同期処理との違い
非同期処理とは何か?
Promiseの解説には「非同期処理」という言葉が切っても切り離せません。
非同期処理とは、時間のかかる処理を待たずに、同時進行で次の処理を進める仕組みのことです。時間のかかる処理をバックグラウンドに任せ、その間に他の処理を進めることができます。(ノンブロッキングといいます)
この仕組みによって、アプリやウェブサイトがフリーズせずに動き続けることができます。
開始は順番通りでも、完了は順不同になることがあります。
非同期処理は英語で「Asynchronous Processing」です。冒頭の5文字をとって「Async」と表記されることもあります。
同期処理とは何か?
同期処理とは、複数のタスクを順番に一つずつ実行していく処理方式です。ある処理を開始したら、その処理が完全に完了するまで、次の処理は開始されません。
厳密に順番通りに処理を実行していきます。先行する処理が完了するまで、後続の処理が停止するので「ブロッキング」と呼びます。
同期処理は英語で「Synchronous Processing」です。冒頭の4文字をとって「Sync」と表記されることもあります。
Promiseの3つのステータス
Promiseには以下の3つの状態があります。
- pending(保留中):まだ処理中で、結果が出ていない状態
- fulfilled(成功):処理が成功し、値(result)が返ってきた状態(Resolvedと呼ばれることもあります)
- rejected(失敗):処理が失敗し、理由(エラー)が返ってきた状態
pending(保留中)は、Promiseが作成された直後から、非同期処理が完了するまでの一時的な状態です。
最終的に返ってくるのは fulfilled(成功) か rejected(失敗) のどちらかの確定した状態だけです。
thenとcatchメソッド
Promiseを使うときは基本的に「then」と「catch」メソッドが欠かせません。
Promiseの構文や実例を紹介する前に、thenとcatchを解説しておきます。
Promise完了後の処理を記述するために使う
then メソッドと catch メソッドは、Promise(プロミス) の結果(成功または失敗)を受け取って、その後の処理を定義するために使われる、最も重要なメソッドです。
両方とも、非同期処理が完了した後に実行されるべき処理を指定するために使われます。
thenメソッドとは何か?
then() は、Promiseが「成功した時」の処理や、「次のPromise」を作成するために使われます。
thenの構文
then(成功時に実行する処理)
then(成功時に実行する処理, 失敗時に実行する処理)thenは第1引数に成功したときの処理、第2引数(省略可)で失敗したときの処理を記述します。
以下のように、Promiseオブジェクトに対して成功したときの処理を記述します。
const myPromise = new Promise( (resolve, reject) => {...};
myPromise.then(function(data) {
// サーバーからデータが正常に取得されたら実行
console.log("成功しました。取得したデータ:", data);
});thenの第一引数に渡される関数(上記の場合data)は、Promiseの処理結果でresolveメソッドで指定したデータが入ります。
thenをメソッドチェーンでつなぐこともできます。
getData() // 1. データを取得
.then(data => process(data)) // 2. データを加工
.then(processedData => save(processedData)) // 3. 加工したデータを保存
.then(() => console.log("全て完了!"))
.catch(error => console.error(error)); // どこかで失敗したらここでキャッチこれにより「Aの処理が終わったら→Bの処理→Bが終わったら→Cの処理」という流れを上から下に読みやすく記述できます。
catchメソッドとは何か?
.catch() は、Promiseが「失敗した時」の処理(エラー処理)を定義する際に使います。基本構文はシンプルです。
.catch(失敗した時の処理)最後に一つだけ記述することで、Promiseチェーンのどの段階でエラーが発生しても全てのエラーをまとめて処理することができます。
const myPromise = new Promise( (resolve, reject) => {...};
myPromise
.then(function(data) {
// 成功時の処理
console.log("データ取得完了");
})
.catch(function(error) {
// 成功せずに終わったとき(または途中の.thenでエラーが発生したとき)に実行
console.error("エラーが発生しました:", error.message);
});thenの第2引数とcatchは何が違うのか?
then の第2引数に指定する関数と、catchで指定する関数は、どちらも Promiseが失敗したとき(Rejectedになったとき)に実行される処理 を定義するという点で同じ役割を持ちます。
しかし、エラーをキャッチできる範囲と、Promiseチェーンの取り回しに大きな違いがあります。
なお、推奨はcatchを使うことです。
thenがキャッチできるエラー
thenは直前のPromiseで発生した失敗しかキャッチできません。
Promiseの処理をつなげる場合に、エラーの記述が煩雑になります。
promiseA()
.then(
dataA => promiseB(), // 成功処理 (Bを実行)
error => console.error("Aのエラーをキャッチ") //
)
.then(
dataB => promiseC(), // Bで失敗したので、ここは実行されない
error => console.error("Bのエラーをキャッチ:", error.message)
);catchがキャッチできるエラー
catchは、それより前のチェーン全体で発生したエラーをキャッチできます。
このため、thenで成功したときの処理を記述して、最後に一つcatchでエラーが発生したときの処理を記述するのが一般的です。
promiseA()
.then(dataA => promiseB()) // Bを実行
.then(dataB => promiseC()) // Bで失敗したので、ここはスキップされる
.catch(error => {
// A、または B、または C (またはその間の then) で
// 発生した全てのエラーをここでまとめてキャッチ!
console.error("エラー発生:", error.message); //
});finallyメソッド
thenとcatchの他にも「finally」もあります。
finallyはPromiseが成功したか拒否失敗したかにかかわらず、処理が完了した時に実行したい処理を指定することができます。
つまり、成功だろうが失敗だろうが必ず実行する処理です。
例えば、ローディング表示を消す、リソースを解放するなどのクリーンアップ処理を記述するために使います。
myPromise
.then(result => { /* 成功時の処理 */ })
.catch(error => { /* 失敗時の処理 */ })
.finally(() => {
// 成功・失敗どちらの場合でも必ず実行する
console.log("Promiseの処理が終了しました。");
});Promiseの使い方(基本構文)
Promiseオブジェクトの作成
Promiseを使うには、new Promise() コンストラクタを使って新しいPromiseオブジェクトを作成します。
Promiseの基本構文は以下になります。
new Promise((resolve, reject) => { ... })このときに、引数として渡す関数(resolve, reject) => { … }を実行関数( Executor) と呼び、ここに非同期処理を記述します。
resolveとreject
Promiseのコンストラクタで実行関数を定義するときに、第一引数でresolve、第二引数でrejectを指定します。
この2つが実行関数内で成功したときと、失敗したときの結果を次のthenとcatchに渡すメソッドとなります。
new Promise((resolve, reject) => {
// まず非同期処理を記述
// 処理が成功した場合
// resolve() を呼び出し、Promiseを Fulfilled にする
resolve(成功時の値); //この値がthenの引数になる
// 処理が失敗した場合
// reject() を呼び出し、Promiseを Rejected にする
reject(エラーオブジェクト); //この値がcatchの引数になる
});引数はresolveとreject以外の任意の値が使えますが、慣習として「resolve」「reject」を使い、他の値は使いません。
※引数名を変更した場合は、処理の中で成功と失敗の結果を返すメソッド名も連動して変わります。
Promiseオブジェクトを変数に入れる
なお、コンストラクタで作成したPromiseオブジェクトは変数(定数)に格納し、Promiseが完了した次の処理(thenやcatch)を記述していきます。
const myPromise = new Promise((resolve, reject) => {});
myPromise
.then( result => {})
.then( result2 => {})
.catch(error => {});
もう少し詳しく記述すると以下のようになります。
const myPromise = new Promise((resolve, reject) => {
// まず非同期処理を記述
// 処理が成功した場合
// resolve() を呼び出し、Promiseを Fulfilled にする
resolve(成功時の値); //この値がthenの引数になる
// 処理が失敗した場合
// reject() を呼び出し、Promiseを Rejected にする
reject(エラーオブジェクト); //この値がcatchの引数になる
});
myPromise
.then( result => {
// 成功したとき(Fulfilled)に実行される関数
// result には、成功時の値が入ります
console.log("成功しました!取得したデータ:", result);
return "次の処理へ"; // 次の.then()へ渡す値を返す
}).then( result2 => {
// 成功したとき(Fulfilled)に実行される関数
// result2 には、成功時の値が入ります
console.log("成功しました!2つ目の取得したデータ:", result2);
return "次の処理へ"; // 次の.then()へ渡す値を返す
})
.catch( error => {
// 失敗したとき(Rejected)に実行される関数
// error には、失敗時のエラーが入ります
console.error("エラーが発生しました:", error);
});Promiseとtry,catch,finallyの実行順序
Promiseとtry,catch,finallyの実行順序を理解しておくことも大切です。
まず、以下のようにPromiseオブジェクトを生成した時点でPromiseの処理は実行されます。
new Promise((resolve, reject) => { ... })
この後に以下の記述をしたとします。
myPromise
.then( result => {})
.then( result2 => {})
.catch(error => {})
.finally(console.log("処理終了!"));このとき、myPromiseを実行するわけではりません。
既に実行してあるmyPromiseの処理が完了した時点で、then、catch、finallyの処理が走ります。
つまり、このコードはPromiseの実行ではなく、Promise後の処理を登録しておくようなものです。(以下の実例2を見るとわかりやすいです)
Promiseの実例1
Promiseおよび、then, catch, finallyの動きを理解するためのコード例です。
//Promiseを作成
const myPromise = new Promise((resolve, reject) => {
const status = Math.random() > 0.5; // 約50%の確率で true になる
setTimeout(() => {
if (status) {
resolve("成功!");
} else {
reject("エラー発生");
}
}, 2000);
});
// Promiseを使用
myPromise
.then(result => {
console.log(result);
})
.catch(error => {
console.error(error);
})
.finally(() => {
console.log("処理が終わりました");
console.log("----------------------");
});
myPromiseを実行してから2秒後に結果が返ってきます。
結果はランダムで変わります。

Promiseの実例2(非同期処理とPromiseの流れ)
非同期処理がよくわかる実例です。
console.log("1. 処理開始");
// Promiseを作成
const myPromise = new Promise((resolve, reject) => {
console.log("2. Promiseコンストラクタ内の処理");
//2秒後に処理を完了させる
setTimeout(() => {
const status = Math.random() > 0.5; // 約50%の確率で true になる
if (status) {
resolve("Promiseの結果: 成功データ");
} else {
reject("Promiseの結果: エラー");
}
}, 1000);
});
// Promise完了時の処理を登録
myPromise
.then(result => {
console.log("4. thenハンドラの処理: " + result);
})
.catch(error => {
console.error("4. catchハンドラの処理: " + error);
});
//最後のコード
console.log("3. Promise定義後の処理");
コードの順番で行くとコンソールには以下のように「1→2→4→3」表示されるはずです。
1. 処理開始
2. Promiseコンストラクタ内の処理
4. thenハンドラの処理: Promiseの結果: 成功データ (またはエラー)
3. Promiseの定義直後の処理
ですが非同期処理なので、Promiseの時間のかかる処理が非同期で実行され「1→2→3→4」となります。
1. 処理開始
2. Promiseコンストラクタ内の処理
3. Promiseの定義直後の処理
4. thenハンドラの処理: Promiseの結果: 成功データ (またはエラー)
▼実際の実行結果

Promiseオブジェクトを生成するコンストラクタ内の処理がまず実行されています。
その中で時間がかかる処理を非同期で実行するため、どんどん他のコードの処理を実行していきます。
fetchはPromiseを返す
Webブラウザ上でネットワーク経由でリソース(データ)を取得するための関数にfetchがあります。
fetchは非同期処理で、かつ、Promiseを返します。
fetch("https://api.example.com/data")
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error("エラー:", error));fetchは通信が成功した場合は、取得してきたResponseオブジェクトをthenに渡します。
通信エラーだった場合はエラーの内容をcatchに渡します。
①404や500も成功とみなされる
fetchのPromiseは、HTTPステータスコードが404 (Not Found) や500 (Server Error) のようなエラーコードであっても、ネットワーク自体が成功していれば、Responseオブジェクトを返します。サーバーエラーの判定は、受け取ったResponseオブジェクトのプロパティ(例: response.ok)をチェックして行う必要があります。
データ抽出が必要:
Responseオブジェクトは、まだ生のデータストリームです。データ本体をJSONやテキストとして利用可能にするには、response.json()やresponse.text()などのメソッドを呼び出す必要があります。


