pin_drop当前位置:知识文库 ❯ 图文

后端通信:Fetch API详解 - 从入门到实践详解

教程简介

Fetch API是现代浏览器提供的网络请求接口,基于Promise设计,取代了老旧的XMLHttpRequest。Fetch API提供了更简洁、更强大的请求和响应处理能力,支持流式读取、请求取消、请求/响应对象等现代特性。作为前端开发者日常使用最频繁的API之一,深入掌握Fetch API是构建现代Web应用的基本功。

本教程将全面讲解Fetch基本用法、Request对象、Response对象、Headers对象、请求配置、响应类型、流式读取、AbortController取消请求,以及Fetch与XHR的对比。

核心概念

1. Fetch API概述

Fetch API由以下几个核心接口组成:

接口 说明
fetch() 全局方法,发起网络请求
Request 请求对象,表示一个HTTP请求
Response 响应对象,表示一个HTTP响应
Headers 头部对象,表示HTTP头部集合

Fetch的核心特点:

  • 基于Promise设计,支持async/await

  • 请求和响应是标准化的对象

  • 支持流式处理(Streams API)

  • 脱离XHR的回调模型

  • 更好的错误处理语义

  • 支持请求取消(AbortController)

2. 基本用法

代码示例

// 最简单的GET请求
fetch('/api/users')
    .then(response => response.json())
    .then(data => console.log(data))
    .catch(error => console.error(error));

// 使用async/await
async function getUsers() {
    try {
        const response = await fetch('/api/users');
        const data = await response.json();
        console.log(data);
    } catch (error) {
        console.error(error);
    }
}

重要:fetch只在网络错误时reject,HTTP错误(4xx/5xx)不会reject!

代码示例

// 必须手动检查response.ok
const response = await fetch('/api/users');
if (!response.ok) {
    throw new Error(`HTTP ${response.status}`);
}

3. Request对象

代码示例

// 创建Request对象
const request = new Request('/api/users', {
    method: 'POST',
    headers: {
        'Content-Type': 'application/json'
    },
    body: JSON.stringify({ name: '张三' })
});

// 使用Request对象
const response = await fetch(request);

Request构造函数选项:

选项 说明 默认值
method 请求方法 GET
headers 请求头部 {}
body 请求体 null
mode 请求模式 cors
credentials 凭证策略 same-origin
cache 缓存模式 default
redirect 重定向策略 follow
referrer 引用来源 about:client
referrerPolicy 引用策略 no-referrer-when-downgrade
signal AbortSignal null
integrity 完整性校验 ''
keepalive 保持连接 false

4. Response对象

Response对象的属性和方法:

属性/方法 说明
ok 状态码200-299为true
status HTTP状态码
statusText 状态描述
headers Headers对象
type 响应类型
url 最终URL(可能经过重定向)
redirected 是否经过重定向
body ReadableStream(流式体)
bodyUsed body是否已读取
json() 解析为JSON
text() 解析为文本
blob() 解析为Blob
arrayBuffer() 解析为ArrayBuffer
formData() 解析为FormData
clone() 克隆响应

响应类型(type):

类型 说明
basic 同源响应
cors 跨域响应
opaque 不透明跨域响应(no-cors模式)
opaqueredirect 不透明重定向响应

5. Headers对象

代码示例

// 创建Headers
const headers = new Headers({
    'Content-Type': 'application/json',
    'Authorization': 'Bearer token123'
});

// 常用方法
headers.get('Content-Type');          // 'application/json'
headers.set('Accept', 'application/json');
headers.append('Accept', 'text/html'); // 追加值
headers.has('Authorization');          // true
headers.delete('X-Temp');
headers.forEach((value, name) => {
    console.log(`${name}: ${value}`);
});

6. 请求配置详解

mode - 请求模式

代码示例

// cors(默认):跨域请求,需要CORS头部
fetch('https://api.example.com/data', { mode: 'cors' });

// no-cors:不透明请求,无法读取响应内容
fetch('https://api.example.com/data', { mode: 'no-cors' });

// same-origin:仅同源请求,跨域直接报错
fetch('/api/data', { mode: 'same-origin' });

credentials - 凭证策略

代码示例

