TypeScript

非同期処理とテスト

完了を待つ処理と失敗を扱い、期待する結果を自動で確認しましょう。

このページの実行環境 例はNode.js 20以降を想定しています。グローバルのfetchはNode.js 18以降でフラグなしで利用でき、node:testはNode.js 20で安定版になりました。版によってAPIや出力が異なる場合があるため、node --versionも確認してください。
01

Promiseは将来の結果を表す

通信やファイル操作は、結果がすぐには返らない非同期処理です。Promise<T>は、将来成功したときにTが得られることを表します。

少し後で値を返すTYPESCRIPT
function waitMessage(): Promise<string> {
  return new Promise((resolve) => {
    setTimeout(() => resolve("完了"), 100);
  });
}

waitMessage().then((message) => console.log(message));
実行結果OUTPUT
完了
02

async/awaitで完了を待つ

awaitはPromiseの結果が決まるまで、その非同期関数の続きだけを待ちます。awaitを使う関数にはasyncを付け、戻り値はPromiseになります。

順番を確認TYPESCRIPT
const delay = (ms: number): Promise<void> =>
  new Promise((resolve) => setTimeout(resolve, ms));

async function main(): Promise<void> {
  console.log("開始");
  await delay(100);
  console.log("終了");
}

await main();
実行結果OUTPUT
開始
終了
トップレベルawaitの前提 この例のawait main();を関数の外に書くには、出力したJavaScriptをES Modulesとして実行する必要があります。package.json"type": "module"など、必要な設定はモジュールとプロジェクト設定を参照してください。前節のwaitMessage().then(...)はトップレベルawaitを使わない点が異なります。
03

外部データは実行時に検証する

response.json()で得る値は外部から来ます。自分で型を書くだけでは内容を保証できないため、unknownとして検証します。

投稿タイトルを取得TYPESCRIPT
type Post = { id: number; title: string };

function isPost(value: unknown): value is Post {
  if (typeof value !== "object" || value === null) return false;
  const item = value as Record<string, unknown>;
  return typeof item.id === "number" && typeof item.title === "string";
}

async function fetchPost(url: string): Promise<Post> {
  const response = await fetch(url);
  if (!response.ok) throw new Error(`HTTP ${response.status}`);
  const data: unknown = await response.json();
  if (!isPost(data)) throw new Error("レスポンスの形式が不正です");
  return data;
}

実際の出力は接続先と時期で変わります。通信処理は成功例だけでなく、タイムアウト、HTTPエラー、不正なJSONも想定します。

04

catchした値を安全に扱う

JavaScriptではError以外もthrowできるため、catchした値はunknownとして絞り込みます。

失敗を表示TYPESCRIPT
function parseCount(text: string): number {
  const value = Number(text);
  if (!Number.isInteger(value) || value < 0) {
    throw new Error("0以上の整数を指定してください");
  }
  return value;
}

try {
  console.log(parseCount("三"));
} catch (error: unknown) {
  const message = error instanceof Error ? error.message : "不明なエラー";
  console.log(message);
}
実行結果OUTPUT
0以上の整数を指定してください
握りつぶさない 空のcatchで失敗を無視すると原因を調べられません。利用者向け表示と、調査に必要なログを分けて設計します。
05

Node.js標準機能でテストする

ここでは依存を増やさず、Node.jsのnode:testnode:assert/strictを使います。先に@types/nodeを開発用依存へ追加します。

src/price.tsTYPESCRIPT
export function calculateTotal(price: number, count: number): number {
  if (price < 0 || !Number.isInteger(count) || count < 0) {
    throw new Error("価格と個数を確認してください");
  }
  return price * count;
}
src/price.test.tsTYPESCRIPT
import test from "node:test";
import assert from "node:assert/strict";
import { calculateTotal } from "./price.js";

test("単価と個数から合計を計算する", () => {
  assert.equal(calculateTotal(200, 3), 600);
});

test("負の個数を拒否する", () => {
  assert.throws(() => calculateTotal(200, -1));
});
ビルド後に実行COMMAND
npm run build
node --test dist/price.test.js
結果の要点OUTPUT
tests 2
pass 2
fail 0

成功する通常値だけでなく、0、負数、空の値など境界や失敗も確認します。詳細表示はNode.jsの版で異なります。

PRACTICE

ミニ課題:安全なJSON変換

JSON文字列を受け取り、{ name: string; quantity: number }の形なら値を返し、それ以外ならエラーにする非同期関数を作ってください。正常なJSON、不正な構文、プロパティ不足、負の個数をテストします。

確認の順序
  • JSON.parseの例外を扱う
  • 結果をunknownとして検証する
  • quantityが0以上の整数か確認する
  • 成功と複数の失敗をテストする