Promise チェーンで処理をつなぐ
Promise の真価は .then() をチェーンすることで発揮される。複数の非同期処理を順番に実行したいとき、コールバックではネストが深くなるが、Promise チェーンならフラットに記述できる。コールバック地獄に対する直接的な解決策がこの仕組みだ。
then はなぜチェーンできるのか
.then() メソッドは新しい Promise を返す。この性質があるからこそ、.then() の後にさらに .then() をつなげられる。
const promise = new Promise(function(resolve) {
resolve(1);
});
promise
.then(function(value) {
console.log(value); // 1
return value + 1;
})
.then(function(value) {
console.log(value); // 2
return value + 1;
})
.then(function(value) {
console.log(value); // 3
});各 .then() のコールバックで値を return すると、その値が次の .then() に渡される。同期的な値を返しても、Promise でラップされて次に伝播する仕組みだ。
コールバック地獄との比較
前の記事で見たコールバック地獄のコードを、Promise チェーンで書き直してみよう。
ネストが深くなり、処理の流れを追うのが困難。エラーハンドリングも分散する。
フラットに並び、上から下へ順番に読める。エラーは末尾の .catch() でまとめて処理できる。
コールバック方式ではこうだった。
getUser(userId, function(user) {
getOrders(user.id, function(orders) {
getOrderDetail(orders[0].id, function(detail) {
console.log(detail);
});
});
});Promise チェーンではこう書ける。
getUser(userId)
.then(function(user) {
return getOrders(user.id);
})
.then(function(orders) {
return getOrderDetail(orders[0].id);
})
.then(function(detail) {
console.log(detail);
})
.catch(function(error) {
console.error("エラー:", error.message);
});ネストが消え、処理が縦に並んで流れが一目でわかる。どの段階でエラーが起きても最後の .catch() で捕捉される点も大きい。
then で Promise を返すパターン
チェーンの中で非同期処理をつなぐ場合、.then() のコールバックから Promise を返す必要がある。Promise を返すと、その Promise が解決されるまで次の .then() は実行されない。
function delay(ms) {
return new Promise(function(resolve) {
setTimeout(resolve, ms);
});
}
delay(1000)
.then(function() {
console.log("1秒経過");
return delay(2000);
})
.then(function() {
console.log("さらに2秒経過");
return delay(500);
})
.then(function() {
console.log("さらに0.5秒経過");
});1 つ目の then で Promise を返す
その Promise が解決されてから次の then が実行される
順番に非同期処理が進む
この「Promise を返すことで次の処理を待たせる」パターンが、Promise チェーンの根幹をなしている。
エラーの伝播と catch の仕組み
Promise チェーンでは、途中のどの段階でエラーが発生しても、チェーンの末尾にある .catch() まで自動的にエラーが伝播する。
getUser(userId)
.then(function(user) {
return getOrders(user.id);
})
.then(function(orders) {
// ここでエラーが発生したとする
throw new Error("注文データの形式が不正です");
})
.then(function(detail) {
// ここはスキップされる
console.log(detail);
})
.catch(function(error) {
// 上のどの段階のエラーもここで捕捉できる
console.error("エラー:", error.message);
});エラーが発生すると、それ以降の .then() はすべてスキップされ、直近の .catch() にジャンプする。この挙動は同期コードにおける try/catch と似ており、直感的に理解しやすい。
catch の後にチェーンを続ける
.catch() も新しい Promise を返すため、その後にさらに .then() をつなげられる。エラーからの回復処理を書くときに使うパターンだ。
fetchData()
.then(function(data) {
return processData(data);
})
.catch(function(error) {
console.warn("エラーが発生、デフォルト値を使用:", error.message);
return { fallback: true };
})
.then(function(result) {
// 正常時は processData の結果、エラー時は fallback オブジェクトが来る
console.log("最終結果:", result);
});.catch() から値を返すと、次の .then() は fulfilled として実行される。これはエラー回復パターンと呼ばれ、フォールバック値やデフォルト動作を提供する際に便利だ。
エラーが起きても処理を中断せず、代替値で続行する設計手法。
よくある間違い:then の中でネストする
Promise チェーンを使っていても、.then() の中でさらに .then() をネストしてしまうと、コールバック地獄と同じ問題が起きる。
// 悪い例:Promise のネスト
getUser(userId).then(function(user) {
getOrders(user.id).then(function(orders) {
getOrderDetail(orders[0].id).then(function(detail) {
console.log(detail);
});
});
});これでは Promise を使う意味がない。正しくは return で Promise を返してチェーンをフラットにする。
// 良い例:フラットなチェーン
getUser(userId)
.then(function(user) {
return getOrders(user.id);
})
.then(function(orders) {
return getOrderDetail(orders[0].id);
})
.then(function(detail) {
console.log(detail);
});.then() のコールバックで return を忘れると、次の .then() には undefined が渡される。これも見落としやすいバグの原因となるため注意が必要だ。
Promise チェーンは非同期処理の流れを制御する強力な仕組みだが、チェーンが長くなると依然として読みにくくなることがある。その問題を解消するのが async/await 構文であり、Promise チェーンをさらに同期的な見た目で書けるようにしたものだ。