第11章:非同期のCQS(Promise版の基本だけ)🌐⏳
この章は「非同期になっても、CQSのルールは一切ブレない」って感覚を作る回だよ〜☺️💕
今日のゴール🎯💡
Promise<void>(Command)とPromise<T>(Query)を迷わず書ける✨fetchの超基本(okチェック →json())が書ける✨ (MDN ウェブドキュメント)- 「Commandしたら Queryで取り直す🔁」の気持ちよさを体験する✨
11-1. 非同期って、結局なに?🤔💭
非同期=「待ち時間がある処理(ネットワーク/DB/ファイルなど)を、Promiseで扱う」ってことだよ〜⏳📦
fetch()は Promise を返す(将来のResponseを約束してる) (MDN ウェブドキュメント)response.json()も Promise を返す(読み取り&変換が“待ち”だから) (MDN ウェブドキュメント)
つまりこう👇
fetch()→Promise<Response>response.json()→Promise<any>(なので型で整えるのがTSの仕事💪)
11-2. 非同期でもCQSのルールは同じ✅✨
✅ Query(読む)📖
- 状態を読む(例:GETで一覧取得)
- 戻り値は
Promise<T>(読む結果があるから)
✅ Command(変える)🔧
- 状態を変える(例:POST/PUT/DELETEで更新)
- 戻り値は基本
Promise<void> - どうしても必要なら「最小限(IDとか)」だけ返すのはOK🎁(第10章の話)
🧠ポイント:HTTPのメソッドで考えると最初は超ラクだよ〜 GET=Query、POST/PUT/DELETE=Command(まずはこれでOK🙆♀️)
11-3. fetch の最小セット(これだけ覚えよ)🧩✨
fetch は「成功っぽく見えても失敗してる」ことがあるので、response.ok を必ず見るのが基本だよ👀✨ (MDN ウェブドキュメント)
// どこでも使い回せる “fetchの型付きラッパー” 🧁
export async function fetchJson<T>(url: string, init?: RequestInit): Promise<T> {
const res = await fetch(url, init);
// ここ大事!HTTP的に失敗なら例外にする(後の章でResult型に進化させてもOK)
if (!res.ok) {
throw new Error(`HTTP Error: ${res.status} ${res.statusText}`);
}
// json()はPromiseを返すよ(=awaitが必要) :contentReference[oaicite:4]{index=4}
return (await res.json()) as T;
}
🍬この
as Tは「型チェックをサボる」書き方でもあるから、 本気で堅くするのは後の章(入力チェックやエラーモデリング)でやればOKだよ☺️
11-4. ToDo題材を「非同期CQS」にするよ〜📝🌐✨
今回は、ダミーAPIとして DummyJSON を使うね(ToDoが用意されてて便利!)
- 一覧:
GET https://dummyjson.com/todos - 追加:
POST https://dummyjson.com/todos/add - 更新:
PUT https://dummyjson.com/todos/1(※“サーバーに本当に保存はされないけど、挙動は学べる”って書いてあるよ) (DummyJSON)
① 型を作る🧸🧩
export type Todo = {
id: number;
todo: string;
completed: boolean;
userId: number;
};
type TodoListResponse = {
todos: Todo[];
total: number;
skip: number;
limit: number;
};
② Query:一覧を取る(Promise<Todo[]>)📖✨
import { fetchJson } from "./fetchJson";
import type { Todo } from "./types";
type TodoListResponse = {
todos: Todo[];
total: number;
skip: number;
limit: number;
};
export async function getTodosQuery(): Promise<Todo[]> {
const data = await fetchJson<TodoListResponse>("https://dummyjson.com/todos");
return data.todos;
}
- 読むだけだから Query ✅
- 配列が欲しいから
Promise<Todo[]>✅
③ Command:追加する(基本は Promise<void>)➕🔧✨
まずは王道の「返さない」版(シンプル!)👇
import { fetchJson } from "./fetchJson";
import type { Todo } from "./types";
export async function addTodoCommand(todoText: string): Promise<void> {
await fetchJson<Todo>("https://dummyjson.com/todos/add", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
todo: todoText,
completed: false,
userId: 1,
}),
});
}
- **状態を変える(追加)**から Command ✅
- 追加の結果(一覧など)を返したくなるけど…ここは我慢!🙅♀️ → Commandのあとに Query で取り直すのがCQSの基本の型だよ🔁✨
④ 「IDだけ欲しい」なら最小限だけ返す🎁✨(オプション)
import { fetchJson } from "./fetchJson";
import type { Todo } from "./types";
export async function addTodoCommand_ReturnId(todoText: string): Promise<{ id: number }> {
const created = await fetchJson<Todo>("https://dummyjson.com/todos/add", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
todo: todoText,
completed: false,
userId: 1,
}),
});
return { id: created.id }; // “最小限” ✅
}
11-5. UIから呼ぶときの「CQS黄金パターン」🏆✨

