JavaScript | 非同期処理:async / await – async 関数の戻り値

JavaScript JavaScript
スポンサーリンク

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 {...}
JavaScript

asyncFunc() を呼ぶと、
42 ではなく Promise オブジェクト が返ってきます。

中身としては「いつか 42 を返す Promise」という意味です。

この Promise から 42 を取り出すには、
thenawait を使います。

asyncFunc().then(value => {
  console.log(value); // 42
});

async function run() {
  const value = await asyncFunc();
  console.log(value); // 42
}
JavaScript

return の値は 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
JavaScript

async 関数も考え方は同じですが、「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); // "何かがうまくいかなかった"
  }
);
JavaScript

Promise 的に書くと、これは次とほぼ同じ意味です。

function willFailLike() {
  return Promise.reject(new Error("何かがうまくいかなかった"));
}
JavaScript

await 側では 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 が出てくる
JavaScript

getData() の戻り値は 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 になってしまう
}
JavaScript

outer の戻り値は 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 関数の戻り値に対する感覚が、グッとクリアになります。

タイトルとURLをコピーしました