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()
)します。
コメント
ログインしてコメントしましょう。