這一課完成後:你會使用 fetch() 與 async / await 取得 JSON,判斷 HTTP response 是否成功,處理 loading / success / error 三種狀態,並把 API 回傳的 array/object 渲染到 DOM。
先把網站用 HTTP server 跑起來
前幾課直接雙擊 index.html 也能完成很多練習,但從網路請求開始,建議養成從本機 HTTP server 開網站的習慣。
在 web-basics-playground 根目錄開 Terminal。如果你有 Python:
python -m http.server 8000
Windows 如果使用 py:
py -m http.server 8000
然後在瀏覽器打開:
http://localhost:8000
現在頁面的來源不再是 file://...,而是一個真正的 HTTP origin。
API 先理解成「另一個程式提供的介面」
這一課會用 JSONPlaceholder 當公開測試 API。它提供假的資料,適合練習 request / response,不需要 API key。
例如這個網址:
https://jsonplaceholder.typicode.com/todos/1
直接在瀏覽器打開,你會看到類似:
{
"userId": 1,
"id": 1,
"title": "...",
"completed": false
}
這不是 HTML,而是 JSON:很常用來讓不同程式交換結構化資料。
第一次 fetch:先只看 Console
在 script.js:
fetch("https://jsonplaceholder.typicode.com/todos/1")
.then((response) => response.json())
.then((data) => {
console.log(data);
});
重新整理頁面,打開 DevTools Console。你應該會看到一個 JavaScript object。
這種 .then() 寫法很常見,你要看得懂;但這門課接下來主要用 async / await,因為對初學者比較容易順著流程閱讀。
為什麼 fetch 不是立刻回傳資料?
網路請求需要時間。瀏覽器要送出 request、等待另一台伺服器處理,再接收 response。
所以這種想法不成立:
const data = fetch("https://example.com/data");
console.log(data.title);
fetch() 回傳的是 Promise,不是已經完成的 JSON 資料。
async / await:把非同步流程寫得比較像一般順序
async function loadTodo() {
const response = await fetch(
"https://jsonplaceholder.typicode.com/todos/1"
);
const data = await response.json();
console.log(data);
}
loadTodo();
async 表示這個函式裡可以使用 await;await 則表示:這個非同步步驟完成後,再繼續這個函式後面的程式。
它不是把整個瀏覽器凍結。其他事件與瀏覽器工作仍然可以繼續。
Response 和 JSON data 是兩個不同東西
這兩行不要混成同一個概念:
const response = await fetch(url);
const data = await response.json();
response 包含 HTTP response 的資訊,例如 status、headers、是否成功;data 才是把 response body 解析成 JavaScript 值之後的結果。
response.json() → 解析 body → JavaScript object / array
很重要:404 / 500 不一定會讓 fetch 自動 throw
很多初學者會以為只要伺服器回 404 或 500,fetch() 就一定直接進 catch。實際上,fetch 通常在「網路請求本身失敗」時才 reject;HTTP error response 仍然可能正常得到一個 Response。
所以要自己檢查:
const response = await fetch(url);
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
response.ok 通常代表 status 在成功範圍。
response.ok → 再判斷 HTTP 結果
把完整錯誤處理補起來
async function loadTodo() {
try {
const response = await fetch(
"https://jsonplaceholder.typicode.com/todos/1"
);
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
const data = await response.json();
console.log(data);
} catch (error) {
console.error("載入失敗:", error);
}
}
loadTodo();
try 裡放可能失敗的流程;catch 接住錯誤。
錯誤可能來自網路中斷、CORS、錯誤 URL、伺服器問題、解析資料失敗,或你自己的程式。
不要只有成功畫面:先建立三種 UI state
任何需要等待外部資料的 UI,至少先想三種狀態:
Success → 資料到了
Error → 沒有成功
HTML:
<button id="load-data" type="button">載入資料</button>
<p id="status">尚未載入。</p>
<div id="result"></div>
JavaScript:
const loadButton = document.querySelector("#load-data");
const status = document.querySelector("#status");
const result = document.querySelector("#result");
async function loadTodo() {
status.textContent = "Loading...";
result.textContent = "";
try {
const response = await fetch(
"https://jsonplaceholder.typicode.com/todos/1"
);
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
const todo = await response.json();
status.textContent = "Loaded.";
result.textContent = todo.title;
} catch (error) {
status.textContent = "載入失敗,請稍後再試。";
console.error(error);
}
}
loadButton.addEventListener("click", loadTodo);
現在前幾課全部串起來了:
等待時先防止重複點擊
如果使用者連點五次,就可能同時送出五個 request。最簡單的改善:
async function loadTodo() {
loadButton.disabled = true;
status.textContent = "Loading...";
try {
const response = await fetch(
"https://jsonplaceholder.typicode.com/todos/1"
);
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
const todo = await response.json();
result.textContent = todo.title;
status.textContent = "Loaded.";
} catch (error) {
status.textContent = "載入失敗。";
console.error(error);
} finally {
loadButton.disabled = false;
}
}
finally 不管成功或失敗都會執行,很適合做「恢復按鈕」、「關閉 loading」這類收尾。
從一筆資料進到一整個陣列
改抓:
https://jsonplaceholder.typicode.com/todos
回傳的是 array。為了不要一次畫 200 筆,我們只取前五筆:
const todos = await response.json();
const firstFive = todos.slice(0, 5);
接著把它們渲染到 DOM:
const list = document.querySelector("#todo-list");
list.replaceChildren();
for (const todo of firstFive) {
const item = document.createElement("li");
item.textContent = todo.title;
if (todo.completed) {
item.classList.add("done");
}
list.append(item);
}
這就是非常典型的前端資料流:
把「取得資料」和「畫畫面」拆開
程式開始變長後,不要把所有東西塞進同一個函式。
function renderTodos(todos) {
list.replaceChildren();
for (const todo of todos) {
const item = document.createElement("li");
item.textContent = todo.title;
list.append(item);
}
}
async function loadTodos() {
const response = await fetch(
"https://jsonplaceholder.typicode.com/todos"
);
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
const todos = await response.json();
renderTodos(todos.slice(0, 5));
}
這樣責任比較清楚:
renderTodos → 負責把資料轉成 DOM
這種拆法之後做 Web App 會非常重要。
DevTools Network:不要只看 Console
打開 DevTools → Network,再按「載入資料」。你可以看到瀏覽器真的送出一筆 request。
點進去可以觀察:
Method
Status code
Response headers
Response body
花了多久
當 API 出問題時,Network panel 常常比只看 JavaScript error 更直接。
CORS:不是你的 fetch 語法錯了
瀏覽器有 same-origin security model。當你的網站向另一個 origin 讀資料時,對方伺服器必須允許這種跨來源存取。
如果 Console 出現 CORS 錯誤,不代表你能靠前端 JavaScript 自己「關掉 CORS」。真正的允許規則是由伺服器 response 決定。
不要照網路教學安裝「停用瀏覽器安全性」的奇怪外掛來解正式專案。練習時改用允許跨來源存取的 API;自己的 Web App 則在 backend / API 正確設定。
不要把 API key 寫在前端 JavaScript
任何送到瀏覽器的 HTML / CSS / JavaScript,使用者基本上都能查看。
所以這種做法不是秘密:
const API_KEY = "super-secret-key";
如果某個服務要求必須保密的 server-side key,它不應該直接出現在公開前端 bundle 裡。Web App 路徑會再正式處理 backend、environment variables 與 secrets。
Secret credential → 通常交給 backend / server-side environment
GET 先學會就夠,其他 HTTP methods 先知道名字
這一課主要使用 GET,也就是「取得資源」。之後你還會遇到:
POST → 建立 / 送資料
PUT / PATCH → 更新
DELETE → 刪除
它們不是 JavaScript 專屬概念,而是 HTTP API 很常見的介面設計。真正建立自己的 API 會放到 Web App。
小挑戰:做一個可以重新載入的 Todo 資料頁
需求 1:頁面有「載入 Todos」按鈕、status 區、list 區。
需求 2:按下按鈕後才發出 fetch request。
需求 3:等待時顯示 Loading,並暫時 disabled 按鈕。
需求 4:一定檢查 response.ok。
需求 5:使用 async / await 與 try / catch / finally。
需求 6:抓 /todos,至少顯示前 5 筆。
需求 7:完成的 todo 加上不同 CSS class。
需求 8:錯誤時畫面要有可理解訊息,不可以只 console.error。
需求 9:在 DevTools Network 找到這次 request,確認 status code。
故意讓它失敗一次
把 URL 暫時改成不存在的資源,例如一個錯誤 route,觀察:
再把 URL 修回正確。你不是只在測「成功會不會動」,而是在測「失敗時網站會不會壞成一片空白」。
最後收斂:外部資料進入畫面的完整路徑
到這裡,Web Basics 的核心零件其實已經全部學過:HTML、CSS、JavaScript、DOM、Events、Fetch。
下一課不會再介紹一個新的基礎技術,而是把這六塊放到同一個專案裡,做一個真正可操作、可重新載入資料、能處理失敗狀態的前端作品。
完成條件:你能從使用者事件啟動 fetch request,使用 async / await 取得 JSON,檢查 HTTP response、處理 loading / error state,並把 array/object 資料安全地渲染成 DOM。