TypeScript / LESSON 14 / TS14

TypeScript:非同期処理と型

1学習の目的

2基礎解説

やりたいこと書き方意味
非同期関数の戻り値async function f(): Promise<number>必ず Promise で包む
結果を取り出すconst n: number = await f()await で中身が出る
並列実行await Promise.all([a(), b()])それぞれの型が保たれる
エラーの型catch (e) { … }e は unknown
✅ 覚えるべき重要ポイント
  1. async 関数の戻り値は必ず Promise<T>return 42 と書いても、型は Promise<number> になる。
  2. ⭐ await を書き忘れると型エラーになるconst n = fetchNumber(); のあと n * 2 と書けば、「Promise は掛け算できない」と止められる。JS15で [object Promise] に悩まされた問題が、ここで解決する
  3. Promise.all は各要素の型を保つ[Promise<number>, Promise<string>] を渡せば、結果は [number, string] になる。
  4. catch で受け取る eunknowne.message と直接書けないので、e instanceof Error で確認してから使う(TS09の型ガード)。
  5. ジェネリクスと組み合わせると、「どんな型でも安全に扱える非同期処理」が書ける(TS10)。

現場使用例:APIからのデータ取得、ファイルの読み込み、データベースへの問い合わせ、複数の処理の並列実行。

// 戻り値は Promise<T> になる
async function fetchPrice(): Promise<number> {
  return 128000;
}

async function main() {
  // ✅ await で中身を取り出す
  const price = await fetchPrice();      // number
  console.log(price * 2);

  // ❌ await を忘れると Promise のまま → エラーになる
  const bad = fetchPrice();              // Promise<number>
  // console.log(bad * 2);
  // → The left-hand side of an arithmetic operation must be of type 'number'...

  // 並列実行(それぞれの型が保たれる)
  const [n, s] = await Promise.all([
    fetchPrice(),                        // Promise<number>
    Promise.resolve("文字列"),            // Promise<string>
  ]);
  console.log(n, s);                     // number, string

  // エラー処理(e は unknown なので確認が必要)
  try {
    await Promise.reject(new Error("通信失敗"));
  } catch (e) {
    console.log(e instanceof Error ? e.message : "不明なエラー");
  }
}

main();

3基本ドリル(10問)

TS14-D01 数値 42 を返す async 関数 getValue を戻り値の型注釈付きで定義し、await で受け取って2倍にして出力せよ。 コーディング★☆☆無料
期待される結果

出力 == 84

ヒント

async function getValue(): Promise { return 42; } と書く。main を作って await する。

模範解答
async function getValue(): Promise<number> {
  return 42;
}

async function main() {
  const value = await getValue();
  console.log(value * 2);
}

main();
解説

戻り値の型は Promise<number>。中身が number でも、async を付けた時点で Promise に包まれる。

TS14-D02 await を書き忘れて const n = getValue(); としたあと n * 2 と書くとどうなるか。
A. NaN になる B. 実行前にエラーになる C. [object Promise] が出る D. 問題ない
選択★☆☆無料
期待される結果

解答 == B

ヒント

Promise は掛け算できるか。

模範解答
B
解説

JS15で最も悩まされた落とし穴が、ここで解決する。JavaScriptなら [object Promise] が表示されて初めて気づいたが、TypeScriptは書いた時点で止めてくれる。

TS14-D03 文字列を返す async 関数 getName("田中")を定義し、await で受け取って「こんにちは、田中さん」と出力せよ。 コーディング★☆☆無料
期待される結果

出力 == こんにちは、田中さん

ヒント

戻り値の型は Promise

模範解答
async function getName(): Promise<string> {
  return "田中";
}

async function main() {
  const name = await getName();
  console.log(`こんにちは、${name}さん`);
}

main();
解説

await した結果は string として扱える。テンプレートリテラルに埋め込んでも安全。

TS14-D04 非同期関数の戻り値の型を補ってコードを完成させよ。 穴埋め★☆☆無料
コード
async function getCount(): ____<number> {
  return 10;
}

async function main() {
  console.log(await getCount());
}

main();
期待される結果

出力 == 10

ヒント