やりたいことはいつもこれ👇
- Command(追加/更新)
- Query(一覧を再取得)
- 画面に反映
import { addTodoCommand } from "./commands";
import { getTodosQuery } from "./queries";
let todos: { id: number; todo: string; completed: boolean; userId: number }[] = [];
export async function refreshTodos(): Promise<void> {
todos = await getTodosQuery();
render(todos);
}
export async function onClickAdd(todoText: string): Promise<void> {
try {
await addTodoCommand(todoText); // Command 🔧
await refreshTodos(); // Query 📖(再取得)🔁
} catch (e) {
alert("失敗しちゃった…🥲 もう一回ためしてみてね!");
}
}
// 画面表示(例)
function render(list: typeof todos) {
console.log(list);
}
🌟ここまでできたら「非同期でもCQSできてる!」って胸張ってOKだよ〜🥳🎉
11-6. あるある事故集😇→😱(ここだけ先に潰す)
😱 事故1:await 付け忘れ
response.json()もPromiseだから、await忘れると中身が取れないよ〜 (MDN ウェブドキュメント)
😱 事故2:QueryのつもりでPOSTしちゃう
- 関数名が
get...なのに中でPOSTしてたらアウト🙅♀️ → 読むならGET、変えるならPOST/PUT/DELETE(最初はこれで十分)
😱 事故3:Commandが“更新後の一覧”を返す
- 「便利そう」でやりがちだけど、責務が混ざって事故りやすい🐘💥 → Commandの後は Queryで取り直す🔁
11-7. ミニ演習✍️✨(手を動かす用)
演習A:分類ゲーム🎯
次は Command?Query?理由も一言で書いてみて〜😊
fetch('/todos')で一覧取得fetch('/todos/add', { method: 'POST', ... })fetch('/todos/1', { method: 'PUT', ... })- 一覧を
completed=trueのものだけに絞る(配列操作だけ)
演習B:実装チャレンジ🧪
setTodoCompletedCommand(id, completed): Promise<void>を作る(PUTでOK) (DummyJSON)- そのあと
refreshTodos()を呼ぶ(黄金パターン🔁)
章末:AIコーナー🤖✨(そのままコピペOK)
- 「この関数はCommand?Query?理由も一言で」🧠
- 「
fetch周りの例外処理、初心者向けに読みやすくして」🧼 - 「Commandの戻り値が大きすぎないかレビューして」🎁👀
- 「
getTodosQueryの戻り値型、もっと安全にする方法ある?」🧷✨
おまけメモ(最新動向ちょいだけ)📌✨
現時点の安定版TypeScriptは 5.9.3 が “latest” 扱いだよ(npmもGitHubも同じ) (npm) あと、TypeScript 6.0/7.0(ネイティブ化の流れ)も進捗が出てるけど、教材としてはまず 今の安定ラインで迷わず書けるのが最優先でOK! (Microsoft for Developers)
次の章(第12章)は「Queryが安定するとキャッシュしやすい🍯」に行けるよ〜! この第11章のコードを、あなたのToDoミニアプリの構成(ファイル分け)に合わせて “教材として綺麗な形” に整えたバージョンも作れるよ☺️💖