概要
現代のUIは非同期で動く。
ユーザーが「何かを待っている」状態に対し、ローディング表示やエラーメッセージ、再試行機能などの応答をどう設計するかが、UXの質と信頼性を決定づける。
本稿では、以下の観点から非同期フローとUI設計を体系化する:
- ローディング状態の明示と制御
- エラーの捕捉とUI通知
- 再試行戦略とユーザーの選択肢設計
- 状態遷移の構造化(状態管理・フラグ・ステートマシン)
- 抽象化による再利用可能な非同期UI構造
1. ローディング状態の構造化
✅ 明示的なローディングフラグの導入
let isLoading = false;
async function fetchData() {
isLoading = true;
try {
const res = await fetch('/api/user');
const data = await res.json();
updateUI(data);
} catch (e) {
showError(e);
} finally {
isLoading = false;
}
}
→ ✅ finally
で必ず解除する構造に
→ ✅ フラグ名は isLoading
/ isSubmitting
など文脈で分ける
2. エラー処理とユーザー通知
✅ try/catch + 明示的なUI表示
let errorMessage = '';
try {
await fetchData();
} catch (e) {
errorMessage = 'データの取得に失敗しました';
}
→ ✅ 「なぜ失敗したか」「次にどうすればいいか」を伝えるメッセージ設計が重要
3. 再試行戦略:明示的なリトライ設計
async function tryFetch(retry = 3) {
for (let i = 0; i < retry; i++) {
try {
return await fetchData();
} catch (e) {
if (i === retry - 1) throw e;
}
}
}
- ✅ 自動再試行は限定的に使う(例:ネット不安定時)
- ✅ 明示的な再試行ボタンもUIとして用意
4. 状態モデルの設計(構造化)
const state = {
status: 'idle', // idle | loading | success | error
data: null,
error: null
};
→ ✅ 状態を文字列・enumで管理すると、分岐が明示的になりUI設計が簡潔になる
5. UIコンポーネントでの非同期ハンドリング
✅ 状態に応じて描画を切り替える
if (state.status === 'loading') {
return showSpinner();
} else if (state.status === 'error') {
return showError(state.error);
} else if (state.status === 'success') {
return renderData(state.data);
}
→ ✅ ロジックとUIの分離が保たれ、状態に応じたUIが意図通り動作する
6. 汎用ラッパー関数で共通化
async function withLoading(action, stateRef) {
stateRef.status = 'loading';
try {
const result = await action();
stateRef.status = 'success';
stateRef.data = result;
} catch (e) {
stateRef.status = 'error';
stateRef.error = e.message;
}
}
→ ✅ 複数の非同期関数に共通の状態管理構造を適用可能
7. トースト通知やダイアログの設計ポイント
- ✅ エラーは“目立つUI”で即時に伝える
- ✅ トーストは短時間・非ブロッキングで適切
- ✅ モーダルは「致命的エラー」や「操作が必要なとき」に限定
設計判断フロー
① 待ち時間が1秒以上ある? → ローディングUI表示
② 失敗する可能性がある? → エラーメッセージ + 再試行導線
③ 状態が複数存在する? → statusを用いた状態遷移モデルに
④ UI更新の責務が集中? → withLoadingなどで共通化
⑤ ユーザー通知は十分か? → トーストやUIメッセージで即時性を担保
よくあるミスと対策
❌ ローディング中に多重発火
→ ✅ isLoading
を使ってボタンを一時無効化
❌ エラーを握り潰してユーザーに何も伝えない
→ ✅ UIにフィードバックを必ず表示する
❌ 成功・失敗でUIが更新されない
→ ✅ 状態フラグとレンダリングの紐づけを明確にする
結語
非同期フローとは「ユーザーの待ち」と「アプリケーションの応答」をつなぐ設計である。
- 待ち時間は明示する
- エラーは伝える
- 成功は確実に表示する
- 状態遷移は構造で示す
- 複雑化したらラップして抽象化する
ユーザーにとって重要なのは、“操作に対して意味のある反応があること”。
その非同期は、意味のある設計として成立しているか?