Node.js Fetch API:每個教學都漏掉的重點(2026 指南)

最後更新:May 14, 2026
AI 摘要
別再在正式環境裡依賴基本的 fetch 呼叫了。了解如何在 2026 年的 Node.js 中處理逾時、HTTP 錯誤、重試邏輯與連線池。

每一篇 Node.js fetch 教學都在教你 await fetch(url),然後就結束了。接著你的正式環境應用程式默默吞掉一個 500 錯誤,某個請求卡了 90 秒卻沒有逾時,最後你在星期五晚上為了本該很明顯的問題除錯。

我在 做內部工具和資料管線已經有一段時間了,可以很肯定地說:從「我的教學能跑 fetch」到「fetch 在正式環境也能穩定運作」之間的落差,正是大多數痛苦的來源。Reddit 上有位開發者說得很到位:「一旦進入正式環境,你就會發現自己需要比原生 fetch 更可靠的東西。」

還有另一位坦白說,「當了 3 年網頁開發者,我才知道 fetch API 的 catch 區塊不是用來處理 HTTP 錯誤的。」 這份指南會涵蓋大多數教學會跳過的五件事——錯誤陷阱、AbortController 逾時、重試邏輯、連線重用,以及什麼時候該超越 fetch 去做結構化資料擷取。如果你曾經在正式環境中遇過 fetch 呼叫默默失敗,這篇就是為你寫的。

nodejs-fetch-error-handling-flowchart.png

什麼是 Node.js Fetch API?

Node.js Fetch API 是 Node.js 內建、相容瀏覽器的 HTTP 請求方式(GET、POST、PUT、DELETE 等),不需要安裝 Axios、node-fetch 或任何其他套件。如果你在瀏覽器裡用過 fetch(),你已經知道語法了。現在同一套 API 也能在伺服器端使用。

先看一下它的版本演進:

里程碑Node 版本發生了什麼事
實驗性 fetch 標記v17.5.0 / v16.15.0fetch 以 --experimental-fetch 形式加入
預設全域 fetchv18.0.0實驗性 fetch 可全域使用,由 Undici 驅動
穩定版 fetchv21.0.0不再是實驗功能
2026 生產環境基準v22 LTS / v24 LTS建議用於正式環境;v20 已進入 EOL

在底層,Node 的 fetch 由 Undici 驅動——這是一個專為 Node.js 設計的高效能 HTTP 用戶端。它不依賴較舊的內建 http 模組。實際好處是:你可以在瀏覽器程式碼、Express 後端、無伺服器函式,以及 CLI 腳本中,用同樣的方式使用現代化、基於 Promise 的 HTTP API。

為什麼 Node.js Fetch API 對你的專案很重要

在 Node 18 以前,每個新專案幾乎都從同一個流程開始:npm install axiosnpm install node-fetch。到了 2026,如果你的專案跑在受維護的 Node LTS 版本上,基本 HTTP 請求已經不需要任何依賴。這對 bundle 大小、供應鏈安全,以及新手上手都是實際的好消息(前端與後端開發者終於能共用同一套 API)。

原生 fetch 的強項在這些情境:

情境為什麼原生 fetch 很適合正式環境注意事項
Express/Fastify 後端呼叫 REST API熟悉的 async/await,沒有依賴加上逾時與 response.ok 檢查
無伺服器函式(Lambda、Vercel 等)冷啟動面積小,免安裝套件逾時要低於平台最大執行時間
CLI 腳本與自動化簡單 GET/POST,不用做專案初始化為不穩定的 API 加上重試/退避
Webhook 傳送或轉發標準 HTTP 方法與標頭不要盲目重試非冪等 POST
報表與儀表板很適合從 API 抓 JSON迴圈中使用分頁與連線池
微服務通訊適合簡單的內部 HTTP 呼叫若需要重試、hooks 或 HTTP/2,考慮直接用 Got 或 Undici

