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相关推荐