非同期の結果を包む型。先頭大文字で7文字。

模範解答
Promise
解説

async を付けたら戻り値は必ず Promise で包む: number と書くとエラーになる。

TS14-D05 catch (e) で受け取る e の型はどれか(厳格モード)。
A. Error B. unknown C. any D. string
選択★☆☆無料
期待される結果

解答 == B

ヒント

何が投げられるか分からない。

模範解答
B
解説

JavaScriptでは Error 以外も throw できるので、TypeScriptは unknown として扱う。e.message と書く前に確認が必要(TS09)。

TS14-D06 数値と文字列を返す2つの async 関数を Promise.all で並列実行し、「42 / 田中」の形式で出力せよ。 コーディング★★☆無料
期待される結果

出力 == 42 / 田中

ヒント

const [n, s] = await Promise.all([getNum(), getName()]); と書く。それぞれの型が保たれる。

模範解答
async function getNum(): Promise<number> {
  return 42;
}

async function getName(): Promise<string> {
  return "田中";
}

async function main() {
  const [num, name] = await Promise.all([getNum(), getName()]);
  console.log(`${num} / ${name}`);
}

main();
解説

Promise.all は要素ごとの型を保つnum は number、name は string として正しく推論される。

TS14-D07 エラーの型を確認するコードを完成させよ。 穴埋め★★☆無料
コード
async function main() {
  try {
    throw new Error("失敗しました");
  } catch (e) {
    console.log(e ____ Error ? e.message : "不明");
  }
}

main();
期待される結果

出力 == 失敗しました

ヒント

「このクラスのインスタンスか」を判定する演算子。10文字。

模範解答
instanceof
解説

unknown のままでは message にアクセスできない。instanceof で Error だと確認して初めて、そのプロパティが使える。

TS14-D08 async function f(): Promise<number> の中で return "文字列"; と書くとどうなるか。
A. 自動変換される B. エラーになる C. Promise<string> になる D. 問題ない
選択★★☆無料
期待される結果

解答 == B

ヒント

宣言した型と返す値が一致しているか。

模範解答
B
解説

Promise の中身の型もチェックされる。「数値を返す」と宣言したのに文字列を返せば、その場でエラーになる。

TS14-D09 商品オブジェクト(name, price)を返す async 関数 fetchProduct を型注釈付きで定義し、await して「ノートPC:128,000円」と出力せよ。 コーディング★★☆無料
期待される結果

出力 == ノートPC:128,000円

ヒント

戻り値の型は Promise のように、型を定義してから使う。

模範解答
type Product = { name: string; price: number };

async function fetchProduct(): Promise<Product> {
  return { name: "ノートPC", price: 128000 };
}

async function main() {
  const p = await fetchProduct();
  console.log(`${p.name}:${p.price.toLocaleString()}円`);
}

main();
解説

await した結果は Product として扱えるp.nmae のような打ち間違いも、その場で検出される。

TS14-D10 任意の値を受け取り、そのまま Promise で返すジェネリックなasync 関数 wrap を定義せよ。数値42と文字列"あ"で試し、2行出力すること。 コーディング★★☆無料
期待される結果

出力2行。42 / あ

ヒント

async function wrap(value: T): Promise { return value; } と書く。

模範解答
async function wrap<T>(value: T): Promise<T> {
  return value;
}

async function main() {
  console.log(await wrap(42));
  console.log(await wrap("あ"));
}

main();
解説

ジェネリクスと非同期の組み合わせ。入れた型がそのまま出てくるので、any を使わずに汎用的な非同期処理が書ける。

4実践シナリオ(5問)

TS14-S01 【擬似API】商品IDを受け取り、商品を返す async 関数 fetchProduct を定義せよ(id=1なら「ノートPC」、それ以外は null)。id=1 と id=99 で試し、「ノートPC:128,000円」「該当なし」と2行出力すること。 コーディング★★☆無料
期待される結果

出力2行が完全一致

ヒント

戻り値の型は Promise。null チェックが必要になる。

模範解答
type Product = { name: string; price: number };

async function fetchProduct(id: number): Promise<Product | null> {
  return id === 1 ? { name: "ノートPC", price: 128000 } : null;
}