對新的 Node 22+ 專案來說,原生 fetch 是合理的預設選項——除非你知道自己需要它不提供的功能(interceptors、內建重試、HTTP/2 等)。npm 下載量也說明了這個正在轉變的生態:,但其中很多是舊專案與轉相依套件。。趨勢很清楚:原生 fetch 已成新基準,而第三方用戶端則用於特定需求。

原生 Fetch vs node-fetch vs Axios vs Got vs Ky:2026 決策矩陣

我在開發者論壇最常看到的問題是:「在 Node.js 裡我該用哪個 HTTP 用戶端?」 有位 Reddit 使用者總結得很好:「既然語言/框架本身就有功能,為什麼還要再引入一個函式庫?」 這很合理——但答案還是要看你的需求。

http-client-libraries-comparison.png

功能原生 fetchnode-fetch v3axiosgot v15ky v2
Node.js 版本≥18(建議 22/24 LTS)≥12.20廣泛≥22≥22
是否需要安裝
ESM + CJS 支援兩者皆可(全域)僅 ESM(v3)兩者皆可僅 ESM僅 ESM
4xx/5xx 自動拒絕
內建重試
Request interceptors是(hooks)是(hooks)
串流支援Web ReadableStream有限強大的 Node streams基於 fetch
Bundle/安裝體積0 KB約 107 KB,3 個依賴約 2.8 MB,4 個依賴約 355 KB,12 個依賴約 405 KB,0 個依賴
HTTP/2 支援透過 Undici dispatcher否(fetch wrapper)

順帶一提 ESM/CJS 的麻煩:node-fetch v3 只支援 ESM,這讓很多使用 require() 的專案直接受影響。原生 fetch 是全域的——不管是 CJS 還是 ESM 檔案都能用,不需要任何 import 技巧。如果你因為 CommonJS 還卡在 node-fetch v2,原生 fetch 可以直接解掉這個問題。

至於早期穩定性疑慮:沒錯,Node 18 初期的 fetch 實作確實有過真實 bug。有位開發者在 Reddit 提到:「最近遇到 native node 18 fetch 的怪 bug,所以只好把我們的應用程式改掉。」 那是 2023 年的事。到了 2026,在 Node 22 與 24 LTS 之下,這些問題已經解決。原生 fetch 已可用於正式環境。

什麼時候應該繼續用原生 Fetch

當以下情況成立時,選原生 fetch:

  • 你的專案跑在 Node 22 LTS 或 Node 24 LTS。
  • 請求很單純,是標準 REST 呼叫(GET、POST、PUT、DELETE)。
  • 你願意自己加一層小包裝處理 response.ok、JSON 解析、逾時與重試。
  • 你希望完全沒有依賴,降低供應鏈風險。
  • 你重視瀏覽器/伺服器 API 的一致性。
  • 你在無伺服器或邊緣環境中,偏好使用內建 API。

什麼時候 Axios、Got 或 Ky 更適合

Axios 適合團隊依賴 request/response interceptors 的情境(例如自動刷新驗證 token、tenant 標頭、集中式記錄),當你希望 HTTP 錯誤預設就拒絕,或需要與較舊 Node 執行環境相容時。

Got 是為高吞吐量 Node 服務打造的,內建重試、hooks、進階逾時階段、串流、分頁輔助、Unix sockets、proxy/caching 工作流程,還有 HTTP/2 支援。它是 Node-only HTTP 工作的瑞士刀。

Ky 則是 fetch 簡潔性的甜蜜點:如果你喜歡 fetch 的寫法,但希望少一點樣板碼,Ky 會補上重試、逾時、hooks,以及 HTTPError,而且套件很小、零依賴。

如何用 Node.js Fetch API 發送 GET 請求

用 async/await 發送 GET 請求大概像這樣:

1const response = await fetch('https://jsonplaceholder.typicode.com/posts/1');
2const post = await response.json();
3console.log(post.title);
4// → "sunt aut facere repellat provident occaecati excepturi optio reprehenderit"

