まず「チェーン処理」を一言でいうと
Promise のチェーン処理は、
「非同期のステップを、then を使って“上から順番に”つなげていく書き方」 です。
コールバック地獄だと、
A の中で B を呼んで
B の中で C を呼んで
C の中で D を呼んで…
と、どんどん右にネストしていました。
Promise のチェーンはこれを、
A
→ then で B
→ then で C
→ then で D
という「縦にまっすぐな流れ」に変えてくれます。
ここが重要です。
チェーン処理の本質は、「時間の順番(A→B→C)を、ネストではなく縦の列として書けるようにすること」 です。
チェーン処理のいちばん基本の形
then が「新しい Promise を返す」という性質
Promise のチェーンを理解するうえで、一番大事なポイントはこれです。
then は必ず「新しい Promise」を返す。
これのおかげで、
「ある Promise の結果を受けて次の Promise を返す」
→ さらにその結果を受けて次…
というふうに「バトン渡し」ができます。
まずはとてもシンプルな例から見てみましょう。
Promise.resolve(1)
.then((v1) => {
console.log("v1:", v1); // 1
return v1 + 1;
})
.then((v2) => {
console.log("v2:", v2); // 2
return v2 + 1;
})
.then((v3) => {
console.log("v3:", v3); // 3
});
JavaScriptここでは、
最初の Promise:1 で成功
1つ目の then:1 を受け取って 2 を返す
2つ目の then:2 を受け取って 3 を返す
3つ目の then:3 を受け取ってログを出す
というふうに、値が「上から下へ」流れていきます。
このとき、各 then の return は、
「次の then が受け取る値になる」
と覚えておくと良いです。
「普通の値」を返しても、「Promise」を返してもチェーンできる
then の中で、
単なる値(数値・文字列・オブジェクトなど)
を return した場合は、「その値で成功した Promise」が次に渡されます。
別の Promise
を return した場合は、「その Promise の結果」が次に渡されます。
この「どっちでもいい」という性質が、非同期処理をきれいにつなぐ鍵になります。
非同期処理をチェーンする実践例
擬似 API を 3 段つなげてみる
次のような「なんちゃって API 関数」を考えます。
ユーザーを取得する fetchUser
投稿を取得する fetchPosts(userId)
コメントを取得する fetchComments(postId)
いずれも Promise を返すものとします。
function fetchUser() {
return new Promise((resolve) => {
setTimeout(() => {
console.log("ユーザー取得");
resolve({ id: 1, name: "Taro" });
}, 500);
});
}
function fetchPosts(userId) {
return new Promise((resolve) => {
setTimeout(() => {
console.log("投稿取得 userId:", userId);
resolve([{ id: 10, title: "最初の投稿" }]);
}, 500);
});
}
function fetchComments(postId) {
return new Promise((resolve) => {
setTimeout(() => {
console.log("コメント取得 postId:", postId);
resolve(["いいね", "すごい"]);
}, 500);
});
}
JavaScriptこれをチェーン処理でつなげてみます。
fetchUser()
.then((user) => {
console.log("then1 user:", user);
return fetchPosts(user.id); // Promise を return
})
.then((posts) => {
console.log("then2 posts:", posts);
return fetchComments(posts[0].id); // Promise を return
})
.then((comments) => {
console.log("then3 comments:", comments);
})
.catch((err) => {
console.error("どこかで失敗:", err);
});
JavaScript流れを丁寧に言葉で追うとこうです。
fetchUser() がユーザー情報で成功する
→ その結果 user を 1つ目の then が受け取る
→ そこで fetchPosts(user.id)(Promise)を return
→ チェーンはその Promise が終わるのを待つ
その Promise(fetchPosts)が成功すると、
→ 2つ目の then が posts を受け取る
→ そこで fetchComments(posts[0].id)(Promise)を return
→ またその Promise が終わるのを待つ
最後に fetchComments が成功すると、
→ 3つ目の then が comments を受け取ってログを出す
ここが重要です。
「return した Promise の完了を、次の then が自動的に待ってくれる」
これがチェーン処理の核心です。
自分で「コールバックの中で次の非同期処理を呼んで…」とネストを書く必要がなくなります。
コールバック地獄との違いを肌で感じる
ネスト版とチェーン版を見比べてみる
さきほどの処理を「コールバック地獄風」に書くとこうなります(イメージ)。
fetchUserCallback((err, user) => {
if (err) { /* エラー処理 */ return; }
fetchPostsCallback(user.id, (err, posts) => {
if (err) { /* エラー処理 */ return; }
fetchCommentsCallback(posts[0].id, (err, comments) => {
if (err) { /* エラー処理 */ return; }
console.log("全部そろった:", user, posts, comments);
});
});
});
JavaScript横にずれていく「ピラミッド」になっているのが分かります。
一方、Promise のチェーン版ではこうでした。
fetchUser()
.then((user) => fetchPosts(user.id))
.then((posts) => fetchComments(posts[0].id))
.then((comments) => {
console.log("全部そろった:", comments);
})
.catch((err) => {
console.error("どこかで失敗:", err);
});
JavaScriptこちらは縦にスッと読めます。
ユーザーを取得
→ 投稿を取得
→ コメントを取得
→ 最後に全部そろった
というストーリーが、
コードの形とほぼ 1 対 1 で対応しています。
ここが重要です。
Promise チェーンは、「時間の順番」を「縦方向のコード」に変換する仕組み です。
これによって、頭の中でのシミュレーションがかなり楽になります。
チェーンの中で「値を変換する」パターン
非同期だけじゃなく、途中でデータを加工できる
then の中では、必ずしも次の非同期処理を返す必要はありません。
普通に値を加工して返すだけでも構いません。
fetchUser()
.then((user) => {
return {
...user,
displayName: user.name + "さん",
};
})
.then((userWithDisplayName) => {
console.log(userWithDisplayName.displayName); // "Taroさん"
});
JavaScriptここでは、
1つ目の then:ユーザーを受け取って displayName を追加した新しいオブジェクトを返す
2つ目の then:その加工済みオブジェクトを受け取る
というふうに、「値を変換しながら流している」イメージです。
then の中で return しないとどうなるか
初心者がよくやるミスも触れておきます。
fetchUser()
.then((user) => {
console.log(user);
fetchPosts(user.id); // これ、return していない
})
.then((posts) => {
// ここでは posts は undefined になる
console.log(posts);
});
JavaScript1つ目の then の中で fetchPosts(user.id) を呼んでいますが、
それを return していないため、
「何も返さない」=「undefined を返す」
→ 次の then には undefined が渡る
という動きになります。
「非同期処理をチェーンしたい」場合は、
「then の最後で必ず return する」
というクセをつけておくと安全です。
チェーンとエラーハンドリングの関係
どこか 1 箇所で catch すればよい
Promise チェーンの大きなメリットの一つは、
途中のどこで失敗しても、最後の catch でまとめて扱える ことです。
fetchUser()
.then((user) => {
return fetchPosts(user.id);
})
.then((posts) => {
return fetchComments(posts[0].id);
})
.then((comments) => {
console.log("成功:", comments);
})
.catch((err) => {
console.error("どこかで失敗:", err);
});
JavaScriptここでは、
fetchUser が reject した
fetchPosts が reject した
fetchComments が reject した
どこかの then の中で throw した
どのパターンでも、最後の catch に流れてきます。
コールバック時代のように、
各階層で if (err) { ... } を書きまくる必要がなくなり、
コードの「正常系の流れ」に集中しやすくなります。
ここが重要です。
「正常系は then の縦の線で、異常系は最後の catch」という分離ができるのが、チェーンとエラーハンドリングの組み合わせの強み です。
初心者としての「チェーン処理」練習のすすめ方
シンプルなステップから自分で書いてみる
頭で分かるだけでなく、手で書いてみると一気に定着します。
例えばこんな流れの練習がおすすめです。
一つ目に、「数値を順番に加工する」チェーン。
1 から始めて
→ then で 2 倍
→ then で 3 足す
→ then でログに出す
など。
Promise.resolve(1)
.then((v) => v * 2)
.then((v) => v + 3)
.then((v) => {
console.log(v); // 5
});
JavaScript二つ目に、「setTimeout を使った疑似非同期」をつなげる。
function wait(ms, value) {
return new Promise((resolve) => {
setTimeout(() => {
console.log(`${ms}ms 後:`, value);
resolve(value);
}, ms);
});
}
wait(500, "A")
.then(() => wait(500, "B"))
.then(() => wait(500, "C"))
.then(() => {
console.log("全部終わり");
});
JavaScriptこういう小さなチェーンを何本か書いてみると、
「then で返したものが次に渡る」
という感覚が体に入ってきます。
まとめ:チェーン処理をどう捉えるか
Promise のチェーン処理を、一言で言い直すとこうなります。
「Promise が返す“結果のバトン”を、then を使って次々に渡していきながら、非同期処理をネストせずに縦に並べる書き方」 です。
押さえておいてほしいポイントは、
then は常に「新しい Promise」を返す
then の中で return した値や Promise が、「次の then」にそのまま渡る
これにより、「A のあと B、そのあと C」という時間の順番を、コード上では「縦のストーリー」として書ける
エラーは最後の catch でまとめて処理できるので、正常系のチェーンがとても読みやすくなる
まずは、
1→2→3 と数を変えていくシンプルなチェーン
setTimeout を使った簡単な擬似 API のチェーン
あたりから「自分の手」で書いてみてください。
その感覚がつかめたとき、
「Promise の一番おいしいところ(チェーンで非同期を直線化する)」が、
きっと腑に落ちているはずです。
