async 関数の戻り値を一言でいうと
async 関数の戻り値は、
「必ず Promise になる」 です。
もう少しだけ正確に言うと:
- async を付けた関数は、必ず Promise を返す
- その Promise は、関数の
returnの値で resolve される - その関数の中で
throwされたエラーは、その Promise の reject になる
ここが重要です。
「同期関数の戻り値」と「async 関数の戻り値」は見た目は似ていても、型がまったく違う と思ってください。
async を付けた瞬間、その関数は「Promise を返す関数」に変わります。
基本①:async を付けると「Promise を返す関数」になる
普通の関数との違い
まず、普通の関数。
function normal() {
return 42;
}
const v1 = normal();
console.log(v1); // 42
JavaScript戻り値はただの数値 42 です。
次に、async 関数。
async function asyncFunc() {
return 42;
}
const v2 = asyncFunc();
console.log(v2); // Promise {...}
JavaScriptasyncFunc() を呼ぶと、42 ではなく Promise オブジェクト が返ってきます。
中身としては「いつか 42 を返す Promise」という意味です。
この Promise から 42 を取り出すには、then か await を使います。
asyncFunc().then(value => {
console.log(value); // 42
});
async function run() {
const value = await asyncFunc();
console.log(value); // 42
}
JavaScriptreturn の値は Promise.resolve されるイメージ
async 関数の中で return X と書いたら、
外側から見ると「Promise.resolve(X) を返した」のと同じです。
async function getText() {
return "hello";
}
// これはほぼ
function getTextLike() {
return Promise.resolve("hello");
}
JavaScript結果として、どちらも呼び出し側からは
「いつか ‘hello’ をくれる Promise」に見えます。
ここが重要です。
async 関数の「戻り値」は、return で返した値そのものではなく、「その値で resolve される Promise」です。
中の世界と外の世界を切り分けて、“外から見れば Promise” と覚えてください。
基本②:何も return しない async 関数の戻り値
return がないときは何が返る?
普通の関数で return を書かないと、戻り値は undefined です。
function normal() {
console.log("do something");
}
console.log(normal()); // undefined
JavaScriptasync 関数も考え方は同じですが、「Promise の中身」として undefined が返されます。
async function asyncFunc() {
console.log("do something");
}
const p = asyncFunc();
console.log(p); // Promise {...}
p.then(value => {
console.log(value); // undefined
});
JavaScriptつまり、戻り値の型としてはPromise<void>(実際には「Promise で、中身は undefined」)というイメージです。
ここが重要です。async function と書いた時点で、return があろうがなかろうが「Promise を返す関数」になる。
何も返さなくても Promise<undefined> が戻り値、という意識を持っておくと混乱しません。
基本③:async 関数内の throw は「Promise の reject」になる
throw と Promise.reject の関係
async 関数の中で throw した場合、そのエラーは Promise の reject になります。
async function willFail() {
throw new Error("何かがうまくいかなかった");
}
const p = willFail();
console.log(p); // Promise {...}
p.then(
value => {
console.log("成功:", value); // 呼ばれない
},
err => {
console.log("失敗:", err.message); // "何かがうまくいかなかった"
}
);
JavaScriptPromise 的に書くと、これは次とほぼ同じ意味です。
function willFailLike() {
return Promise.reject(new Error("何かがうまくいかなかった"));
}
JavaScriptawait 側では try / catch で受け止められる
await を使う側から見れば、
そのエラーは「そこで throw された」のと同じです。
async function run() {
try {
const result = await willFail();
console.log("成功:", result);
} catch (err) {
console.log("catch:", err.message); // "何かがうまくいかなかった"
}
}
JavaScriptここが重要です。
async 関数の戻り値の Promise は、「成功時は return の値で resolve」「失敗時は throw のエラーで reject」という二本立て。
つまり、同期関数の return / throw が、そのまま Promise の resolve / reject に対応している、と思ってください。
まとめてイメージする:async 関数の「内側」と「外側」
内側(async 関数の中)で見ている世界
async 関数の中では、普通の値と await で「中身を取り出した値」だけを扱います。
async function fetchUserName() {
const user = await fetchUser(); // Promise<User> を await → User
return user.name; // 戻り値は string
}
JavaScriptここだけ見ると、
「User を取って name を返す、シンプルな関数」のように見えます。
外側(呼び出す側)から見た世界
しかし、呼び出す側から見ると、fetchUserName() の戻り値は「string ではなく Promise<string>」です。
const p = fetchUserName();
console.log(p); // Promise<string> な感じ
// 中身が欲しければ await か then
async function run() {
const name = await fetchUserName(); // name は string
console.log(name);
}
JavaScript型の目線でいうと、
- async 関数の中で
return T - 外から見た戻り値は
Promise<T>
という変換が常に起きています。
ここが重要です。
async 関数は「中は普通のコード、外は Promise の世界」という二重構造になっています。
中では return / throw で考え、外からは resolve / reject(await / catch)で扱う。
この切り替えが自然にできるようになると、非同期処理がかなりクリアになります。
具体例で戻り値の流れを確認する
例1:数値を 2 倍にして返す async 関数
async function doubleAsync(x) {
return x * 2;
}
JavaScriptこのとき:
- 関数の中(定義する側)の感覚:
「数値を受け取って、2 倍を返す関数」 - 外(呼び出す側)から見ると:
「数値を受け取って、2 倍を resolve する Promise を返す関数」
実際の使い方:
doubleAsync(10).then(value => {
console.log(value); // 20
});
async function run() {
const result = await doubleAsync(10);
console.log(result); // 20
}
JavaScript例2:失敗する可能性がある async 関数
async function fetchUserOrFail(id) {
if (!id) {
throw new Error("id が必要です");
}
const user = await fetchUserById(id);
return user;
}
JavaScriptこのとき:
- id が falsy のとき → Promise は reject(Error)
- 正しい id のとき → Promise は resolve(user オブジェクト)
呼び出す側:
async function run() {
try {
const user = await fetchUserOrFail(0);
console.log("成功:", user);
} catch (err) {
console.log("エラー:", err.message); // "id が必要です"
}
}
JavaScriptここが重要です。
「戻り値としてどんな Promise を返したいか」が、async 関数の設計そのものです。
成功時に返す型(T)と、失敗時に投げるエラーの種類を意識して設計できるようになると、
async 関数の戻り値を“信頼して使える” ようになります。
async 関数の戻り値でよくある勘違いと注意点
勘違い①:async 関数の戻り値を「普通の値」として使おうとする
こんなコード、見覚えありませんか。
async function getData() {
const res = await fetch(...);
return res.json();
}
const data = getData(); // ここで data を使おうとしてしまう
console.log(data); // Promise が出てくる
JavaScriptgetData() の戻り値は Promise なので、
そのまま data を使おうとしても中身はまだ取れていません。
正しくはこうです。
async function run() {
const data = await getData();
console.log(data);
}
JavaScriptまたは
getData().then(data => {
console.log(data);
});
JavaScript勘違い②:async 関数の戻り値を return して「上に伝えたつもり」になる
async function inner() {
return 42;
}
function outer() {
const v = inner();
return v; // outer の戻り値も Promise になってしまう
}
JavaScriptouter の戻り値は Promise<number> です。
呼び出し側が await outer() しない限り、中身は取れません。
「外側も async にしてしまう」のが素直です。
async function outer() {
const v = await inner();
return v * 2;
}
JavaScriptこうすると、
- inner の戻り値:Promise<number>
- outer の戻り値:Promise<number>(中身は 84)
という形になり、await outer() で最終結果を取れるようになります。
ここが重要です。
「戻り値が Promise になる」というルールを忘れて「普通の値だろう」と思い込むと、
“Promise が出てきたけどよく分からない” という状態になります。
async 関数を一段追加したら、「その戻り値は Promise になった」と必ず意識して確認してみてください。
初心者として「async 関数の戻り値」で本当に押さえてほしいこと
async を付けた関数は、必ず Promise を返す。
中で return 値 と書いても、外から見れば「値で resolve される Promise」。
何も return しない async 関数も、戻り値は Promise<undefined> のイメージ。
async 関数の中で throw されたエラーは、その Promise の reject になる。
await 側では try / catch で受け止める。
async 関数の戻り値を普通の値として使いたいなら、呼び出し側で必ず await する(または then を使う)。
「箱(Promise)」のまま扱うのか、「中身(値)」を取り出すのかを意識して使い分ける。
ここが重要です。
async 関数の戻り値は、「非同期処理を一つの Promise にパッケージしたもの」です。
自分で async 関数を書くときは、
“この関数を await したとき、呼び出し側から見てどんな値(型)が返ってきてほしいか?”
を先に決めてから、中身の実装を組み立ててみてください。
そうすると、戻り値の Promise が“ただの仕様”ではなく、“あなたが設計したインターフェイス”になっていきます。
もし練習したくなったら、こんな課題をやってみるといいです。
// 1. 数字を受け取り、その2倍を resolve する async 関数 doubleAsync を作る。
// 2. doubleAsync を await して4倍を返す async 関数 quadrupleAsync を作る。
// 3. await quadrupleAsync(5) の戻り値が何になるか、自分で予想してから console.log で確認する。
// 4. 途中のどの時点で「Promise」を扱い、どの時点で「ただの数値」を扱っているかを、コメントに書き込んでみる。
JavaScript“どこまでが Promise の世界で、どこからが普通の値の世界か” を意識して線を引いてみると、
async 関数の戻り値に対する感覚が、グッとクリアになります。
