Learning Objectives
- 說明 AJAX 如何不重新整理頁面就與伺服器交換資料,以及它的優缺點
- 認識 HTTP 請求方法(GET、POST)、狀態碼、API 與 JSON 在 AJAX 裡的角色
- 說明 XMLHttpRequest 的流程:建立物件、open、send、在 onload 讀取 responseText
- 用 fetch 搭配 then 或 async/await 取得資料,檢查 response.ok 與 status,並送出 POST 請求
AJAX:不重新整理頁面就更新內容
傳統的網頁每做一件事就要換一整頁:送出表單 → 伺服器處理 → 送回一整個新網頁。 前後兩頁大部分的 HTML 都一樣(頁首、選單、頁尾),還是要重新傳一次,浪費頻寬,畫面也會閃一下。
AJAX(Asynchronous JavaScript and XML,非同步 JavaScript 與 XML)的做法不同:
- 使用者操作(例如按「載入更多」)
- JavaScript 在背景向伺服器送出請求,頁面不動
- 伺服器只回傳需要的資料(現在大多是 JSON)
- JavaScript 收到資料,只更新頁面上需要改變的那一塊
AJAX 不是一種新的語言,而是一組技術的組合;這個名稱由 Jesse James Garrett 在 2005 年提出。名稱裡雖然有 XML,現在傳的資料以 JSON 為主,也可以是純文字或 HTML。 社群網站往下捲就自動載入新貼文、搜尋框邊打字邊出現建議,都是 AJAX。
| 優點 | 要注意的地方 |
|---|---|
| 只傳必要的資料,回應更快、伺服器負擔較小 | 用 AJAX 換掉的內容不會自動進入瀏覽器歷史,「上一頁」與書籤可能回不到剛剛的狀態 |
| 頁面不會整個重新載入,操作比較流暢 | 網路有延遲,要顯示「載入中…」並處理失敗的情況,否則使用者以為壞掉了 |
| 不需要安裝外掛,瀏覽器內建 | 需要瀏覽器允許執行 JavaScript |
HTTP 請求、狀態碼、API 與 JSON
瀏覽器與伺服器之間用 HTTP 溝通:瀏覽器送出請求(request),伺服器回一個回應(response)。
請求方法:最常用的兩種
| 方法 | 用途 | 資料放在哪裡 |
|---|---|---|
| GET | 取得資料 | 網址的 query string,例如 http://www.example.com/?name=JavaScript |
| POST | 送出、新增資料 | 請求的本文(body),網址上看不到 |
狀態碼:回應裡的三位數字,告訴你結果如何
| 狀態碼 | 意思 |
|---|---|
| 200 OK | 成功 |
| 201 Created | 成功新增了資料(常見於 POST) |
| 404 Not Found | 找不到這個網址 |
| 500 Internal Server Error | 伺服器端出錯 |
API(Application Programming Interface,應用程式介面)是伺服器對外約定好的取得資料方式:哪個網址、用哪個方法、要帶什麼參數、會回傳什麼格式。
例如 GET /api/users 回傳使用者清單。前端只要照著約定送請求,不需要知道伺服器裡面怎麼做。
回傳的資料通常是 JSON 字串,用 JSON.parse()(或 fetch 的 response.json())轉成 JavaScript 的物件或陣列。
跨來源:網頁預設只能用 AJAX 讀取同一個來源(通訊協定+網域+連接埠都相同)的資料;要讀別的網站的 API, 那個網站必須在回應裡表明允許(這個機制叫 CORS,Cross-Origin Resource Sharing)。這也是上一課要開本機伺服器的原因之一。
本課的逐步執行動畫與網頁沙箱都沒有真的連上網路:
/api/…的回應是課程事先準備的假資料,所以每次的結果都一樣。
XMLHttpRequest:最早的 AJAX 寫法
XMLHttpRequest(常簡稱 XHR)是瀏覽器最早提供的 AJAX 工具。雖然名字有 XML 與 HTTP,它可以接收任何格式的資料。流程有四步:
const xhr = new XMLHttpRequest(); // 1. 建立物件
xhr.open('GET', '/api/news'); // 2. 設定方法與網址(第三個參數 true 表示非同步,預設就是 true)
xhr.onload = function () { // 3. 設定收到回應時要做什麼
if (xhr.status === 200) {
const data = JSON.parse(xhr.responseText);
}
};
xhr.send(); // 4. 送出(POST 時把資料放在 send 的參數)
readyState 屬性記錄請求進行到哪裡,每次改變都會觸發 readystatechange 事件:
| readyState | 名稱 | 意思 |
|---|---|---|
| 0 | UNSENT | 物件建立了,還沒呼叫 open |
| 1 | OPENED | 已經呼叫 open |
| 2 | HEADERS_RECEIVED | 已經呼叫 send,收到回應的標頭 |
| 3 | LOADING | 正在接收回應的內容 |
| 4 | DONE | 完成,回應已經全部收到 |
舊的教材常寫 xhr.onreadystatechange = function () { if (xhr.readyState === 4 && xhr.status === 200) { … } },意思和 onload 相同。
很舊的 IE5、IE6 要用 new ActiveXObject("Microsoft.XMLHTTP"),現在的瀏覽器都不需要了。
XMLHttpRequest 的四個步驟
注意第 14 行先印出「請求已送出」:send() 不會等回應。300 ms 後回應到了,onload 才被呼叫。動畫裡的 xhr 是課程用的模擬版本,會多出 _h、_url 等內部欄位,readyState 也直接從 1 跳到 4;真正的瀏覽器會經過 2、3。
fetch:用 Promise 送出請求
fetch 是現在的標準寫法,回傳 Promise,所以可以用 then 或 await。取得資料要經過兩個階段:
fetch(url, options) // 第一階段:Promise 完成時得到 Response 物件(只有狀態碼與標頭)
.then((response) => response.json()) // 第二階段:讀取並解析內容,又是一個 Promise
.then((data) => { … });
| Response 的屬性與方法 | 意思 |
|---|---|
response.status |
狀態碼,例如 200、404 |
response.ok |
狀態碼在 200~299 之間時是 true |
response.json() |
把內容解析成物件或陣列(回傳 Promise) |
response.text() |
把內容當成字串(回傳 Promise) |
最容易誤會的一點:fetch 只有在網路層失敗(斷線、網址連不上、被 CORS 擋下)時才會 rejected,錯誤是 TypeError: Failed to fetch。
伺服器回 404 或 500 時,Promise 仍然是 fulfilled,只是 response.ok 是 false。所以要自己檢查 response.ok,必要時自己 throw。
fetch+then:兩個階段
看第 3 行的 response:它不是資料本身,而是一個有 status、ok、json 的物件。要等第 5 行的 response.json() 完成,第 7 行才拿到真正的陣列 users。(動畫裡的 Response 是課程模擬的,顯示成一般物件。)
fetch+async/await,並檢查 response.ok
getJSON 是很常見的寫法:fetch 之後先檢查 response.ok,不是 2xx 就自己丟出錯誤。第二次請求 /api/users/99 得到 404:fetch 本身沒有失敗,是第 5 行的 throw 讓程式跳到 catch。
送出 POST 請求
fetch 的第二個參數是設定物件,送出資料時最常用這三項:
| 選項 | 意思 |
|---|---|
method |
請求方法,預設 'GET';新增資料用 'POST' |
headers |
請求標頭;送 JSON 時要寫 {'Content-Type': 'application/json'},告訴伺服器內容的格式 |
body |
要送出的內容;物件要先用 JSON.stringify() 轉成字串 |
伺服器處理完通常會回傳新增的那筆資料(例如加上了 id),並以 201 Created 回應。
用 POST 新增一位使用者
第 3 到 7 行是一個 fetch 呼叫:設定物件裡有 method、headers 與 body。這裡的假伺服器不會真的讀取 body,只會回傳事先準備好的回應 {id: 4, name: 'Dora'}。
想一想:XMLHttpRequest 與 fetch
用 fetch('/api/missing') 請求一個不存在的網址,伺服器回應 404。fetch 回傳的 Promise 會怎樣?
假設 /api/score 回傳 {"name": "Amy", "score": 92},下面的程式會印出什麼?
fetch('/api/score')
.then((res) => res.json())
.then((data) => console.log(data.name, data.score));
XMLHttpRequest 的 readyState 是 4 代表什麼?
想一想:AJAX 的應用
假設 /api/ping 會正常回應,下面的程式會印出什麼?
let msg = '開始';
fetch('/api/ping').then(() => {
msg = '完成';
});
console.log(msg);
要用 fetch 把 {name: 'Dora'} 以 JSON 格式 POST 給伺服器,body 應該怎麼寫?
在 catch 裡收到 TypeError: Failed to fetch,最可能的原因是什麼?
重點精華回顧
- AJAX 讓 JavaScript 在背景與伺服器交換資料,只更新頁面上需要改變的部分,不必重新載入整頁;要記得顯示載入中、處理失敗。
- GET 取得資料(參數在 query string),POST 送出資料(放在 body);狀態碼 200 成功、201 新增成功、404 找不到、500 伺服器錯誤。
- API 是伺服器約定好的取得資料方式,回傳的資料多半是 JSON;讀取其他網站的資料需要對方以 CORS 允許。
- XMLHttpRequest:
new XMLHttpRequest()→open(method, url)→ 設定onload→send();readyState4 表示完成,內容在responseText。 - fetch 回傳 Promise,分兩階段:先得到 Response,再用
response.json()取得資料;搭配async/await最好讀。 - fetch 只在網路層失敗時 rejected;404、500 時要自己檢查
response.ok或response.status。 - POST 時設定
method、headers(Content-Type: application/json)與body: JSON.stringify(資料)。