如果你偏好 .then() 串接,也可以這樣寫:

1fetch('https://jsonplaceholder.typicode.com/posts/1')
2  .then(response => response.json())
3  .then(post => console.log(post.title))
4  .catch(error => console.error(error));

兩種都能用。但目前都還不算適合正式環境(這點等一下會說明)。

你應該認識的 response 讀取方法:

方法使用時機
response.json()伺服器回傳 JSON
response.text()伺服器回傳 HTML、純文字、CSV、Markdown
response.arrayBuffer()需要二進位資料(圖片、檔案)
response.body需要串流/分塊處理

更好的模式是這樣——真的會檢查錯誤:

1async function getPost(id) {
2  const response = await fetch(`https://jsonplaceholder.typicode.com/posts/$\{id\}`);
3  if (!response.ok) {
4    throw new Error(`HTTP $\{response.status\} $\{response.statusText\}`);
5  }
6  return response.json();
7}
8const post = await getPost(1);
9console.log(post.title);

這一行 if (!response.ok),就是教學範例和正式程式碼的差別。這也帶我們進入最大的陷阱。

如何用 Node.js Fetch API 發送 POST 請求

POST 請求的結構一樣——只是設定 method、headers 和 body:

1const response = await fetch('https://jsonplaceholder.typicode.com/posts', {
2  method: 'POST',
3  headers: {
4    'Content-Type': 'application/json',
5  },
6  body: JSON.stringify({
7    title: 'Node fetch guide',
8    body: '正式環境的 fetch 需要錯誤處理。',
9    userId: 1,
10  }),
11});
12if (!response.ok) {
13  throw new Error(`HTTP $\{response.status\}`);
14}
15const created = await response.json();
16console.log(created.id); // → 101

發送其他請求類型(PUT、DELETE、PATCH)

PUT、PATCH 和 DELETE 只是 method 的值不同,其餘結構完全相同:

1// PUT — 完整取代
2await fetch('https://jsonplaceholder.typicode.com/posts/1', {
3  method: 'PUT',
4  headers: { 'Content-Type': 'application/json' },
5  body: JSON.stringify({ id: 1, title: 'Replaced', body: 'Full replacement', userId: 1 }),
6});
7// PATCH — 部分更新
8await fetch('https://jsonplaceholder.typicode.com/posts/1', {
9  method: 'PATCH',
10  headers: { 'Content-Type': 'application/json' },
11  body: JSON.stringify({ title: 'Partial update' }),
12});
13// DELETE
14await fetch('https://jsonplaceholder.typicode.com/posts/1', {
15  method: 'DELETE',
16});

Express 的 body-parser 陷阱: 如果你把 JSON POST 到 Express 伺服器,而 req.body 回傳 undefined,通常修正方式都一樣:用 express.json(),不要用 express.urlencoded()。伺服器需要在路由之前先加上 express.json() middleware,才能解析 Content-Type: application/json 的請求內容。這是關於 Express 最常見的 之一,而且每次都會有人踩雷。

1import express from 'express';
2const app = express();
3app.use(express.json()); // ← JSON POST body 需要這個
4app.post('/api/posts', (req, res) => {
5  res.json({ received: req.body });
6});

會讓正式環境應用程式出問題的 fetch() 錯誤陷阱

fetch-error-handling-flowchart.png

大多數正式環境的 fetch bug 都來自這裡。

fetch() 不會在 HTTP 4xx 或 5xx 錯誤時拒絕它的 promise。 它只會在網路層級失敗時拒絕——像是 DNS 錯誤、沒有網路、請求被中止。如果伺服器回傳 403 Forbidden 或 500 Internal Server Error,fetch 仍然把它視為 成功 的回應。你的 .catch() 區塊不會執行。你的 try/catch 也抓不到。你的程式會很開心地處理伺服器送回來的任何內容。

把這件事寫得很清楚,但大多數教學都輕描淡寫。結果就是?像下面這種程式看起來沒問題,卻會默默吞掉錯誤:

