はじめに|この記事で得られる価値
「async/awaitは普段から使ってるけど、Promiseの型定義がなんとなく“any”頼み…。
エラーハンドリングも自己流で、後輩レビューのたびにソワソワ…。」
こんな“開発者あるある”、身に覚えありませんか?
私たちも現場で同じようにモヤモヤしてきました。
本記事では、その悩みを“一緒に”整理し、自信を持って使いこなせるPromiseの型・使い分け・アンチパターン回避まで、図とコード例で伴走します。
1. Promiseって何?— async/awaitの“裏側”で動く非同期処理の正体
■ 用語と仕組み:「Promise」とは?
「async/await使えてるのに、なぜPromiseを意識しないといけないの?」そんな疑問、よく聞きます。
Promiseとは、ざっくり言えば「非同期処理の結果を持つ“箱”」。
例えばAPIデータの取得やファイル操作――“今は結果がないけど、未来に値が入る”そんな箱です。
(非同期処理の基礎やJavaScriptの実例については『非同期処理とは?JavaScriptとJavaの実例でわかる使い方・落とし穴・解決法』をご参照ください)
用語解説:Promise
JavaScriptで非同期処理の「成功・失敗・進行中」の状態や結果を管理するための仕組み。API通信やファイル読み込みなど、すぐに結果が出ない処理の「完了」や「失敗」を後から受け取れる“箱”のような存在。用語解説:非同期処理
プログラムの実行中に、他の処理を待たずに進める仕組み。例えば「データ取得が終わるまで他の作業も同時進行できる」イメージ。用語解説:API
他のサービスやシステムとデータをやり取りするための「窓口」や「取り決め」。
- pending(待機中)…非同期処理の最中
- fulfilled(成功)…無事に値が入った状態
- rejected(失敗)…エラー(理由)が入った状態
一度「fulfilled」か「rejected」になれば結果は不変です。
ここでポイント。
async/awaitはPromiseの“糖衣構文”、つまり本質はPromise。直感的に書けるだけで、裏側は全てPromiseが制御しています。
用語解説:async/await
JavaScriptでPromiseを「直感的・簡単」に書くための文法。asyncを付けた関数は必ずPromiseを返し、awaitで「Promiseの完了を待つ」ことができる。用語解説:糖衣構文(シンタックスシュガー)
本質的な動作は同じだが、より分かりやすく・書きやすくするための文法上の工夫。
// async関数は必ずPromiseを返します
async function fetchData(): Promise<string> {
return "データ";
}
// 上は内部的にPromise.resolve("データ")と同じ意味
複雑な非同期制御やエラー処理をちゃんとしたいなら、Promiseの正体から理解しておくのが近道です。
(async/awaitのバグや使い分けについては『async関数でthenが使えない?原因と対策を完全解説』や『JavaScript thisの違い・バグ事例7選|アロー関数やbindの迷わない使い分け』も参考になります)
2. any撲滅!Promiseの型定義3パターン
■ 正しい型定義で「型安全」と保守性UP
「ついPromiseの型定義がanyに…。型安全、どこいった?」
この悩み、私たちも通りました。
TypeScriptでPromiseを扱う際は「正しい型定義」が最重要。anyを安易に使えば型安全は崩壊します。
(TypeScriptの型や開発環境の始め方は『TypeScriptとは?JavaScriptとの違いを初心者向けにわかりやすく解説』や『TypeScriptの始め方|Node.jsとVSCodeで学ぶ開発環境構築ガイド【初心者向け完全解説】』もご参照ください)
用語解説:型安全
プログラム中で「値の種類(型)」が正しく守られている状態。型が合わないミスを事前に防げる。用語解説:any型
TypeScriptで「どんな型でもOK」として扱う危険な型。型安全が失われる原因。用語解説:TypeScript
JavaScriptに「型」の仕組みを加えた言語。大規模開発や保守性向上に役立つ。
■ 基本:Promise<T>で型を明示する
成功時に返す値の型は、Promise<T>で指定します。
Tにはstringやnumber、自作のinterfaceなどOK。
用語解説:interface
TypeScriptで「オブジェクトの形(プロパティや型)」を定義する仕組み。型チェックや自動補完に役立つ。
interface User {
id: number;
name: string;
}
function fetchUser(userId: string): Promise<User> {
return new Promise((resolve, reject) => {
const user: User = { id: 1, name: "Taro Yamada" };
if (user) resolve(user);
else reject(new Error("User not found."));
});
}
fetchUser("1").then(user => {
console.log(user.name); // userはUser型で推論
});
■ async関数は返り値の型推論にお任せ
interface Product {
id: string;
price: number;
}
// 型注釈なしでもOK
async function fetchProduct(productId: string) {
const response = await fetch(`/api/products/${productId}`);
if (!response.ok) throw new Error("Network response was not ok");
return await response.json() as Product;
}
// 型を明示して堅牢にする場合
async function fetchProductExplicit(productId: string): Promise<Product> {
return { id: productId, price: 1000 };
}
■ catchのエラー型はunknown+型ガードが鉄則
async function execute() {
try {
throw "文字列エラー";
} catch (error: unknown) {
console.error(getErrorMessage(error));
}
}
function getErrorMessage(error: unknown): string {
if (error instanceof Error) return error.message;
if (typeof error === 'string') return error;
return 'An unknown error occurred.';
}
unknown型+型ガードで予期せぬエラーにも安心対応!
(JavaScriptのよくあるエラーや原因・対策は『JavaScriptでよく発生するエラー「ReferenceError」「SyntaxError」などの原因と対策を具体例付きで解説』も参考になります)
用語解説:unknown型
TypeScriptで「どんな型か分からない」ことを明示する型。anyより安全で、使うときは型チェック(型ガード)が必須。用語解説:型ガード
値の型をif文などで判定し、安全に使うためのテクニック。
3. 静的メソッドの使い分けフローチャート—all, allSettled, race, anyの違いは?
■ 複数の非同期処理、どうまとめる?
Promiseには用途ごとに使い分けるべき静的メソッドが揃っています。
用語解説:静的メソッド
クラスやオブジェクトに属する「インスタンス化せずに使える」関数。Promise.allなどは直接Promiseから呼び出せる。
1. 全部“成功”前提ならPromise.all
「全部のAPIが揃わないとダメ!」ならPromise.allです。
一つでも失敗(rejected)すると、全体が即失敗になるので注意。
(APIの基礎や現場での活用法は『APIとは?SES現場で役立つ基礎〜Postman活用まで完全ガイド』もご参照ください)
用語解説:Promise.all
複数のPromise(非同期処理)をまとめて実行し、全部成功したら結果を配列で返す。1つでも失敗すると全体が失敗になる。
async function getDashboardData() {
try {
const [user, posts, settings] = await Promise.all([
fetch('/api/user'),
fetch('/api/posts'),
fetch('/api/settings')
]);
console.log('All data loaded!');
} catch (error) {
console.error("Failed to load one of the resources:", error);
}
}
2. 一部失敗OK・全部の結果が欲しいならallSettled
用語解説:Promise.allSettled
複数のPromiseをまとめて実行し、全部の「成功・失敗」結果を配列で返す。失敗があっても全体は止まらない。
async function checkServiceStatus() {
const services = ['/api/auth', '/api/payment', '/api/invalid-endpoint'];
const results = await Promise.allSettled(
services.map(url => fetch(url))
);
results.forEach((result, index) => {
if (result.status === 'fulfilled') {
console.log(`Service ${services[index]} is OK.`);
} else {
console.error(`Service ${services[index]} is down:`, result.reason);
}
});
}
3. 一番早い結果だけ採用したいならrace
用語解説:Promise.race
複数のPromiseのうち、一番早く完了したものの結果だけを返す。残りは無視される。
function requestWithTimeout(url: string, timeout: number): Promise<Response> {
const timeoutPromise = new Promise<never>((_, reject) =>
setTimeout(() => reject(new Error('Request timed out')), timeout)
);
return Promise.race([fetch(url), timeoutPromise]);
}
requestWithTimeout('/api/heavy-process', 5000)
.then(response => console.log('Got response:', response))
.catch(error => console.error(error.message));
4. どれか一つ“成功”でいいならany
用語解説:Promise.any
複数のPromiseのうち、どれか1つでも成功したらその結果を返す。全部失敗した場合だけエラーになる。
async function fetchFromFastestCDN() {
const cdnEndpoints = [
'https://cdn1.example.com/resource',
'https://cdn2.example.com/resource',
'https://cdn3.example.com/resource'
];
try {
const fastestResponse = await Promise.any(
cdnEndpoints.map(url => fetch(url))
);
console.log('Fetched from fastest CDN:', fastestResponse.url);
} catch (error) {
console.error('All CDNs are unavailable.', error);
}
}
4. コードレビューで指摘したい!Promiseアンチパターン5選
■ 「やりがち」NG例とOK例をセットで
用語解説:アンチパターン
よくある「やってはいけない書き方」や「失敗しやすい実装例」。なぜNGか理由も理解しておくと良い。用語解説:コードレビュー
他の開発者がソースコードをチェックし、ミスや改善点を指摘する工程。品質向上や学び合いに役立つ。
-
new Promiseの中でasync/awaitを使う
async関数はそれ自体がPromise。内部でnew Promiseは冗長です。
NG例:function ngFetch(): Promise<string> { return new Promise(async (resolve, reject) => { try { const response = await fetch('/api/data'); resolve(await response.text()); } catch (e) { reject(e); } }); }OK例:
async function okFetch(): Promise<string> { const response = await fetch('/api/data'); return response.text(); } -
ループ内でawait、並列化できてない
awaitをループ内で都度実行すると、直列処理に…。
NG例:async function ngFetchUsers(ids: string[]) { const users = []; for (const id of ids) { users.push(await fetch(`/api/users/${id}`)); } return Promise.all(users); }OK例:
async function okFetchUsers(ids: string[]) { const userPromises = ids.map(id => fetch(`/api/users/${id}`)); return await Promise.all(userPromises); } -
.then()のreturn漏れ
.then()の中でreturnを書き忘れると、次の.then()に値が渡りません。
NG例:fetch('/api/user/1') .then(response => response.json()) .then(user => { console.log(user.name); // returnを書き忘れ }) .then(result => { console.log(result); // undefined });OK例:
fetch('/api/user/1') .then(response => response.json()) .then(user => { console.log(user.name); return user.id; // 明示的にreturn }) .then(userId => { console.log(userId); }); -
async関数なのにPromiseを返してしまう
asyncをつけた時点でPromise化されるので、Promise.resolveで包むのは冗長。
NG例:async function ngGetValue(): Promise<string> { return Promise.resolve('value'); }OK例:
async function okGetValue(): Promise<string> { return 'value'; } -
try-catchや.catch()でエラーを握りつぶす
エラー未処理のPromiseは“unhandled promise rejection”となり、想定外のバグ要因に。
NG例:async function mightFail() { throw new Error('Something went wrong'); } mightFail(); // エラーキャッチしていないOK例:
async function main() { try { await mightFail(); } catch (e) { console.error("Caught an error:", e); } } mightFail().catch(e => console.error("Caught an error:", e)); main();
5. まとめ|「なぜそう書くか?」まで腹落ちしたPromise設計を
- Promiseは「非同期の状態を持つ箱」
- async/awaitはPromiseを扱いやすくする糖衣構文
- Promise<T>型、型推論、unknown型ガードで“any”を撃退
- all, allSettled, race, anyはユースケースごとに適切に選択
- アンチパターンを把握して、保守性の高いコードを意識
用語解説:保守性
コードが「直しやすい」「拡張しやすい」など、長期的に管理しやすい性質。用語解説:ユースケース
どんな場面・目的で使うかという具体的な利用例や想定シナリオ。
“なんとなく書ける”から“なぜそう書くべきか”が腹落ちすれば、
あなたのコード品質も、チームの生産性もグンと高まります。
ぜひコードをコピペして、まずは動かしてみてください。
FAQ|よくある疑問に回答
-
Q.
Promise<void>はどんなとき使う?
A. 返り値が不要な非同期処理で使います。例:DB更新API呼び出しなど「完了だけ待てばOK」なとき。 -
Q. .then()の中でfetchを呼ぶのはNG?
A. 必ずしもNGではありませんが、ネストが深くなるならasync/awaitでフラットに書き直すと可読性UPです。 -
Q. async関数にtry-catchなしだとどうなる?
A. エラー未捕捉のPromiseは「unhandled promise rejection」となり、ブラウザならコンソールエラー、Node.jsならバージョン次第でプロセスクラッシュも。必ずどこかでキャッチしましょう。 -
Q. Promise.resolve()やPromise.reject()はいつ使う?
A. テストやモック、値を強制的にPromise化したい時など。「同期値をPromiseチェーンに載せたい」場面で便利です。
用語解説:void型
「何も値を返さない」ことを明示する型。Promiseは「完了したらOK」という非同期処理で使う。用語解説:ネスト
if文や関数などが「入れ子」になって深くなること。深いと読みにくくバグの温床になる。用語解説:unhandled promise rejection
Promiseのエラーがどこにも捕捉されず放置された状態。バグや予期せぬ動作の原因になる。用語解説:モック
テスト用に本物の代わりとなる「ダミーの処理やデータ」を用意すること。