async function main() {
  for (const id of [1, 99]) {
    const p = await fetchProduct(id);
    console.log(
      p ? `${p.name}:${p.price.toLocaleString()}円` : "該当なし"
    );
  }
}

main();
解説

Promise<Product | null> で「見つからない可能性」を型で表現している。呼び出し側は null チェックを迫られるので、確認漏れが起きない。

TS14-S02 【並列取得】商品名と在庫数を別々の async 関数で取得し、Promise.all で並列に受け取って「ノートPC:在庫5個」と出力せよ。 コーディング★★☆無料
期待される結果

出力 == ノートPC:在庫5個

ヒント

Promise.all の結果を分割代入で受け取る。型は [string, number] になる。

模範解答
async function fetchName(): Promise<string> {
  return "ノートPC";
}

async function fetchStock(): Promise<number> {
  return 5;
}

async function main() {
  const [name, stock] = await Promise.all([fetchName(), fetchStock()]);
  console.log(`${name}:在庫${stock}個`);
}

main();
解説

それぞれの型が正しく保たれているname は string、stock は number——順序を取り違えれば型エラーになる。

TS14-S03 【エラー処理】必ず失敗する async 関数を try/catch で受け止め、「エラー:通信に失敗しました」と出力せよ(e の型を確認してからmessage を使うこと)。 コーディング★★☆無料
期待される結果

出力 == エラー:通信に失敗しました

ヒント

catch (e) の e は unknown。instanceof Error で確認する。

模範解答
async function fetchData(): Promise<string> {
  throw new Error("通信に失敗しました");
}

async function main() {
  try {
    await fetchData();
  } catch (e) {
    const message = e instanceof Error ? e.message : "不明なエラー";
    console.log(`エラー:${message}`);
  }
}

main();
解説

e は unknown なので、そのまま message にアクセスできない。TS09の型ガードが、エラー処理の場面でも必要になる。

TS14-S04 【安全なラッパー】Promise を受け取り、成功なら { ok: true; data: T }、失敗なら { ok: false; error: string } を返すジェネリックな関数 safe を定義せよ。成功と失敗の2通りで試し、「成功:42」「失敗:通信エラー」と2行出力すること。 コーディング★★★無料
期待される結果

出力2行が完全一致

ヒント

型は Promise<{ ok: true; data: T } | { ok: false; error: string }>。try/catch で包む。

模範解答
type Result<T> =
  | { ok: true; data: T }
  | { ok: false; error: string };

async function safe<T>(promise: Promise<T>): Promise<Result<T>> {
  try {
    return { ok: true, data: await promise };
  } catch (e) {
    return {
      ok: false,
      error: e instanceof Error ? e.message : "不明なエラー",
    };
  }
}

async function main() {
  const a = await safe(Promise.resolve(42));
  console.log(a.ok ? `成功:${a.data}` : `失敗:${a.error}`);

  const b = await safe(Promise.reject(new Error("通信エラー")));
  console.log(b.ok ? `成功:${b.data}` : `失敗:${b.error}`);
}

main();
解説

例外を「値」に変換している。try/catch を毎回書かずに、if (result.ok) で分岐できるようになる——TS09のタグ付きユニオンとTS10のジェネリクスの組み合わせ。

TS14-S05 【逐次処理と集計】3件の商品IDを順に取得し(id=2は失敗する)、成功件数と失敗件数を集計せよ。「[1] ノートPC」「[2] 取得失敗」「[3] マウス」「成功2件 / 失敗1件」の4行を出力すること。 コーディング★★★無料
期待される結果

出力4行が完全一致

ヒント

for...of の中で try/catch する。カウンタをループの外に用意する。

模範解答
async function fetchName(id: number): Promise<string> {
  if (id === 2) {
    throw new Error("取得失敗");
  }
  return id === 1 ? "ノートPC" : "マウス";
}

async function main() {
  let ok = 0;
  let ng = 0;

  for (const id of [1, 2, 3]) {
    try {
      const name = await fetchName(id);
      console.log(`[${id}] ${name}`);
      ok++;
    } catch (e) {
      const message = e instanceof Error ? e.message : "不明";
      console.log(`[${id}] ${message}`);
      ng++;
    }
  }

  console.log(`成功${ok}件 / 失敗${ng}件`);
}

