フロントエンド開発において、非同期処理は避けて通れない道です。API通信、タイマー処理、ファイル読み込みなど、様々な場面で非同期処理が登場します。JavaScriptで非同期処理を扱うための主要な仕組みの一つが「Promise」であり、その中でも特に重要なのが `Promise.prototype.then()` メソッドです。
しかし、「then()がどういうものか、なんとなくは理解しているけど、いまいち使いこなせていない…」という方もいるのではないでしょうか。今回は、そんな方のために、`then()` をより深く理解し、非同期処理をスッキリと、そして安全に記述するための実践的なテクニックを、具体的なコード例を交えながら解説していきます。
then() の基本:成功と失敗のハンドリング
`then()` メソッドは、Promiseが生成した値(成功時)またはエラー(失敗時)を受け取るためのコールバック関数を登録します。
const myPromise = new Promise((resolve, reject) => {
// 非同期処理の実行(例:APIリクエスト)
setTimeout(() => {
const success = true; // 処理が成功したと仮定
if (success) {
resolve(“データ取得成功!”);
} else {
reject(“データ取得失敗…”);
}
}, 1000);
});
myPromise.then(
(result) => {
// 成功時の処理
console.log(“成功:”, result); // 成功: データ取得成功!
},
(error) => {
// 失敗時の処理
console.log(“失敗:”, error); // 失敗: データ取得失敗…
}
);
`then()` は、第一引数に成功時のコールバック関数、第二引数に失敗時のコールバック関数をオプションで受け取ることができます。この第二引数は、`catch()` メソッドの役割も兼ねています。
then() の連鎖:非同期処理の組み立て方
`then()` の真価は、その「連鎖」にあります。`then()` メソッドは、新しいPromiseを返すため、複数の非同期処理を順番に実行していくことができます。
例えば、ユーザー情報を取得し、そのユーザーIDを使って投稿リストを取得する、といった処理を考えてみましょう。
function getUser(userId) {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log(`ユーザーID ${userId} の情報を取得中…`);
resolve({ id: userId, name: “Alice” });
}, 500);
});
}
function getPosts(userId) {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log(`ユーザーID ${userId} の投稿を取得中…`);
resolve([{ id: 1, title: “Promiseについて” }, { id: 2, title: “非同期処理のコツ” }]);
}, 500);
});
}
getUser(123)
.then((user) => {
console.log(“ユーザー情報:”, user);
// 次のthenで、取得したユーザーIDを使って投稿を取得
return getPosts(user.id);
})
.then((posts) => {
console.log(“投稿リスト:”, posts);
})
.catch((error) => {
console.error(“エラーが発生しました:”, error);
});
このように、`then()` のコールバック関数内で別のPromiseを `return` することで、処理が順番に繋がっていきます。これは、コールバック地獄(Callback Hell)を回避し、コードを読みやすく、管理しやすくするための強力な手段です。
then() で返される値が次の then() にどう渡されるか?
ここが理解の鍵となります。`then()` のコールバック関数が返す値は、次の `then()` のコールバック関数の引数として渡されます。
- 通常の値を返す場合: その値がそのまま次の `then()` の引数に渡されます。
- Promiseを返す場合: そのPromiseが解決(fulfilled)されるまで待機し、解決された値が次の `then()` の引数に渡されます。
- エラーを投げる(throw)場合: そのエラーが捕捉され、次の `catch()` または失敗時のコールバック関数に渡されます。
この仕組みを理解することで、複雑な非同期処理の流れを正確に制御できるようになります。
then() の落とし穴と注意点
- then() の第二引数(失敗時のコールバック)の省略: `then(successCallback)` のように、失敗時のコールバックを省略した場合、エラーが発生してもそれは捕捉されません。エラーハンドリングを確実に行うためには、`catch()` メソッドを使うか、`then()` の第二引数を明示的に指定しましょう。
- then() の中でエラーを投げ忘れる: Promiseチェーンの途中でエラーが発生しても、それを `throw` しなかったり、`reject()` しなかったりすると、後続の `catch()` で捕捉できません。
// 誤った例:エラーが捕捉されない
getUser(456)
.then((user) => {
// ここでエラーが発生しても、catchに伝わらない
console.log(user.nonExistentProperty);
})
.catch((error) => {
console.error(“このエラーは捕捉されません:”, error);
});
// 正しい例:エラーをthrowする
getUser(789)
.then((user) => {
try {
console.log(user.nonExistentProperty);
} catch (e) {
// エラーをthrowして、catchに渡す
throw e;
}
})
.catch((error) => {
console.error(“エラーが捕捉されました:”, error);
});
- Promise.resolve() / Promise.reject() の活用: `then()` の中で、同期的に値を返す場合や、すぐに失敗させたい場合があります。そんな時は `Promise.resolve(value)` や `Promise.reject(error)` を使うと、Promiseチェーンをスムーズに継続させることができます。
まとめ
`Promise.prototype.then()` は、単なる非同期処理のコールバック関数ではありません。それは、非同期処理を構造化し、連鎖させるための強力なインターフェースです。
- `then()` は成功時と失敗時の両方のコールバックを受け取れる。
- `then()` は新しいPromiseを返すため、処理を連鎖できる。
- `then()` のコールバックで返された値(または解決されたPromiseの値)が、次の `then()` の引数になる。
- エラーハンドリングは `catch()` または `then()` の第二引数で確実に行う。
これらのポイントを意識して `then()` を使いこなすことで、あなたのコードはよりクリーンで、堅牢になり、保守性も格段に向上するはずです。ぜひ、日々の開発で積極的に活用してみてください!