本文 AI 產出,尚未審核

JavaScript – Fetch 與網路請求(Networking)

主題:JSON 解析


簡介

在前端開發中,與後端或第三方 API 交換資料是最常見的需求,而 JSON(JavaScript Object Notation)已成為事實上的資料交換格式。
透過 fetch 取得的回應往往是 JSON 字串,若不正確解析,就會導致程式錯誤、使用者體驗下降,甚至安全問題。因此,掌握 JSON 解析的正確流程與技巧,是每位 JavaScript 開發者必備的基礎能力。

本篇文章將從 概念說明實作範例常見陷阱與最佳實踐,一步步帶你了解如何在 fetch 中安全、有效地處理 JSON,並提供多個可直接套用的程式碼範例,讓你在實務專案中快速上手。


核心概念

1. 為什麼要使用 JSON?

  • 輕量且易讀:相較於 XML,JSON 結構簡潔,直接對應 JavaScript 物件。
  • 跨平台支援:幾乎所有程式語言都有成熟的 JSON 解析函式庫。
  • 效能佳:字串大小較小,傳輸與解析速度快。

2. fetch 回傳的 Response 物件

fetch(url, options) 會回傳一個 Promise,解決後得到 Response 物件。
Response 本身不會自動把內容轉成 JavaScript 物件,必須呼叫以下方法之一:

方法 目的 回傳值
text() 取得純文字 Promise<string>
json() 解析為 JSON 物件 Promise<any>
blob() 取得二進位檔案 Promise<Blob>
arrayBuffer() 取得二進位緩衝區 Promise<ArrayBuffer>

重點:只有呼叫 response.json() 才會把 JSON 字串自動解析成 JavaScript 物件,且此過程會自動拋出 SyntaxError(若字串不是合法的 JSON)。

3. 解析流程的完整範例

fetch('https://api.example.com/users')
  .then(response => {
    // 檢查 HTTP 狀態碼是否成功 (200~299)
    if (!response.ok) {
      // 自訂錯誤訊息,拋出讓後面的 catch 捕捉
      throw new Error(`Network response was not ok (${response.status})`);
    }
    // 直接呼叫 json() 解析
    return response.json();
  })
  .then(data => {
    // data 已是 JavaScript 物件
    console.log('取得的使用者資料:', data);
  })
  .catch(error => {
    // 處理網路錯誤、JSON 解析錯誤或自訂錯誤
    console.error('發生錯誤:', error);
  });

程式碼範例

以下提供 5 個常見且實用的 JSON 解析範例,每個範例都附上說明與最佳實踐。

範例 1️⃣ 基本 GET 請求與 JSON 解析

// 取得公開的天氣資訊
fetch('https://api.openweathermap.org/data/2.5/weather?q=Taipei&appid=YOUR_KEY')
  .then(res => {
    if (!res.ok) throw new Error(`HTTP ${res.status}`);
    return res.json();               // 解析 JSON
  })
  .then(weather => {
    // 直接使用解構賦值取得需要的欄位
    const { main: { temp }, weather: [{ description }] } = weather;
    console.log(`目前氣溫 ${temp}K,天氣 ${description}`);
  })
  .catch(err => console.error('取得天氣失敗:', err));

技巧:使用 解構賦值 可以一次取出深層結構,程式碼更簡潔。


範例 2️⃣ POST 請求:送出 JSON 並解析回傳結果

const newUser = {
  name: 'Alice',
  email: 'alice@example.com',
  role: 'admin'
};

fetch('https://api.example.com/users', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json'   // 告訴伺服器請求主體是 JSON
  },
  body: JSON.stringify(newUser)          // 將物件序列化成字串
})
  .then(res => {
    if (res.status === 201) return res.json(); // 成功建立,解析回傳的使用者物件
    // 其他狀態碼視為錯誤
    return res.text().then(txt => { throw new Error(txt); });
  })
  .then(createdUser => {
    console.log('新使用者已建立:', createdUser);
  })
  .catch(err => console.error('建立使用者失敗:', err));

重點Content-Type: application/json 必須與 JSON.stringify 同時使用,否則伺服器可能無法正確解析請求體。


範例 3️⃣ 使用 async/await 重構

async function fetchProducts() {
  try {
    const response = await fetch('https://api.example.com/products');
    if (!response.ok) throw new Error(`HTTP ${response.status}`);

    const products = await response.json(); // 直接解析
    // 篩選出價格低於 1000 的商品
    const cheap = products.filter(p => p.price < 1000);
    console.log('低價商品:', cheap);
  } catch (e) {
    console.error('取得商品失敗:', e);
  }
}

fetchProducts();

優點async/await 讓非同步流程更像同步程式,易於閱讀與除錯。


範例 4️⃣ 處理 巨量 JSON(分頁或流式)

當 API 回傳大量資料時,直接一次 json() 可能會造成記憶體壓力。此時可配合 分頁ReadableStream