// same-origin(默认):同源请求携带Cookie
fetch('/api/data', { credentials: 'same-origin' });

// include:所有请求都携带Cookie
fetch('https://api.example.com/data', { credentials: 'include' });

// omit:不携带Cookie
fetch('https://api.example.com/data', { credentials: 'omit' });

cache - 缓存模式

代码示例

// default:使用标准缓存策略
fetch('/api/data', { cache: 'default' });

// no-store:不使用缓存
fetch('/api/data', { cache: 'no-store' });

// reload:重新获取,更新缓存
fetch('/api/data', { cache: 'reload' });

// no-cache:使用缓存前先验证
fetch('/api/data', { cache: 'no-cache' });

// force-cache:强制使用缓存
fetch('/api/data', { cache: 'force-cache' });

// only-if-cached:只使用缓存
fetch('/api/data', { cache: 'only-if-cached' });

redirect - 重定向策略

代码示例

// follow(默认):自动跟随重定向
fetch('/api/data', { redirect: 'follow' });

// error:重定向时报错
fetch('/api/data', { redirect: 'error' });

// manual:不跟随重定向
fetch('/api/data', { redirect: 'manual' });

7. 流式读取

Fetch支持使用Streams API流式读取响应体:

代码示例

async function streamRead() {
    const response = await fetch('/api/large-data');
    const reader = response.body.getReader();
    const decoder = new TextDecoder();

    while (true) {
        const { done, value } = await reader.read();
        if (done) break;

        const chunk = decoder.decode(value, { stream: true });
        console.log('收到数据块:', chunk);
    }
}

8. AbortController取消请求

代码示例

const controller = new AbortController();
const signal = controller.signal;

fetch('/api/data', { signal })
    .then(response => response.json())
    .then(data => console.log(data))
    .catch(error => {
        if (error.name === 'AbortError') {
            console.log('请求已取消');
        } else {
            console.error('请求失败:', error);
        }
    });

// 取消请求
controller.abort();

超时取消:

代码示例

function fetchWithTimeout(url, timeout = 5000) {
    const controller = new AbortController();
    const timeoutId = setTimeout(() => controller.abort(), timeout);

    return fetch(url, { signal: controller.signal })
        .finally(() => clearTimeout(timeoutId));
}

9. Fetch vs XHR对比

特性 Fetch XHR
设计模式 Promise 事件回调
请求取消 AbortController abort()
流式读取 支持 不支持
进度监控 不直接支持 支持
超时 需手动实现 timeout属性
HTTP错误 不reject 不reject
Cookie控制 credentials选项 withCredentials
重定向控制 redirect选项 无
缓存控制 cache选项 无
Worker支持 支持 支持

代码示例

示例1:Fetch API完整演示