1try {
2  const response = await fetch('https://api.example.com/private');
3  const data = await response.json(); // ← 即使是 403 這行也會執行
4  console.log('看起來成功了:', data);
5} catch (error) {
6  // 只有網路層級失敗才會進到這裡
7  console.error('捕捉到:', error);
8}

快速拆解一下每種寫法實際能抓到什麼:

模式能抓到網路錯誤能抓到 4xx/5xx能安全解析 JSON可重複使用
原始 .then(res => res.json())是(透過 .catch()沒有 content-type 保護
搭配 await fetch()try/catch沒有 content-type 保護
每次手動寫 if (!res.ok)取決於每個呼叫部分
自訂 fetchJSON() 包裝

建立可重複使用的 fetchJSON() 包裝器

做一個包裝器,然後到處引用它。不要再把 if (!response.ok) 複製貼到每個檔案:

1export class HTTPError extends Error {
2  constructor(message, { status, statusText, url, body }) {
3    super(message);
4    this.name = 'HTTPError';
5    this.status = status;
6    this.statusText = statusText;
7    this.url = url;
8    this.body = body;
9  }
10}
11export async function fetchJSON(url, options = {}) {
12  const response = await fetch(url, {
13    headers: {
14      Accept: 'application/json',
15      ...options.headers,
16    },
17    ...options,
18  });
19  const contentType = response.headers.get('content-type') || '';
20  const isJSON = contentType.includes('application/json');
21  const body = isJSON ? await response.json().catch(() => null) : await response.text();
22  if (!response.ok) {
23    throw new HTTPError(`HTTP $\{response.status\} $\{response.statusText\}`, {
24      status: response.status,
25      statusText: response.statusText,
26      url: response.url,
27      body,
28    });
29  }
30  return body;
31}

現在,當伺服器回傳 403 時:

1try {
2  const data = await fetchJSON('https://api.example.com/private');
3} catch (error) {
4  if (error instanceof HTTPError) {
5    console.error(`伺服器回傳 $\{error.status\}:`, error.body);
6  } else {
7    console.error('網路或其他失敗:', error);
8  }
9}

這個錯誤物件會帶有狀態碼、回應內容和 URL——記錄、告警或對使用者顯示訊息時需要的資訊都有了。只要匯入一次,到處都能用。

AbortController 與逾時:Node.js Fetch API 的正式環境寫法

request-retry-pooling-flowchart.png

如果沒有逾時,當遠端伺服器沒有回應時,fetch 呼叫就會無限期卡住。你的 Express 路由被阻塞。你的 Lambda 把執行額度燒光。你的腳本就這樣……停在那裡。

我查了前幾個搜尋結果:沒有任何一篇專門講 Node.js 的 fetch 教學有涵蓋請求取消或逾時。但逾時正是很多開發者還是選 Axios 或 Got 的原因之一。甚至有個 Reddit 討論串標題就叫 「Node fetch does not timeout」

使用 AbortSignal.timeout()(Node 18.11+)

最簡單的方法——只多一個選項:

1try {
2  const response = await fetch('https://api.example.com/data', {
3    signal: AbortSignal.timeout(5000), // 5 秒
4  });
5  if (!response.ok) throw new Error(`HTTP $\{response.status\}`);
6  const data = await response.json();
7  console.log(data);
8} catch (error) {
9  if (error.name === 'TimeoutError') {
10    console.error('請求在 5 秒後逾時。');
11  } else {
12    throw error;
13  }
14}

注意:AbortSignal.timeout() 丟出的是 TimeoutError,不是 AbortError。這是連一些有經驗的開發者都可能搞錯的細節。

使用 AbortController 手動設定逾時

如果你需要更多控制,或是要根據使用者操作取消請求,而不只是單純靠計時器:

1const controller = new AbortController();
2const timeout = setTimeout(() => controller.abort(), 5000);
3try {
4  const response = await fetch('https://api.example.com/data', {
5    signal: controller.signal,
6  });
7  const data = await response.json();
8  console.log(data);
9} catch (error) {
10  if (error.name === 'AbortError') {
11    console.error('請求已被手動中止。');
12  } else {
13    throw error;
14  }
15} finally {
16  clearTimeout(timeout);
17}

處理 AbortError 與 TimeoutError 的差異

這個區別對記錄與使用者訊息都很重要:

中止路徑catch 區塊中的錯誤名稱
AbortSignal.timeout(ms)TimeoutError
controller.abort()AbortError
DNS/網路失敗通常是 TypeError: fetch failed

下面是一個實際情境——Express 路由呼叫外部 API,且必須在 3 秒內回應:

1app.get('/dashboard', async (req, res, next) => {
2  try {
3    const data = await fetchJSON('https://api.example.com/report', {
4      signal: AbortSignal.timeout(3000),
5    });
6    res.json(data);
7  } catch (error) {
8    if (error.name === 'TimeoutError') {
9      res.status(504).json({ error: '上游 API 逾時' });
10      return;
11    }
12    next(error);
13  }
14});

沒有這種寫法,緩慢的上游 API 會把你的整個路由卡住,直到客戶端放棄為止。

重試邏輯與連線重用:把 Node.js Fetch API 提升到正式環境等級

原生 fetch 沒有內建重試。一次短暫網路抖動或臨時的 503,就會讓請求直接失敗。對正式環境中大多數讀取操作來說,這是不能接受的。

可組合的重試包裝器,搭配指數退避

這段刻意寫得很短——實際邏輯大概只有 10 行:

1const wait = ms => new Promise(resolve => setTimeout(resolve, ms));
2export async function fetchWithRetry(url, options = {}, retries = 2) {
3  for (let attempt = 0; ; attempt++) {
4    try {
5      const response = await fetch(url, options);
6      if (response.ok || ![408, 429, 500, 502, 503, 504].includes(response.status)) {
7        return response;
8      }
9      if (attempt >= retries) return response;
10    } catch (error) {
11      if (attempt >= retries) throw error;
12    }
13    await wait(250 * 2 ** attempt); // 250ms、500ms、1000ms...
14  }
15}

什麼時候該重試,什麼時候不該

  • 應該重試: 冪等的 GET 與 HEAD 請求、短暫性狀態碼(408、429、500、502、503、504)、短暫網路抖動。
  • 不應該重試: 會建立紀錄、扣款、或觸發副作用的非冪等 POST 請求——除非你有使用 idempotency key。
  • 尊重 Retry-After: 對 429(速率限制)和 503(服務不可用),在退避之前先檢查 Retry-After 標頭。

如果你不想自己寫重試邏輯, 是一個輕量級的 fetch 包裝器,內建重試、逾時、hooks 和 HTTPError,而且零依賴。

使用 Undici 的 Agent 與 Pool 做連線重用

對高吞吐量迴圈——抓取數百個頁面、批次呼叫 API、輪詢服務——重用 TCP 連線可以省下很多時間。每建立一條新連線,就代表要重新做 DNS 查詢、TCP 握手,以及(對 HTTPS 而言)TLS 協商。

因為 Node 的 fetch 是由 Undici 驅動,所以你可以傳入自訂 dispatcher:

1import { Agent } from 'undici';
2const agent = new Agent({
3  keepAliveTimeout: 10_000,
4  keepAliveMaxTimeout: 60_000,
5});
6const response = await fetch('https://api.example.com/data', {
7  dispatcher: agent,
8});

如果要針對特定來源做更細的控制:

1import { Pool } from 'undici';
2const pool = new Pool('https://api.example.com', { connections: 10 });
3const response = await fetch('https://api.example.com/data', {
4  dispatcher: pool,
5});
6// 用完後:
7await pool.close();

顯示,連線重用與池化能大幅提升吞吐量——他們本地測試中 undici - dispatch 約 22,234 req/sec,而 undici - fetch 約 5,904 req/sec。實際數字會不同,但方向很明確:如果你對同一個來源發送大量請求,pooling 很重要。

還有一件事:一定要消耗或取消 response body。未被消耗的 body 可能會讓 Node 的 HTTP 內部資源洩漏。

使用 Node.js Fetch API 處理串流回應

大檔下載、分塊 JSON 資料流、server-sent events、LLM 輸出——這些情境如果一定要等整個回應完成才處理,會浪費時間和記憶體。串流可以讓你在資料到達時就開始處理。

streaming-data-chunking-process.png

Node 18+ 已包含與瀏覽器相容的 ReadableStream。下面示範如何串流一個以換行分隔的 JSON 回應,並在每一行到達時立即處理:

1const response = await fetch('https://example.com/large-file.ndjson');
2if (!response.ok) throw new Error(`HTTP $\{response.status\}`);
3const reader = response.body.getReader();
4const decoder = new TextDecoder();
5let buffer = '';
6while (true) {
7  const { value, done } = await reader.read();
8  if (done) break;
9  buffer += decoder.decode(value, { stream: true });
10  let newlineIndex;
11  while ((newlineIndex = buffer.indexOf('\n')) >= 0) {
12    const line = buffer.slice(0, newlineIndex).trim();
13    buffer = buffer.slice(newlineIndex + 1);
14    if (line) {
15      const item = JSON.parse(line);
16      console.log('已處理:', item.id);
17    }
18  }
19}

如果只是想串流純文字(例如把 LLM 輸出直接接到 stdout):

1const response = await fetch('https://example.com/stream');
2const reader = response.body.getReader();
3const decoder = new TextDecoder();
4for (;;) {
5  const { value, done } = await reader.read();
6  if (done) break;
7  process.stdout.write(decoder.decode(value, { stream: true }));
8}

串流是原生 fetch 和 Got 都表現很好的地方。Axios 的串流支援則相對有限。

當 fetch() 遇到限制:用 API 做結構化網頁爬取

總有一天,fetch 不再是瓶頸。真正的問題會變成:「我拿到 HTML 了,接下來呢?」

web-scraping-vs-ai-extraction-api-comparison.png

Fetch 是 HTTP 用戶端——它只會取回 bytes、文字、JSON 或 HTML。它不懂商品卡片、價格、評分,或聯絡人表格。對結構化網頁爬取來說,典型的原始技術棧長這樣:

  1. fetch() 下載 HTML
  2. 用 Cheerio(或類似工具)以 CSS selector 選取元素
  3. 自訂分頁邏輯
  4. 當頁面由前端 JavaScript 渲染時,還要處理 JS 執行
  5. 處理 proxy/反爬蟲/CAPTCHA
  6. 每次網站版型改變都要維護 selector

下面是一個典型的 fetch + Cheerio 範例——大約 15 行就能爬出商品標題:

1import * as cheerio from 'cheerio';
2const response = await fetch('https://example-store.com/products');
3if (!response.ok) throw new Error(`HTTP $\{response.status\}`);
4const html = await response.text();
5const $ = cheerio.load(html);
6const products = $('.product-card')
7  .map((_, el) => ({
8    name: $(el).find('.product-title').text().trim(),
9    price: $(el).find('.price').text().trim(),
10    url: new URL($(el).find('a').attr('href'), response.url).href,
11  }))
12  .get();
13console.log(products);

這個方法適合 HTML 穩定、結構可預測的頁面。但它很快就會變得脆弱——JavaScript 渲染內容、class 名稱變動、反爬措施和分頁,全部都會增加複雜度。

Thunderbit 的開放 API:一次呼叫,從原始 HTML 到結構化資料

這就是另一種工具開始變得有用的地方。在 ,我們做了一層 API,幫你處理那些麻煩事——JavaScript 渲染、反爬保護、版型變動——讓你專注於真正想要的資料。

Distill APIPOST /distill):把任何 URL 轉成乾淨的 Markdown。適合餵給 LLM、建立知識庫或做內容分析——不需要 HTML parser。

