JavaScriptの非同期処理を扱う際、Promiseは欠かせない存在です。特に .then() と .finally() メソッドは頻繁に使用されますが、一見似ているように見えるこれらのメソッドには重要な違いがあります。この記事では、実際のコード開発で気をつけるべき両者の違いと適切な使い分けについて解説します。
疑問の前提
Promiseチェーンを扱っていると、以下のような疑問が生じることがあります:
-
.then()
と.finally()
の根本的な違いは何か? - 「
.finally()
は正確な.then(f, f)
のエイリアスではない」とはどういう意味か? - Promise チェーンにおける値の受け渡しがどのように行われるのか?
.then()
と .finally()
の本質的な違い
チェーンにおける役割の違い
-
.then()
の本質的な役割:-
.then()
は Promise チェーンの「変換器」として機能します - 前の Promise の結果を受け取り、新しい値や Promise を生成します
- return 文がなくても、暗黙的に undefined を返すため、次の Promise の値は undefined になります
-
-
.finally()
の本質的な役割:-
.finally()
は Promise チェーンの「通過点」として機能します - 前の Promise の結果に影響を与えず、単にサイドエフェクト(副作用)を実行するためのものです
- return 文があってもなくても、元の Promise の結果がそのまま次に渡されます
// thenは値を変換する Promise.resolve(1) .then(x => x * 2) // return は x * 2,次のresultになる .then(result => console.log(result)); // 2 // finallyは値を変更しない Promise.resolve(1) .finally(() => 999) // 戻り値は無視される .then(result => console.log(result)); // 1(元の値)
-
値の透過性
.finally()
の最も重要な特性は「透過性」です。これは元のPromiseの状態と値をそのまま次のハンドラに渡すことを意味します。
上述の .finally()
は Promise チェーンに影響を及ぼさないことと同じです。
// 成功の場合も失敗の場合も、finallyは値を変更せずに通過させる
Promise.resolve("成功データ")
.finally(() => {
console.log("処理完了");
return "別の値"; // この戻り値は無視される
})
.then(result => console.log(result)); // "成功データ"(元の値)
Promise.reject(new Error("エラー発生"))
.finally(() => {
console.log("処理完了");
// エラーをキャッチしない
})
.catch(error => console.log(error.message)); // "エラー発生"(元のエラー)
逆に .then()
で以下のように return を省略した場合はどうなるでしょう。
これは .finally()
とは異なります。.finally()
は元の値をそのまま通過させますが、then は必ず何らかの値(明示的に返さない場合は undefined)を次に渡します。
Promise.resolve(1)
.then(result => {
console.log(result); // 1
// return文がない = 暗黙的にundefinedを返す
})
.then(result => {
console.log(result); // undefined (前のthenの戻り値がundefined)
});
実際のコード開発で気をつけるべきポイント
1. 値の変換が必要な場合は .then()
を使う
データの加工や変換が必要な場合は、.then()
を使用します。これにより、Promiseチェーンに新しい値を導入できます。
fetch('/api/users')
.then(response => response.json()) // レスポンスをJSONに変換
.then(users => users.filter(user => user.active)) // アクティブユーザーのみをフィルタリング
.then(activeUsers => renderUserList(activeUsers)); // フィルタリングされたユーザーを表示
2. クリーンアップ処理には .finally()
を使う
処理の成否に関わらず実行すべきクリーンアップ処理には、.finally() が最適です。
function fetchUserData(userId) {
showLoadingSpinner();
return fetch(`/api/users/${userId}`)
.then(response => {
if (!response.ok) throw new Error('ユーザーデータの取得に失敗しました');
return response.json();
})
.finally(() => {
// 成功しても失敗しても必ずローディングを非表示にする
hideLoadingSpinner();
});
}
// 使用例
fetchUserData(123)
.then(userData => {
// ここでuserDataを使った処理を行う
// finallyを通過しても元のデータにアクセス可能
displayUserProfile(userData);
})
.catch(error => {
// エラー処理
showErrorMessage(error.message);
});
3. .finally() でエラーを処理しようとしない
.finally() はエラーハンドリングには適していません。エラー処理は .catch() で行うべきです。
// 良くない例
fetchData()
.finally(() => {
try {
// エラー処理を試みる(推奨されない)
} catch (e) {
console.error(e);
}
});
// 良い例
fetchData()
.catch(error => {
// 適切なエラー処理
console.error(error);
showErrorNotification(error.message);
})
.finally(() => {
// クリーンアップのみを行う
hideLoadingIndicator();
});
まとめ
.then()
と .finally()
は一見似ているように見えますが、Promiseチェーンにおける役割が根本的に異なります:
-
.then()
は値を変換し、チェーンの流れを変更するために使います -
.finally()
は値に影響を与えず、クリーンアップなどの副作用のみを実行するために使います
実際のコード開発では、この違いを理解して適切に使い分けることが重要です。特に:
- データの加工や変換には
.then()
を使用する - クリーンアップ処理には
.finally()
を使用する -
.then()
内では必要に応じて明示的に値を返す - エラー処理は
.catch()
で行い、.finally()
では行わない
これらのポイントを押さえることで、より明確で保守しやすい非同期コードを書くことができるでしょう。