Blog Article
JavaScript Promise 初心者でもわかる非同期処理の基本

Promiseとは?
Promise (プロミス) は、非同期処理の結果を表すオブジェクトです。簡単に言うと、「今は結果がわからないけど、あとで成功するか失敗するかを約束するもの」です。
なぜ Promise が必要なのか?
JavaScript では非同期処理 (例えば API のリクエストやファイルの読み込み) が多く使われます。昔は callback という方法で処理していましたが、コードが複雑になりやすいという問題がありました (いわゆる 「コールバック地獄」)。
// コールバック地獄の例
getUser(1, function (user) {
getPosts(user.id, function (posts) {
getComments(posts[0].id, function (comments) {
console.log(comments)
})
})
})
このようなネスト (入れ子) が深くなる と、可読性が下がってしまいます。これを解決するために Promiseが導入されました。
Promiseの基本構造
const promise = new Promise((resolve, reject) => {
// 非同期処理を実行
let success = true
if (success) {
resolve('成功!') // 成功時の処理
} else {
reject('失敗…') // 失敗時の処理
}
})
Promise は 3 つの状態を持ちます。
- pending (保留) - まだ結果が決まっていない状態。まだ非同期処理が完了していません。
- fulfilled (成功) -
resolve()が呼ばれて成功した状態。この状態になった時、Promiseは解決され、結果がthen()に渡されます。 - rejected (失敗) -
reject()が呼ばれて失敗した状態。この状態になった時、Promiseは拒否され、catch()に渡されます。
引数のresolveとrejectは関数なの?
resolveとrejectは、関数です。new Promise()コンストラクタに渡されるコールバック関数内で、resolveとrejectはそれぞれ引数として渡されます。これらは、非同期処理が正常に完了したときや、エラーが発生したときに Promise の状態を変更するための関数です。
const promise = new Promise((resolve, reject) => {1
// ここで `resolve` と `reject` は関数として使われます
});
resolve: 非同期処理が成功した場合に呼ばれ、Promise の状態をfulfilled(解決済み)に変更します。resolve()が呼ばれると、Promise は成功として解決され、その値が.then()のコールバックに渡されます。reject: 非同期処理が失敗した場合に呼ばれ、Promise の状態をrejected(拒否) に変更します。reject()が呼ばれると、Promise はエラーとして拒否され、そのエラーメッセージが.catch()のコールバックに渡されます。
Promiseをインスタンス化する理由ってなに?
- 非同期処理の結果が成功か失敗かを管理するために、Promiseをインスタンス化します。
resolve(): 成功時の値を返すreject(): 失敗時のエラーを返す
const myPromise = new Promise((resolve, reject) => {
const success = true; // 成功するかどうか(仮の条件)
setTimeout(() => {
if (success) {
resolve("成功しました!");
} else {
reject("エラーが発生しました");
}
}, 1000);
});
myPromise
.then((result) => console.log(result)) // 成功時: "成功しました!"
.catch((error) => console.log(error)); // 失敗時: "エラーが発生しました"
.then()や.catch()を使って、処理をつなげるため
new Promise()で作成したインスタンス (promiseオブジェクト) には、 .then()や.catch()などのメソッドがあり、非同期処理を連鎖的に実行できます。
const fetchData = new Promise((resolve) => {
setTimeout(() => {
resolve("データを取得しました");
}, 1000);
});
fetchData
.then((data) => {
console.log(data); // 1秒後: "データを取得しました"
return "次の処理へ";
})
.then((nextStep) => {
console.log(nextStep); // "次の処理へ"
});
- 非同期処理をカスタマイズできる
API のリクエストやファイルの読み込みなど、特定の処理をラップしてPromiseとして扱うことで、再利用しやすくなります。
function asyncTask() {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve("タスク完了");
}, 1000);
});
}
asyncTask().then((message) => console.log(message)); // 1秒後: "タスク完了"
Promiseのインスタンス化とは具体的に何をしているのか?
インスタンス化とは、クラス (設計図) からオブジェクト (実体)を作ることを指します。
JavaScript の Promiseはクラス (関数オブジェクト) なので、new Promise()を使うことで Promiseのインスタンス (オブジェクト) を作成しています。そのオブジェクトは 「非同期処理の結果を持つオブジェクト」 になります。
const promise = new Promise((resolve, reject) => {
setTimeout(() => {
resolve("成功!");
}, 1000);
});
console.log(promise); // すぐに「Promise { <pending> }」が表示される
変数promiseには、Promiseオブジェクトが格納されています。つまり、Promiseクラスのインスタンス (オブジェクト)を作成しています。
例:オンラインショップでの注文
オンラインショップで商品を注文し、支払いが完了したら商品を発送する流れを Promise で表現してみます。
- 商品を注文する (
orderProduct()) - 注文が完了すると、支払いが行われる (2秒後)
- 支払いが完了すると、商品が発送される (
shipProduct()) - 商品の発送が完了すると、通知が届く (1.5秒後)
function orderProduct(product) {
return new Promise((resolve, reject) => {
console.log(`${product} を注文しました。`);
setTimeout(() => {
resolve(`${product} の支払いが完了しました。`);
}, 2000); // 2秒後に支払い完了
});
}
function shipProduct(paymentConfirmation) {
return new Promise((resolve) => {
console.log(paymentConfirmation);
setTimeout(() => {
resolve("商品が発送されました。");
}, 1500); // 1.5秒後に発送
});
}
orderProduct("ノートPC")
.then((paymentConfirmation) => {
return shipProduct(paymentConfirmation);
})
.then((shippingStatus) => {
console.log(shippingStatus);
})
.catch((error) => {
console.log(`エラー: ${error}`);
});
orderProduct(product)の動作
function orderProduct(product) {
return new Promise((resolve, reject) => {
console.log(`${product} を注文しました。`);
setTimeout(() => {
resolve(`${product} の支払いが完了しました。`);
}, 2000); // 2秒後に支払い完了
});
}
orderProduct()が呼ばれると、新しいPromiseを作成console.log()で「商品を注文しました」と表示 (この時点では、まだPending)setTimeout()で2秒後にresolve()を実行resolve()が実行されると、支払い完了のメッセージ ("ノートPC の支払いが完了しました。") がPromiseの成功 (fulfilled) の結果として渡される
shipProduct(paymentConfirmation)の動作
function shipProduct(paymentConfirmation) {
return new Promise((resolve) => {
console.log(paymentConfirmation);
setTimeout(() => {
resolve("商品が発送されました。");
}, 1500); // 1.5秒後に発送
});
}
shipProduct()が呼ばれると、新しいPromiseを作成console.log()で**paymentConfirmation** (支払い完了メッセージ) を表示 (まだpending)setTimeout()で 1.5秒後にresolve()を実行resolve()が実行されると、「商品が発送されました。」というメッセージが Promise の成功の結果になる
then()を使って処理を順番に実行
orderProduct("ノートPC")
.then((paymentConfirmation) => {
return shipProduct(paymentConfirmation);
})
.then((shippingStatus) => {
console.log(shippingStatus);
})
.catch((error) => {
console.log(`エラー: ${error}`);
});
orderProduct("ノートPC")を呼び出す (すぐにconsole.log("ノートPC を注文しました。")を実行)- 2秒後に支払い完了 →
resolve("ノートPC の支払いが完了しました。") .then((paymentConfirmation) => { ... })に渡され、shipProduct(paymentConfirmation)を実行- 1.5秒後に発送完了 →
resolve("商品が発送されました。") .then((shippingStatus) => console.log(shippingStatus))に渡され、「商品が発送されました。」を表示
なぜ、new Promiseと書かなくても、.then()や.catch()が実行できるのか
このコードでは、orderProduct関数自体がPromiseを返しているため、.then()や.catch()を使って、Promiseの結果を処理することができます。
orderProduct関数は、new Promise()を使ってPromiseを生成し、そのPromiseが解決される(resolve() が呼ばれる) ことで結果を返します。この関数自体がPromiseを返すので、次のように.then()や.catch()を使ってその結果を処理できます。
このように記述すると、より長い処理の連鎖を作成しても、コールバック地獄を防ぐことができます。
Promiseを返さないとどうなる?
Promiseを返さずに非同期処理を実行すると、処理の完了を.then()チェーンで待てなくなります。つまり、非同期処理が終わる前に次の処理が進んでしまいます。
悪い例
function orderProduct(product) {
return new Promise((resolve) => {
console.log(`${product} を注文しました。`);
setTimeout(() => {
resolve(`${product} の支払いが完了しました。`);
}, 2000);
});
}
function shipProduct(paymentConfirmation) {
console.log(paymentConfirmation); // ここでプロミスを返していない!
setTimeout(() => {
console.log("商品が発送されました。");
}, 1500);
}
orderProduct("ノートPC")
.then((paymentConfirmation) => {
shipProduct(paymentConfirmation); // ← ここで return していない
})
.then(() => {
console.log("発送完了後に実行したい処理");
});
// 実行結果
// ノートPC を注文しました。
// (2秒後) ノートPC の支払いが完了しました。
// (2.5秒後) 発送完了後に実行したい処理 ← ここが先に実行される!
// (3.5秒後) 商品が発送されました。 ← 後から出る
shipProduct()が非同期なのに .then()のチェーンに含まれないので、「発送完了後に実行したい処理」が発送完了前に実行されてしまいます。
.then() の中で非同期処理をするときは、必ずPromiseをreturnしましょう。そうしないと「浮いているプロミス」になり、正しく処理を待てなくなります。
そして、次の.then()は、前の.then()の処理が終わるのを待ってから実行されます。
なので「浮いているプロミス」を防ぐには、.then() の中で return を忘れないようにしましょう。
Promiseを使った非同期処理
例えば、fetchを使ってAPIからデータを取得するコードはPromiseで書かれています。
このコードでは、returnでPromiseを返していないし、resolve()やreject() を書いていないのに、.then()や.catch()を記述しています。
ここで使われているfetch関数は、すでにPromiseを返す関数 なので、new Promise()を書く必要がありません。また、resolve()やreject()を使っていないのは、fetchがその内部で既に resolveとrejectを使っているからです。
fetch('<https://jsonplaceholder.typicode.com/posts/1>')
.then((response) => response.json()) // レスポンスを JSON に変換
.then((data) => console.log('記事:', data)) // 取得したデータを表示
.catch((error) => console.error('エラー:', error)) // エラー時の処理
fetchがPromiseを返す
fetchは標準的なブラウザAPIの一つで、ネットワークリクエスト (例: API へのGETリクエスト) を行います。そして、fetchはPromiseを返す非同期関数です。つまり、fetch自体がPromiseの役割を果たしているため、さらにnew Promise()を使って新しいPromiseを作成する必要はありません。
fetchがPromiseを返した際に、resolveやrejectが不要な理由
resolve()やreject()は、通常は新しいPromiseを手動で作成した場合に使います。しかし、fetch は内部で自動的に次のような動作をしています。
- 成功した場合、
resolve()を呼び出してPromiseを解決 (fulfilled)します。 - 失敗した場合、
reject()を呼び出して Promise を拒否 (rejected) します。
そのため、fetch を使う場合には、resolveやrejectを自分で書く必要はありません。fetchがネットワークリクエストの成功または失敗に基づいて、Promise を解決 (resolve())または拒否(reject())します。