Extract APIPOST /extract):定義一份描述你要的結構化資料的 JSON Schema(商品名稱、價格、評分),AI 就會幫你抽取。沒有 CSS selector,也不會因為版型變動而壞掉。

下面是同一個商品爬取任務,改用 Thunderbit 的 Extract API——透過原生 fetch 呼叫:

1const response = await fetch('https://openapi.thunderbit.com/openapi/v1/extract', {
2  method: 'POST',
3  headers: {
4    Authorization: `Bearer $\{process.env.THUNDERBIT_API_KEY\}`,
5    'Content-Type': 'application/json',
6  },
7  body: JSON.stringify({
8    url: 'https://example-store.com/products',
9    renderMode: 'basic',
10    schema: {
11      type: 'object',
12      properties: {
13        products: {
14          type: 'array',
15          items: {
16            type: 'object',
17            properties: {
18              name: { type: 'string', description: '商品名稱' },
19              price: { type: 'string', description: '顯示的商品價格' },
20              rating: { type: 'number', description: '平均顧客評分' },
21            },
22            required: ['name', 'price'],
23          },
24        },
25      },
26      required: ['products'],
27    },
28  }),
29});
30if (!response.ok) throw new Error(`Thunderbit API: $\{response.status\}`);
31const result = await response.json();
32console.log(result.data);