async function fetchAllPages() {
  let page = 1;
  const allItems = [];

  while (true) {
    const res = await fetch(`https://api.example.com/items?page=${page}`);
    if (!res.ok) throw new Error(`Page ${page} error`);
    const { data, nextPage } = await res.json(); // 假設回傳 { data: [], nextPage: number|null }

    allItems.push(...data);
    if (!nextPage) break; // 沒有下一頁就結束
    page = nextPage;
  }

  console.log('共取得', allItems.length, '筆資料');
  return allItems;
}

fetchAllPages();

實務建議:若 API 支援 RangeCursor,盡量使用分頁機制,避免一次載入過大 JSON。


範例 5️⃣ 防止 JSON 注入(安全性)

有時候後端會回傳包含使用者輸入的字串,若直接 eval 會產生安全風險。永遠使用 JSON.parse(或 response.json())而非 eval

// 錯誤做法(千萬別這樣!)
const unsafe = '{"name":"<script>alert(1)</script>"}';
const obj = eval('(' + unsafe + ')'); // 可能執行惡意腳本

// 正確做法
const safeObj = JSON.parse(unsafe);
console.log(safeObj.name); // 只會得到字串,不會執行腳本

結論JSON.parse 只會把字串轉成資料結構,絕不會執行其中的程式碼,確保前端不受注入攻擊。


常見陷阱與最佳實踐

常見陷阱 說明 解決方法
忘記檢查 response.ok 即使 HTTP 404/500,response.json() 仍會嘗試解析,導致不易察覺的錯誤。 在呼叫 json() 前先 if (!res.ok) throw new Error(...)
直接使用 response.text()JSON.parse 若忘記 await 或忘記回傳 Promise,會得到 Promise 本身。 使用 await response.json()(或 return response.json())即可。
跨域 CORS 錯誤 瀏覽器會阻擋未授權的跨域請求,返回的 Response 會是 opaque,無法讀取內容。 確認伺服器有正確設定 Access-Control-Allow-Origin,或使用 JSONP / Proxy
JSON 數字精度問題 JavaScript 的 Number 只能安全表示到 2^53-1,過大整數會失真。 若需要大數字,請在伺服器端傳回字串,或使用 BigIntJSON.parse 仍會是字串,需要自行轉換)。
多層嵌套導致 undefined 直接存取深層屬性時若其中一層不存在會拋 TypeError 使用 可選鏈 (obj?.prop?.sub) 或先檢查 if (obj && obj.prop).

最佳實踐清單

  1. 統一錯誤處理:建立一個 handleResponse 函式,負責檢查狀態碼、解析 JSON、捕捉錯誤。
  2. 使用 async/await:讓非同步流程更直觀,特別是在多個串接請求時。
  3. 設定 Accept: application/json:告訴伺服器你希望得到 JSON。
  4. 避免 eval:永遠使用 JSON.parse,切勿自行拼接字串後 eval
  5. 在 TypeScript 中加上型別:利用介面 (interface) 定義回傳結構,提升開發時的 IntelliSense 與編譯安全。
// 範例:通用的 response 處理函式
async function fetchJSON(url, options = {}) {
  const res = await fetch(url, {
    headers: { 'Accept': 'application/json', ...options.headers },
    ...options
  });
  if (!res.ok) {
    const errMsg = await res.text();
    throw new Error(`HTTP ${res.status}: ${errMsg}`);
  }
  return res.json(); // 直接回傳解析後的物件
}

實際應用場景

場景 需求 JSON 解析的角色
SPA(單頁應用) 動態載入列表、表單提交、即時搜尋 fetch + response.json() 為主,配合 debounce 減少請求次數。
行動端離線緩存 需要在離線時仍能瀏覽先前資料 取得 JSON 後使用 IndexedDBLocalStorage 寫入本機,之後再 JSON.parse 讀回。
儀表板(Dashboard) 同時顯示多個圖表、需要即時更新 使用 Promise.all 同時取得多筆 JSON,或 WebSocket 配合 JSON.stringify / JSON.parse 進行雙向通訊。
第三方服務整合 如支付、地圖、社群登入 多數第三方 API 回傳 JSON,必須正確解析才能取得授權 token、回傳結果等關鍵資訊。
伺服器端渲染(SSR) 先在 Node.js 端取得資料後渲染 HTML node-fetch(或原生 fetch)同樣返回 Response,解析方式不變,只是執行環境在伺服器。

總結

  • JSON 是現代 Web 應用的核心資料格式,透過 fetch 搭配 response.json() 可以安全、快速地把伺服器回傳的字串轉成可操作的 JavaScript 物件。
  • 正確的 錯誤檢查跨域設定、以及 安全解析(避免 eval)是避免常見問題的關鍵。
  • 使用 async/await、可選鏈、以及 解構賦值,可以讓程式碼更簡潔、可讀性更高。
  • 在實務開發中,將 JSON 解析錯誤統一處理分頁/緩存策略 結合,才能打造效能佳、使用者體驗好的前端應用。

掌握以上概念與實作技巧,你將能在任何需要與後端或第三方 API 互動的情境下,穩定且安全地處理 JSON 資料,為你的 JavaScript 專案奠定堅實的基礎。祝你寫程式愉快,持續探索更深入的前端技術!