代码示例

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Fetch API完整演示</title>
    <style>
        * { margin: 0; padding: 0; box-sizing: border-box; }
        body { font-family: 'Segoe UI', system-ui, sans-serif; background: #f0f2f5; padding: 20px; color: #333; }
        .container { max-width: 900px; margin: 0 auto; }
        h1 { text-align: center; color: #1a73e8; margin-bottom: 24px; font-size: 28px; }
        .card { background: #fff; border-radius: 12px; padding: 24px; margin-bottom: 20px; box-shadow: 0 2px 8px rgba(0,0,0,0.08); }
        .card h2 { font-size: 18px; color: #1a73e8; margin-bottom: 16px; padding-bottom: 8px; border-bottom: 2px solid #e8eaed; }
        .url-bar { display: flex; gap: 10px; margin-bottom: 16px; }
        .url-bar select { padding: 10px 14px; border: 1px solid #dadce0; border-radius: 8px; font-weight: 700; }
        .url-bar input { flex: 1; padding: 10px 14px; border: 1px solid #dadce0; border-radius: 8px; font-size: 14px; }
        .url-bar input:focus, .url-bar select:focus { outline: none; border-color: #1a73e8; }
        .send-btn { padding: 10px 24px; background: #1a73e8; color: #fff; border: none; border-radius: 8px; font-weight: 700; cursor: pointer; }
        .send-btn:hover { background: #1557b0; }
        .cancel-btn { padding: 10px 24px; background: #ea4335; color: #fff; border: none; border-radius: 8px; font-weight: 700; cursor: pointer; }
        .config-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; margin-bottom: 16px; }
        .config-item label { display: block; font-size: 13px; font-weight: 600; color: #555; margin-bottom: 4px; }
        .config-item select, .config-item input { width: 100%; padding: 8px 12px; border: 1px solid #dadce0; border-radius: 6px; font-size: 13px; }
        .response-area { background: #1e1e1e; color: #d4d4d4; padding: 16px; border-radius: 8px; font-family: 'Consolas', monospace; font-size: 13px; line-height: 1.7; max-height: 400px; overflow-y: auto; white-space: pre-wrap; word-break: break-all; }
        .status-badge { display: inline-block; padding: 4px 12px; border-radius: 12px; font-weight: 700; font-size: 13px; margin-bottom: 12px; }
        .status-ok { background: #e8f5e9; color: #2e7d32; }
        .status-err { background: #ffebee; color: #c62828; }
        .headers-table { width: 100%; border-collapse: collapse; margin-bottom: 12px; }
        .headers-table th, .headers-table td { padding: 6px 10px; text-align: left; border-bottom: 1px solid #e8eaed; font-size: 13px; }
        .headers-table th { background: #f8f9fa; font-weight: 600; color: #555; }
        textarea { width: 100%; padding: 10px; border: 1px solid #dadce0; border-radius: 8px; font-family: 'Consolas', monospace; font-size: 13px; resize: vertical; margin-bottom: 12px; }
    </style>
</head>
<body>
    <div class="container">
        <h1>Fetch API完整演示</h1>
        <div class="card">
            <h2>请求配置</h2>
            <div class="url-bar">
                <select id="method"><option>GET</option><option>POST</option><option>PUT</option><option>DELETE</option><option>PATCH</option></select>
                <input type="text" id="url" value="https://httpbin.org/get">
                <button class="send-btn" onclick="sendFetch()">发送</button>
                <button class="cancel-btn" onclick="cancelFetch()">取消</button>
            </div>
            <div class="config-grid">
                <div class="config-item"><label>Mode</label><select id="mode"><option>cors</option><option>no-cors</option><option>same-origin</option></select></div>
                <div class="config-item"><label>Credentials</label><select id="credentials"><option>same-origin</option><option>include</option><option>omit</option></select></div>
                <div class="config-item"><label>Cache</label><select id="cache"><option>default</option><option>no-store</option><option>reload</option><option>no-cache</option></select></div>
                <div class="config-item"><label>Redirect</label><select id="redirect"><option>follow</option><option>error</option><option>manual</option></select></div>
            </div>
            <textarea id="body" rows="3" placeholder='请求体 (POST/PUT/PATCH)'></textarea>
        </div>
        <div class="card">
            <h2>响应结果</h2>
            <div id="responseInfo">点击"发送"按钮发起请求</div>
        </div>
    </div>
    <script>
        let controller = null;
        function sendFetch() {
            if (controller) controller.abort();
            controller = new AbortController();
            const method = document.getElementById('method').value;
            const url = document.getElementById('url').value;
            const body = document.getElementById('body').value;
            const options = {
                method,
                signal: controller.signal,
                mode: document.getElementById('mode').value,
                credentials: document.getElementById('credentials').value,
                cache: document.getElementById('cache').value,
                redirect: document.getElementById('redirect').value,
                headers: { 'Accept': 'application/json' }
            };
            if (['POST','PUT','PATCH'].includes(method) && body) {
                options.headers['Content-Type'] = 'application/json';
                options.body = body;
            }
            document.getElementById('responseInfo').innerHTML = '<span class="status-badge" style="background:#e3f2fd;color:#1565c0;">请求中...</span>';
            fetch(url, options).then(async response => {
                const headers = [];
                response.headers.forEach((v, k) => headers.push({ name: k, value: v }));
                let bodyText = '';
                try { bodyText = JSON.stringify(await response.json(), null, 2); } catch { bodyText = await response.text(); }
                const badge = response.ok ? 'status-ok' : 'status-err';
                document.getElementById('responseInfo').innerHTML = `
                    <span class="status-badge ${badge}">${response.status} ${response.statusText}</span>
                    <span style="font-size:13px;color:#888;">type: ${response.type} | redirected: ${response.redirected} | ok: ${response.ok}</span>
                    <table class="headers-table"><thead><tr><th>响应头</th><th>值</th></tr></thead><tbody>
                    ${headers.map(h => `<tr><td>${h.name}</td><td>${h.value}</td></tr>`).join('')}
                    </tbody></table>
                    <div class="response-area">${bodyText}</div>`;
            }).catch(error => {
                if (error.name === 'AbortError') {
                    document.getElementById('responseInfo').innerHTML = '<span class="status-badge status-err">请求已取消</span>';
                } else {
                    document.getElementById('responseInfo').innerHTML = `<span class="status-badge status-err">请求失败</span><div class="response-area">${error.message}</div>`;
                }
            });
        }
        function cancelFetch() { if (controller) { controller.abort(); controller = null; } }
    </script>
</body>
</html>

示例2:流式数据读取演示

代码示例

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>流式数据读取演示</title>
    <style>
        * { margin: 0; padding: 0; box-sizing: border-box; }
        body { font-family: 'Segoe UI', system-ui, sans-serif; background: #f0f2f5; padding: 20px; color: #333; }
        .container { max-width: 800px; margin: 0 auto; }
        h1 { text-align: center; color: #1a73e8; margin-bottom: 24px; font-size: 28px; }
        .card { background: #fff; border-radius: 12px; padding: 24px; margin-bottom: 20px; box-shadow: 0 2px 8px rgba(0,0,0,0.08); }
        .card h2 { font-size: 18px; color: #1a73e8; margin-bottom: 16px; padding-bottom: 8px; border-bottom: 2px solid #e8eaed; }
        .stream-output { background: #1e1e1e; color: #d4d4d4; padding: 16px; border-radius: 8px; font-family: 'Consolas', monospace; font-size: 13px; line-height: 1.7; min-height: 200px; max-height: 400px; overflow-y: auto; white-space: pre-wrap; word-break: break-all; }
        .controls { display: flex; gap: 10px; margin-bottom: 16px; }
        button { padding: 10px 20px; border: none; border-radius: 8px; font-weight: 600; cursor: pointer; }
        .btn-primary { background: #1a73e8; color: #fff; }
        .btn-danger { background: #ea4335; color: #fff; }
        .stats { display: flex; gap: 16px; margin-top: 12px; }
        .stat-item { padding: 8px 16px; background: #f8f9fa; border-radius: 6px; font-size: 13px; }
        .stat-value { font-weight: 700; color: #1a73e8; }
    </style>
</head>
<body>
    <div class="container">
        <h1>流式数据读取演示</h1>
        <div class="card">
            <h2>模拟流式数据</h2>
            <div class="controls">
                <button class="btn-primary" onclick="startStream()">开始流式读取</button>
                <button class="btn-danger" onclick="stopStream()">停止</button>
            </div>
            <div class="stream-output" id="streamOutput">等待开始...</div>
            <div class="stats">
                <div class="stat-item">已接收: <span class="stat-value" id="bytesReceived">0</span> 字节</div>
                <div class="stat-item">数据块: <span class="stat-value" id="chunksReceived">0</span> 个</div>
                <div class="stat-item">耗时: <span class="stat-value" id="elapsed">0</span> ms</div>
            </div>
        </div>
    </div>
    <script>
        let streamController = null;
        async function startStream() {
            streamController = new AbortController();
            const output = document.getElementById('streamOutput');
            output.textContent = '';
            let totalBytes = 0, chunks = 0;
            const startTime = Date.now();
            try {
                const response = await fetch('https://httpbin.org/stream/10', { signal: streamController.signal });
                const reader = response.body.getReader();
                const decoder = new TextDecoder();
                while (true) {
                    const { done, value } = await reader.read();
                    if (done) break;
                    chunks++;
                    totalBytes += value.length;
                    const text = decoder.decode(value, { stream: true });
                    output.textContent += text;
                    document.getElementById('bytesReceived').textContent = totalBytes;
                    document.getElementById('chunksReceived').textContent = chunks;
                    document.getElementById('elapsed').textContent = Date.now() - startTime;
                    output.scrollTop = output.scrollHeight;
                }
                output.textContent += '\n\n--- 流式读取完成 ---';
            } catch (error) {
                if (error.name === 'AbortError') {
                    output.textContent += '\n\n--- 读取已取消 ---';
                } else {
                    output.textContent += '\n\n错误: ' + error.message;
                }
            }
        }
        function stopStream() { if (streamController) { streamController.abort(); streamController = null; } }
    </script>
</body>
</html>

示例3:Fetch工具类封装

代码示例

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Fetch工具类封装</title>
    <style>
        * { margin: 0; padding: 0; box-sizing: border-box; }
        body { font-family: 'Segoe UI', system-ui, sans-serif; background: #f0f2f5; padding: 20px; color: #333; }
        .container { max-width: 900px; margin: 0 auto; }
        h1 { text-align: center; color: #1a73e8; margin-bottom: 24px; font-size: 28px; }
        .card { background: #fff; border-radius: 12px; padding: 24px; margin-bottom: 20px; box-shadow: 0 2px 8px rgba(0,0,0,0.08); }
        .card h2 { font-size: 18px; color: #1a73e8; margin-bottom: 16px; padding-bottom: 8px; border-bottom: 2px solid #e8eaed; }
        .code-block { background: #1e1e1e; color: #d4d4d4; padding: 16px; border-radius: 8px; font-family: 'Consolas', monospace; font-size: 12px; line-height: 1.7; white-space: pre-wrap; margin-bottom: 16px; }
        .demo-btns { display: flex; gap: 10px; flex-wrap: wrap; margin-bottom: 16px; }
        button { padding: 10px 20px; border: none; border-radius: 8px; font-weight: 600; cursor: pointer; }
        .btn-blue { background: #1a73e8; color: #fff; }
        .btn-green { background: #34a853; color: #fff; }
        .btn-orange { background: #ff9800; color: #fff; }
        .result { background: #f8f9fa; padding: 16px; border-radius: 8px; font-family: 'Consolas', monospace; font-size: 13px; min-height: 80px; white-space: pre-wrap; }
    </style>
</head>
<body>
    <div class="container">
        <h1>Fetch工具类封装</h1>
        <div class="card">
            <h2>HttpFetch工具类</h2>
            <div class="code-block">class HttpFetch {
  constructor(baseURL = '', defaultConfig = {}) {
    this.baseURL = baseURL;
    this.defaultConfig = {
      headers: { 'Accept': 'application/json' },
      timeout: 30000,
      ...defaultConfig
    };
  }

  async request(url, options = {}) {
    const config = { ...this.defaultConfig, ...options };
    const controller = new AbortController();
    const timeoutId = setTimeout(() => controller.abort(), config.timeout);
    const fetchOptions = {
      ...config,
      signal: controller.signal,
      headers: { ...this.defaultConfig.headers, ...options.headers }
    };
    if (config.body && typeof config.body === 'object') {
      fetchOptions.body = JSON.stringify(config.body);
      fetchOptions.headers['Content-Type'] = 'application/json';
    }
    try {
      const response = await fetch(this.baseURL + url, fetchOptions);
      if (!response.ok) {
        const error = await response.json().catch(() => ({}));
        throw new HttpError(response.status, response.statusText, error);
      }
      if (response.status === 204) return null;
      return await response.json();
    } finally {
      clearTimeout(timeoutId);
    }
  }

  get(url, params, options) {
    const query = params ? '?' + new URLSearchParams(params) : '';
    return this.request(url + query, { ...options, method: 'GET' });
  }
  post(url, body, options) {
    return this.request(url, { ...options, method: 'POST', body });
  }
  put(url, body, options) {
    return this.request(url, { ...options, method: 'PUT', body });
  }
  patch(url, body, options) {
    return this.request(url, { ...options, method: 'PATCH', body });
  }
  delete(url, options) {
    return this.request(url, { ...options, method: 'DELETE' });
  }
}

class HttpError extends Error {
  constructor(status, statusText, data) {
    super(`${status} ${statusText}`);
    this.status = status;
    this.statusText = statusText;
    this.data = data;
  }
}</div>
        </div>
        <div class="card">
            <h2>使用演示</h2>
            <div class="demo-btns">
                <button class="btn-blue" onclick="demoGet()">GET</button>
                <button class="btn-green" onclick="demoPost()">POST</button>
                <button class="btn-orange" onclick="demoTimeout()">超时测试</button>
            </div>
            <div class="result" id="result">点击按钮测试</div>
        </div>
    </div>
    <script>
        class HttpFetch {
            constructor(baseURL = '', defaultConfig = {}) {
                this.baseURL = baseURL;
                this.defaultConfig = { headers: { 'Accept': 'application/json' }, timeout: 30000, ...defaultConfig };
            }
            async request(url, options = {}) {
                const config = { ...this.defaultConfig, ...options };
                const controller = new AbortController();
                const timeoutId = setTimeout(() => controller.abort(), config.timeout);
                const fetchOptions = { ...config, signal: controller.signal, headers: { ...this.defaultConfig.headers, ...options.headers } };
                if (config.body && typeof config.body === 'object') {
                    fetchOptions.body = JSON.stringify(config.body);
                    fetchOptions.headers['Content-Type'] = 'application/json';
                }
                try {
                    const response = await fetch(this.baseURL + url, fetchOptions);
                    if (!response.ok) {
                        const error = await response.json().catch(() => ({}));
                        throw new Error(`HTTP ${response.status}: ${JSON.stringify(error)}`);
                    }
                    if (response.status === 204) return null;
                    return await response.json();
                } finally { clearTimeout(timeoutId); }
            }
            get(url, params, options) {
                const query = params ? '?' + new URLSearchParams(params) : '';
                return this.request(url + query, { ...options, method: 'GET' });
            }
            post(url, body, options) { return this.request(url, { ...options, method: 'POST', body }); }
            put(url, body, options) { return this.request(url, { ...options, method: 'PUT', body }); }
            delete(url, options) { return this.request(url, { ...options, method: 'DELETE' }); }
        }

        const http = new HttpFetch('https://httpbin.org');

        async function demoGet() {
            document.getElementById('result').textContent = 'GET请求中...';
            try {
                const data = await http.get('/get', { name: '张三', page: 1 });
                document.getElementById('result').textContent = 'GET成功!\n' + JSON.stringify(data, null, 2);
            } catch (e) { document.getElementById('result').textContent = '错误: ' + e.message; }
        }
        async function demoPost() {
            document.getElementById('result').textContent = 'POST请求中...';
            try {
                const data = await http.post('/post', { name: '张三', email: 'zhangsan@example.com' });
                document.getElementById('result').textContent = 'POST成功!\n' + JSON.stringify(data, null, 2);
            } catch (e) { document.getElementById('result').textContent = '错误: ' + e.message; }
        }
        async function demoTimeout() {
            document.getElementById('result').textContent = '超时测试中(1ms超时)...';
            const fastHttp = new HttpFetch('https://httpbin.org', { timeout: 1 });
            try {
                await fastHttp.get('/delay/5');
            } catch (e) { document.getElementById('result').textContent = '超时捕获成功!\n' + e.message; }
        }
    </script>
</body>
</html>

浏览器兼容性

特性 Chrome Firefox Safari Edge 说明
fetch() 42+ 39+ 10.1+ 14+ 基本功能
Request/Response 42+ 39+ 10.1+ 14+ 标准对象
Headers 42+ 39+ 10.1+ 14+ 头部操作
AbortController 66+ 57+ 12.1+ 16+ 取消请求
Streaming 43+ 65+ 10.1+ 14+ 流式读取
credentials 50+ 39+ 10.1+ 14+ Cookie控制
cache选项 48+ 48+ 11.1+ 15+ 缓存控制
referrerPolicy 52+ 52+ 11.1+ 15+ 引用策略

注意事项与最佳实践

1. 始终检查response.ok

fetch不会因HTTP错误状态码reject,必须手动检查:

代码示例

if (!response.ok) throw new Error(`HTTP ${response.status}`);

2. 合理设置超时

fetch没有内置超时,使用AbortController实现:

代码示例

const controller = new AbortController();
setTimeout(() => controller.abort(), 30000);
await fetch(url, { signal: controller.signal });

3. 正确处理Cookie

跨域请求携带Cookie需要同时设置credentials和CORS头部:

代码示例

// 前端
fetch(url, { credentials: 'include' });

// 后端
Access-Control-Allow-Credentials: true
Access-Control-Allow-Origin: https://specific-origin.com  // 不能为*

4. 避免重复读取body

Response.body只能读取一次,需要多次使用时先clone:

代码示例

const clone = response.clone();
const data = await response.json();
// clone仍可使用

代码规范示例

完整的Fetch封装

代码示例

class HttpClient {
    constructor(baseURL) {
        this.baseURL = baseURL;
        this.interceptors = { request: [], response: [] };
    }

    use(type, interceptor) {
        this.interceptors[type].push(interceptor);
    }

    async request(url, options = {}) {
        let config = { ...options, url: this.baseURL + url };
        // 执行请求拦截器
        for (const interceptor of this.interceptors.request) {
            config = await interceptor(config);
        }
        const controller = new AbortController();
        const timeout = setTimeout(() => controller.abort(), config.timeout || 30000);
        try {
            let response = await fetch(config.url, { ...config, signal: controller.signal });
            // 执行响应拦截器
            for (const interceptor of this.interceptors.response) {
                response = await interceptor(response);
            }
            if (!response.ok) throw new Error(`HTTP ${response.status}`);
            return await response.json();
        } finally { clearTimeout(timeout); }
    }

    get(url, params, options) {
        const query = params ? '?' + new URLSearchParams(params) : '';
        return this.request(url + query, { ...options, method: 'GET' });
    }
    post(url, body, options) {
        return this.request(url, { ...options, method: 'POST', body: JSON.stringify(body),
            headers: { 'Content-Type': 'application/json', ...options?.headers } });
    }
}

// 使用
const api = new HttpClient('https://api.example.com');
api.use('request', async (config) => {
    config.headers = { ...config.headers, 'Authorization': `Bearer ${getToken()}` };
    return config;
});
api.use('response', async (response) => {
    if (response.status === 401) { await refreshToken(); }
    return response;
});

常见问题与解决方案

问题1:fetch不reject HTTP错误

解决方案:封装时统一检查response.ok,抛出包含状态码的错误。

问题2:无法获取下载进度

解决方案:使用response.body流式读取计算进度,或回退到XHR。

问题3:CORS预检请求失败

解决方案:确保服务器正确响应OPTIONS请求,设置必要的CORS头部。

问题4:请求被浏览器缓存

解决方案:设置cache: 'no-store'或在URL添加时间戳参数。

总结

Fetch API是现代前端网络请求的标准方案:

  1. 基于Promise:支持async/await,告别回调地狱

  2. Request/Response:标准化的请求和响应对象

  3. Headers:灵活的头部操作

  4. 流式读取:使用Streams API处理大数据

  5. AbortController:优雅的请求取消机制

  6. 丰富的配置:mode、credentials、cache、redirect等选项

  7. 与XHR对比:API更现代,但缺少原生进度和超时支持

掌握Fetch API的各种配置和最佳实践,能够让你在前后端通信中游刃有余。

常见问题

什么是Fetch API详解?

Fetch API详解是HTML5开发中的重要技术,本教程详细介绍了其核心概念和实践方法。

如何学习Fetch API详解的实际应用?

教程中提供了完整的代码示例和实践指导,建议结合示例代码动手练习。

Fetch API详解有哪些注意事项?

常见注意事项包括兼容性、性能优化等,教程的注意事项与最佳实践部分有详细说明。

Fetch API详解适合初学者吗?

本教程从基础概念讲起,循序渐进,适合有一定HTML和JavaScript基础的初学者。

Fetch API详解的核心要点是什么?

核心要点包括教程简介等内容,建议按教程顺序逐步学习。

标签: Promise HTTP头部 响应码 async await 场景管理 请求缓存 Fetch API RESTful

本文由小确幸生活整理发布,转载请注明出处

本文涉及AI创作

内容由AI创作,请仔细甄别

list快速访问

上一篇: 后端通信:Ajax基础与XMLHttpRequest - 从入门到实践详解 下一篇: 后端通信:Promise与异步编程 - 从入门到实践详解

poll相关推荐