對比很明顯:大約 15 行 fetch + Cheerio(外加脆弱的 selector),和一次回傳乾淨 JSON 的 API 呼叫。對批次工作而言,Thunderbit 支援每次批次 extract 最多 50 個 URL、每次批次 distill 最多 100 個 URL。

Thunderbit 不是 fetch 的替代品——fetch 是傳輸層。當原始 HTML 解析本身變成真正的問題時,你會需要 Thunderbit 這一層擷取工具。如果你對價格有興趣, 提供 600 個 API 單位可供試用,付費方案則從每月 6 美元起。你也可以直接看 ,在瀏覽器中無程式碼擷取資料。

若想更深入了解結構化爬取方法,我們關於 以及 的指南,會更詳細地介紹不同工作流程。

快速參考:Node.js Fetch API 備忘表

這一段很適合加書籤。需要可直接複製貼上的範例時再回來看。

模式範例
基本 GETconst res = await fetch(url); const data = await res.json();
基本 POSTawait fetch(url, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload) });
HTTP 錯誤檢查if (!res.ok) throw new Error(\HTTP ${res.status}`);`
逾時(簡單)await fetch(url, { signal: AbortSignal.timeout(5000) });
手動中止const c = new AbortController(); setTimeout(() => c.abort(), 5000); await fetch(url, { signal: c.signal });
重試狀態碼重試 408、429、500、502、503、504。不要盲目重試 POST。
JSON 包裝器使用 fetchJSON() 檢查 ok、解析 content type、丟出 HTTPError
連線池import { Pool } from 'undici'; const pool = new Pool(origin, { connections: 10 }); fetch(url, { dispatcher: pool });
串流分塊const reader = res.body.getReader(); loop over await reader.read()
結構化擷取當目標是網頁欄位而不是原始 HTML 時,使用 Thunderbit Extract API。

結論與重點整理

到了 2026,Node.js 的原生 fetch 已經可用於正式環境——新專案不需要 node-fetch,也不一定要預設依賴 Axios。但單靠原始 fetch(),本身還稱不上完整的正式環境 HTTP 策略。

大多數教學會跳過的五件事——也正是這份指南涵蓋的內容:

  1. 錯誤陷阱: fetch() 不會在 4xx/5xx 時丟錯。務必檢查 response.ok,或使用像 fetchJSON() 這樣的包裝器。
  2. 逾時: 單純情境可用 AbortSignal.timeout()AbortSignal.timeout() 會丟出 TimeoutError;手動 controller.abort() 會丟出 AbortError
  3. 重試邏輯: 不是內建的。對冪等請求與短暫失敗加上指數退避;或者直接用 Ky,開箱即有 fetch 風格的重試。
  4. 連線重用: 對高吞吐量迴圈,透過 dispatcher 選項使用 Undici 的 AgentPool
  5. 結構化擷取: 當你要的是網頁中的資料欄位,而不只是原始 HTML,與其維護脆弱的 CSS selector,不如考慮像 Thunderbit 這樣的擷取 API。

一句話的決策矩陣:大多數專案用原生 fetch;需要 interceptors 用 Axios;需要內建重試與 HTTP/2 用 Got;想要 fetch 但預設更好用就選 Ky;而當你的 fetch 型爬取腳本複雜到難以維護時,就用 Thunderbit 的 API。

試試這份指南中的寫法。如果你想看看 Thunderbit 如何處理結構化擷取, 是很好的起點——或者也可以到 看操作示範。

試用 Thunderbit 進行 AI 網頁爬蟲

常見問題

1. Node.js 內建 fetch 嗎?還是需要安裝?

Fetch 自 Node.js 18 之後就已內建——不需要安裝。它在 Node 21 變成穩定版,並且在 Node 22 LTS 與 Node 24 LTS 中完整支援。如果你用的是更舊的 Node 版本,可以安裝 node-fetch npm 套件,但新專案應該以受維護的 LTS 版本為目標。

2. fetch 遇到 404 或 500 會丟錯嗎?

不會。Fetch 只會在網路層級失敗時拒絕 promise(DNS 錯誤、沒有連線、請求中止)。像 404、403、500 這類 HTTP 回應會正常 resolve,但 response.ok === false。你必須明確檢查 response.okresponse.status——或者使用本指南展示的 fetchJSON() 包裝器。

3. 要怎麼在 Node.js 的 fetch 加上逾時?

最簡單的做法是使用 AbortSignal.timeout(ms),Node 18.11 起可用:await fetch(url, { signal: AbortSignal.timeout(5000) })。如果請求超過 5 秒,它會丟出 TimeoutError。若要更多控制,可以手動建立 AbortController,再用 setTimeout 呼叫 controller.abort()。手動模式請捕捉 AbortErrorAbortSignal.timeout() 則對應 TimeoutError

4. 可以在 Node.js 用 fetch 做網頁爬蟲嗎?

可以,但 fetch 只會回傳原始 HTML。你還需要像 Cheerio 這樣的 parser 來擷取特定元素,另外也要自己處理分頁、由 JavaScript 渲染的頁面,以及反爬措施。如果要大規模擷取結構化資料——例如乾淨的 JSON 商品名稱、價格或聯絡資訊——可以考慮 ,它會用 AI 回傳結構化資料,不需要 CSS selector 或依賴版型的程式碼。

5. 到了 2026,我應該從 Axios 轉成原生 fetch 嗎?

對於 Node 22+ 的新專案,原生 fetch 是很強的預設選項。它零依賴、基於 Promise,而且和瀏覽器 fetch 使用相同 API。如果你依賴 request/response interceptors、預設 HTTP 錯誤拒絕,或需要與舊版 Node 相容,就保留 Axios。兩者都合理——關鍵在於你的專案實際用了哪些功能。

了解更多

Fawad Khan
Fawad Khan
Fawad 靠寫作維生,而且老實說,他其實還滿喜歡這件事。他花了好幾年摸索,究竟什麼樣的文案能讓人記住,又是什麼讓讀者直接滑過。你要是問他行銷,他可以聊上好幾個小時;你要是問他 carbonara,他只會聊得更久。

試試 Thunderbit

只要 2 下就能抓取潛在客戶與其他資料。AI 驅動。

取得 Thunderbit 完全免費
使用 AI 擷取資料
輕鬆將資料轉移到 Google Sheets、Airtable 或 Notion
PRODUCT HUNT#1 Product of the Week