main();
解説

try/catch をループの中に置くのが要点(JS15と同じ)。外に置くと最初の失敗で全体が止まる。

5仕上げ課題

TS14-FINAL 【商品情報の取得システム】型安全な非同期処理でデータを取得し、レポートを出力せよ。

擬似API(すべて async 関数として実装)
fetchProduct(id): Promise<Product> … id=3 は例外を投げる
 データ:1→ノートPC 128000/2→マウス 3200/4→モニター 45000
fetchStock(id): Promise<number> … 1→5, 2→0, 4→3

type Product = { id: number; name: string; price: number };

処理
① id 1, 2, 3, 4 を順に処理
② 各IDで商品と在庫を Promise.all で並列取得
③ 成功なら「[1] ノートPC:128,000円(在庫5個)」、在庫0なら末尾に「 ※品切れ」
④ 失敗なら「[3] エラー:商品が見つかりません」
⑤ 最後に成功・失敗件数と合計金額を出力

期待される出力(6行)
=== 商品情報の取得 ===
[1] ノートPC:128,000円(在庫5個)
[2] マウス:3,200円(在庫0個) ※品切れ
[3] エラー:商品が見つかりません
[4] モニター:45,000円(在庫3個)
成功3件 / 失敗1件 / 合計176,200円
期待される結果

出力6行が完全一致

ヒント

Record でデータを持つと引きやすい。for...of の中で try/catch し、その中で Promise.all を使う。catch の e は unknown なので instanceof で確認する。

模範解答
type Product = { id: number; name: string; price: number };

const PRODUCTS: Record<number, Omit<Product, "id">> = {
  1: { name: "ノートPC", price: 128000 },
  2: { name: "マウス", price: 3200 },
  4: { name: "モニター", price: 45000 },
};

const STOCKS: Record<number, number> = { 1: 5, 2: 0, 4: 3 };

async function fetchProduct(id: number): Promise<Product> {
  const found = PRODUCTS[id];
  if (!found) {
    throw new Error("商品が見つかりません");
  }
  return { id, ...found };
}

async function fetchStock(id: number): Promise<number> {
  return STOCKS[id] ?? 0;
}

async function main() {
  console.log("=== 商品情報の取得 ===");

  let ok = 0;
  let ng = 0;
  let total = 0;

  for (const id of [1, 2, 3, 4]) {
    try {
      const [product, stock] = await Promise.all([
        fetchProduct(id),
        fetchStock(id),
      ]);

      const mark = stock === 0 ? " ※品切れ" : "";
      console.log(
        `[${id}] ${product.name}:${product.price.toLocaleString()}円(在庫${stock}個)${mark}`
      );

      total += product.price;
      ok++;
    } catch (e) {
      const message = e instanceof Error ? e.message : "不明なエラー";
      console.log(`[${id}] エラー:${message}`);
      ng++;
    }
  }

  console.log(
    `成功${ok}件 / 失敗${ng}件 / 合計${total.toLocaleString()}円`
  );
}

main();
解説

JS15の仕上げ課題と同じ処理を、型付きで書き直した。違いは3つある。

第一に、await の書き忘れが起こりえないconst [product, stock] = Promise.all([…]) と await を落とせば、分割代入の時点でエラーになる。JS15では [object Promise] が表示されて初めて気づいていた。

第二に、catch (e)eunknown であること。e.message と直接書けないので、「Error 以外が投げられる可能性」を必ず考えさせられる。実際、JavaScriptでは文字列やオブジェクトも throw できる。

第三に、Record<number, Omit<Product, "id">> という型だ。TS13で学んだユーティリティ型を使い、「idはキーとして持つので、値には含めない」という構造を正確に表現している。

非同期処理は「いつ結果が来るか分からない」という不確実さを扱う。TypeScriptは、その不確実さを型として明示し、扱い忘れを防いでくれる。

次章では外部データの型付けを学ぶ。実際のAPIから来る「型が保証されないデータ」を、安全に扱う方法を扱う。