4日目のゴールと今日やること
4日目のテーマは
「fetch・async/await・エラーハンドリングの“型”はそのままに、ニュースアプリの“使い勝手”を一段上げる」
ことです。
ここまでであなたはすでに、
- キーワード+条件付きで NewsAPI から記事を取得する
- ページネーション(前へ / 次へ)を実装する
- ローディング状態とボタン制御を入れる
- 通信失敗時にページ番号を意識したエラーメッセージを出す
という「ニュース API 通信アプリの骨格」を手に入れました。
4日目はここに、
- ソース(ニュースサイト)で絞り込む
- 記事の「お気に入り」機能を付ける
- ローディング表示を少しリッチにする
- エラー表示を“パターン化”して整理する
を足して、「毎日触りたくなるニュースビューア」に近づけていきます。
今日のアプリのイメージを先に描く
どんな機能を足すか
4日目で目指すアプリは、こんな動きをします。
- キーワード・期間・並び順・ニュースソースを指定して検索
- 1 ページ 10 件、ページネーション付き
- 記事ごとに「★ お気に入り」ボタンがあり、クリックすると別枠に保存
- お気に入りはページを変えても残る
- ローディング中は「読み込み中…」+簡単なスケルトン表示
- エラーは「入力エラー」「通信エラー」「API エラー」で見た目を揃える
fetch・async/await・エラーハンドリングの“型”は変えません。
変えるのは「状態」と「UI の振る舞い」です。
fetch・async/await・エラーハンドリングの「型」をもう一度固定する
中心となる非同期処理のフォーム
3日目までで、中心となる非同期処理はほぼこの形になっていました。
async function fetchNewsWithCurrentState() {
if (isLoading) return;
startLoading();
showStatus("ニュースを取得中です…");
resultDiv.textContent = "";
try {
const url = buildUrl({
keyword: currentKeyword,
from: currentFrom,
to: currentTo,
sortBy: currentSortBy,
page: currentPage,
pageSize
});
const response = await fetch(url);
if (!response.ok) {
showError(`サーバーエラーが発生しました。(${response.status})`);
return;
}
const data = await response.json();
if (data.status === "error") {
showError(`エラー:${data.message}`);
return;
}
showStatus(`ニュースの取得に成功しました。(ページ ${currentPage})`);
renderArticles(data);
updatePaginationInfo(data);
} catch (error) {
showError(`ページ ${currentPage} の取得に失敗しました。ネットワークを確認してください。`);
console.error(error);
} finally {
endLoading();
}
}
JavaScriptここで大事なのは、
- fetch の前後に「ローディング開始 / 終了」があること
- HTTP エラー(!response.ok)と API エラー(data.status === “error”)を分けていること
- catch では「通信レベルの失敗」を扱っていること
4日目は、この「型」を崩さずに、周辺の UI と状態を育てていきます。
ソース(ニュースサイト)で絞り込む機能を追加する
NewsAPI の source / domain をどう使うか
NewsAPI には、ニュースソースを絞り込む方法がいくつかあります。
sources: NewsAPI が定義しているソース ID(例:bbc-news)domains: ドメイン名(例:nytimes.com)
中級編 4日目では、まずはシンプルに「よく使うソースをセレクトボックスで選ぶ」形にします。
UI にソース選択を追加する
HTML にセレクトボックスを追加します。
<select id="sourceSelect">
<option value="">すべてのソース</option>
<option value="bbc-news">BBC News</option>
<option value="the-verge">The Verge</option>
<option value="techcrunch">TechCrunch</option>
</select>
JavaScript で取得します。
const sourceSelect = document.getElementById("sourceSelect");
let currentSource = "";
JavaScriptbuildUrl に source を組み込む
function buildUrl({ keyword, from, to, sortBy, page, pageSize, source }) {
const params = new URLSearchParams({
q: keyword,
apiKey: API_KEY,
language: "ja",
sortBy: sortBy || "publishedAt",
page: String(page || 1),
pageSize: String(pageSize || 10)
});
if (from) params.set("from", from);
if (to) params.set("to", to);
if (source) params.set("sources", source);
return `${baseUrl}?${params.toString()}`;
}
JavaScript検索開始時に source も状態として保存する
searchButton.addEventListener("click", () => {
const keyword = keywordInput.value.trim();
const from = fromInput.value;
const to = toInput.value;
const sortBy = sortSelect.value;
const source = sourceSelect.value;
const error = validateKeyword(keyword);
if (error) {
showError(error);
resultDiv.textContent = "";
return;
}
currentKeyword = keyword;
currentFrom = from;
currentTo = to;
currentSortBy = sortBy;
currentSource = source;
currentPage = 1;
fetchNewsWithCurrentState();
});
JavaScriptfetchNewsWithCurrentState で source を渡す
const url = buildUrl({
keyword: currentKeyword,
from: currentFrom,
to: currentTo,
sortBy: currentSortBy,
page: currentPage,
pageSize,
source: currentSource
});
JavaScriptここでのポイントは、
「条件が増えても、fetch の書き方は変わらない」
ということです。
変わるのは URL のパラメータだけです。
記事の「お気に入り」機能を実装する
お気に入りをどう表現するか
お気に入りは、
「記事の情報を配列で持つ」のがシンプルです。
const favorites = [];
const favoritesDiv = document.getElementById("favorites");
JavaScript1 件の記事を「お気に入り用のオブジェクト」として保存します。
function addFavorite(article) {
const exists = favorites.some((fav) => fav.url === article.url);
if (exists) {
showStatus("すでにお気に入りに追加されています。");
return;
}
favorites.push(article);
showStatus("お気に入りに追加しました。");
renderFavorites();
}
JavaScript記事一覧に「★ お気に入り」ボタンを付ける
renderArticles を少し拡張します。
function renderArticles(data) {
const articles = data.articles;
if (!articles || articles.length === 0) {
resultDiv.textContent = "該当するニュースが見つかりませんでした。";
return;
}
let html = "";
articles.forEach((article, index) => {
const title = article.title || "タイトルなし";
const description = article.description || "説明文はありません。";
const url = article.url;
const source = article.source?.name || "不明なソース";
html += `
<div class="article" data-index="${index}">
<h3>${title}</h3>
<p>${description}</p>
<p>提供元:${source}</p>
<p><a href="${url}" target="_blank" rel="noopener noreferrer">記事を読む</a></p>
<button class="favorite-button">★ お気に入りに追加</button>
</div>
`;
});
resultDiv.innerHTML = html;
const buttons = resultDiv.querySelectorAll(".favorite-button");
buttons.forEach((btn, index) => {
btn.addEventListener("click", () => {
const article = articles[index];
addFavorite(article);
});
});
}
JavaScriptここでのポイントは、
- API から返ってきた
articlesをそのままお気に入りに渡している - URL をキーとして「同じ記事を重複登録しない」ようにしている
というところです。
お気に入り一覧を表示する
function renderFavorites() {
if (favorites.length === 0) {
favoritesDiv.textContent = "お気に入りはまだありません。";
return;
}
let html = "<h3>お気に入り記事</h3>";
favorites.forEach((article) => {
const title = article.title || "タイトルなし";
const url = article.url;
const source = article.source?.name || "不明なソース";
html += `
<div class="favorite-article">
<p>★ ${title}(${source})</p>
<p><a href="${url}" target="_blank" rel="noopener noreferrer">記事を読む</a></p>
</div>
`;
});
favoritesDiv.innerHTML = html;
}
JavaScriptお気に入りは「ページを変えても残る」ので、
「API の結果」とは別の“アプリの状態”として扱うことになります。
ローディング表示を少しリッチにする
スケルトン表示の考え方
スケルトン表示とは、
「まだ中身はないけど、ここにカードが並ぶよ」という
“薄いグレーのダミー UI”のことです。
ここでは、シンプルに「ダミー記事カード」を表示してみます。
function renderLoadingSkeleton() {
let html = "";
for (let i = 0; i < 3; i++) {
html += `
<div class="article skeleton">
<h3>読み込み中...</h3>
<p>説明文を読み込み中です…</p>
<p>提供元:読み込み中…</p>
</div>
`;
}
resultDiv.innerHTML = html;
}
JavaScriptfetchNewsWithCurrentState の中で、
ローディング開始時にこれを呼びます。
async function fetchNewsWithCurrentState() {
if (isLoading) return;
startLoading();
showStatus("ニュースを取得中です…");
renderLoadingSkeleton();
try {
// fetch 〜 JSON 〜 成功処理
} catch (error) {
// エラー処理
} finally {
endLoading();
}
}
JavaScriptここでのポイントは、
「ローディング中も resultDiv を空にしない」
ということです。
ユーザーにとっては、
「何もない」より「読み込み中の形が見える」方が安心感があります。
エラーハンドリングを「パターン」として整理する
エラー表示の種類を揃える
4日目では、エラー表示を少しだけ整理して、
「どの種類のエラーでも同じ関数を通る」ようにします。
function setStatus(message, type) {
statusDiv.textContent = message;
statusDiv.className = "status " + type; // CSS で色を変える
}
function showError(message) {
setStatus(message, "error");
}
function showStatus(message) {
setStatus(message, "success");
}
function showLoadingStatus() {
setStatus("ニュースを取得中です…", "loading");
}
JavaScriptCSS 側で例えばこうしておきます。
.status.loading {
color: #555;
}
.status.success {
color: #0a0;
}
.status.error {
color: #c00;
}
fetchNewsWithCurrentState はこう書き換えられます。
async function fetchNewsWithCurrentState() {
if (isLoading) return;
startLoading();
showLoadingStatus();
renderLoadingSkeleton();
try {
const url = buildUrl({
keyword: currentKeyword,
from: currentFrom,
to: currentTo,
sortBy: currentSortBy,
page: currentPage,
pageSize,
source: currentSource
});
const response = await fetch(url);
if (!response.ok) {
showError(`サーバーエラーが発生しました。(${response.status})`);
return;
}
const data = await response.json();
if (data.status === "error") {
showError(`エラー:${data.message}`);
return;
}
showStatus(`ニュースの取得に成功しました。(ページ ${currentPage})`);
renderArticles(data);
updatePaginationInfo(data);
} catch (error) {
showError(`ページ ${currentPage} の取得に失敗しました。ネットワークを確認してください。`);
console.error(error);
} finally {
endLoading();
}
}
JavaScriptここでの深掘りポイントは、
「エラーの種類は増えても、“見せ方”は一つの関数に集約する」
という設計です。
4日目の全体像をまとめる
4日目でやったことを整理すると、こうなります。
- fetch・async/await・エラーハンドリングの「型」はそのまま
- NewsAPI の
sourcesパラメータを使ってソース絞り込みを追加 - 検索条件(キーワード・期間・並び順・ソース)を「状態」として保持
- 記事ごとに「★ お気に入り」ボタンを付け、別枠に一覧表示
- ローディング中にスケルトン表示を出して、体験を良くする
- エラー表示を setStatus / showError / showStatus でパターン化
どれも「新しい難しい文法」ではなく、
「状態管理 × DOM 更新 × 既存の fetch ロジック」
の組み合わせです。
今日いちばん深く理解してほしいこと
4日目の本質は、
- fetch・async/await・エラーハンドリングはもう“土台”として固まっている
- これからの差は「どんな状態を持ち、どう UI に反映するか」で決まる
- API の結果(articles)とアプリの状態(favorites・currentSource など)は別物として扱う
という感覚です。
あなたはもう、
「API を叩いて結果を表示する人」ではなく、
“API を前提にアプリの体験を設計する人”になりつつあります。
次の 5 日目では、
このニュースアプリに「検索履歴」や「最近見た記事」などを加えて、
さらに“日常使いできるレベル”に近づけていきましょう。

