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

HTML5 API:HTML5 Fetch API - 完整教程与代码示例

一、教程简介

Fetch API 是 HTML5 提供的现代网络请求接口,用于替代传统的 XMLHttpRequest。Fetch 基于 Promise 设计,API 更加简洁直观,支持请求和响应的流式处理,默认不发送 Cookie(需手动配置),提供了更灵活的请求控制和错误处理机制。Fetch API 是现代 Web 应用中进行 HTTP 请求的首选方案。

二、核心概念

Fetch vs XMLHttpRequest

对比项 Fetch XMLHttpRequest
设计模式 Promise 事件回调
语法 简洁 复杂
流式处理 支持 不支持
CORS 默认支持 需配置
Cookie 默认不发送 默认发送
超时 需手动实现 有 timeout 属性
进度 有限支持 完善
取消 AbortController abort()

请求配置

代码示例

fetch(url, {
    method: 'GET',               // 请求方法
    headers: {                    // 请求头
        'Content-Type': 'application/json'
    },
    body: JSON.stringify(data),   // 请求体
    mode: 'cors',                 // 模式:cors/no-cors/same-origin
    credentials: 'same-origin',   // Cookie:omit/same-origin/include
    cache: 'default',             // 缓存策略
    redirect: 'follow',           // 重定向:follow/error/manual
    referrer: 'no-referrer',      // 引用来源
    signal: abortController.signal // 取消信号
});

Response 对象

属性/方法 说明
ok 状态码 200-299 为 true
status HTTP 状态码
statusText 状态文本
headers 响应头
url 响应 URL
type 响应类型
json() 解析为 JSON
text() 解析为文本
blob() 解析为 Blob
arrayBuffer() 解析为 ArrayBuffer
formData() 解析为 FormData
clone() 克隆响应

三、语法与用法

基本请求

代码示例

// GET 请求
const response = await fetch('/api/data');
const data = await response.json();

// POST 请求
const response = await fetch('/api/data', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ name: '张三', age: 25 })
});

错误处理

代码示例

try {
    const response = await fetch('/api/data');
    if (!response.ok) {
        throw new Error('HTTP ' + response.status);
    }
    const data = await response.json();
} catch (error) {
    console.error('请求失败:', error);
}

取消请求

代码示例

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

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

// 取消
controller.abort();

四、代码示例

示例一: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 API - 请求工具</title>
    <style>
        * { margin: 0; padding: 0; box-sizing: border-box; }
        body {
            font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
            background: #1a1a2e;
            min-height: 100vh;
            padding: 30px 20px;
            color: #e0e0e0;
        }
        .container { max-width: 700px; margin: 0 auto; }
        h1 { font-size: 24px; color: #fff; margin-bottom: 8px; }
        .desc { color: #7a8ba0; font-size: 14px; margin-bottom: 24px; }
        .card {
            background: #16213e;
            border-radius: 12px;
            padding: 24px;
            margin-bottom: 16px;
            border: 1px solid #1a3a5c;
        }
        .card h2 { font-size: 16px; color: #fff; margin-bottom: 16px; }
        .input-group { margin-bottom: 12px; }
        .input-group label {
            display: block;
            font-size: 12px;
            color: #7a8ba0;
            margin-bottom: 4px;
        }
        input, select, textarea {
            width: 100%;
            padding: 8px 12px;
            border: 1px solid #1a3a5c;
            border-radius: 6px;
            background: #0f3460;
            color: #e0e0e0;
            font-size: 13px;
            outline: none;
        }
        input:focus, select:focus { border-color: #00cec9; }
        .btn-group { display: flex; gap: 10px; flex-wrap: wrap; }
        button {
            padding: 8px 18px;
            border: none;
            border-radius: 6px;
            font-size: 13px;
            font-weight: 600;
            cursor: pointer;
        }
        .btn-cyan { background: #00cec9; color: #1a1a2e; }
        .btn-cyan:hover { background: #00b5ad; }
        .btn-red { background: #e17055; color: #fff; }
        .btn-red:hover { background: #d35f48; }
        .btn-orange { background: #fdcb6e; color: #1a1a2e; }
        .btn-orange:hover { background: #e5b85c; }
        .response-area {
            background: #0a0a1a;
            border-radius: 8px;
            padding: 16px;
            margin-top: 12px;
            font-family: monospace;
            font-size: 12px;
            line-height: 1.7;
            max-height: 400px;
            overflow-y: auto;
            white-space: pre-wrap;
            word-break: break-all;
        }
        .status-badge {
            display: inline-block;
            padding: 2px 10px;
            border-radius: 4px;
            font-size: 12px;
            font-weight: 600;
            margin-bottom: 8px;
        }
        .status-2xx { background: #00b894; color: #fff; }
        .status-4xx { background: #fdcb6e; color: #1a1a2e; }
        .status-5xx { background: #e17055; color: #fff; }
        .status-error { background: #636e72; color: #fff; }
        .quick-urls {
            display: flex;
            gap: 8px;
            flex-wrap: wrap;
            margin-bottom: 12px;
        }
        .quick-url {
            padding: 4px 12px;
            border: 1px solid #1a3a5c;
            border-radius: 4px;
            font-size: 11px;
            cursor: pointer;
            color: #7a8ba0;
            transition: all 0.2s;
        }
        .quick-url:hover { border-color: #00cec9; color: #00cec9; }
        .loading { color: #fdcb6e; }
        .timer { font-size: 12px; color: #7a8ba0; margin-top: 8px; }
    </style>
</head>
<body>
    <div class="container">
        <h1>Fetch 请求工具</h1>
        <p class="desc">使用 Fetch API 发送 HTTP 请求,查看响应结果。</p>

        <div class="card">
            <h2>请求配置</h2>
            <div class="quick-urls">
                <span class="quick-url" onclick="setUrl('https://jsonplaceholder.typicode.com/posts/1')">获取文章</span>
                <span class="quick-url" onclick="setUrl('https://jsonplaceholder.typicode.com/users')">用户列表</span>
                <span class="quick-url" onclick="setUrl('https://jsonplaceholder.typicode.com/todos?_limit=5')">待办事项</span>
            </div>
            <div class="input-group">
                <label>请求 URL</label>
                <input type="text" id="reqUrl" value="https://jsonplaceholder.typicode.com/posts/1">
            </div>
            <div style="display:flex;gap:12px;">
                <div class="input-group" style="flex:0 0 120px;">
                    <label>方法</label>
                    <select id="reqMethod">
                        <option>GET</option>
                        <option>POST</option>
                        <option>PUT</option>
                        <option>DELETE</option>
                    </select>
                </div>
                <div class="input-group" style="flex:1;">
                    <label>请求体(JSON)</label>
                    <input type="text" id="reqBody" placeholder='{"key":"value"}'>
                </div>
            </div>
            <div class="btn-group">
                <button class="btn-cyan" id="sendBtn" onclick="sendRequest()">发送请求</button>
                <button class="btn-red" id="cancelBtn" onclick="cancelRequest()" disabled>取消请求</button>
            </div>
        </div>

        <div class="card">
            <h2>响应结果</h2>
            <div id="responseStatus"></div>
            <div class="response-area" id="responseBody">等待请求...</div>
            <div class="timer" id="responseTimer"></div>
        </div>
    </div>

    <script>
        let currentController = null;

        function setUrl(url) {
            document.getElementById('reqUrl').value = url;
            document.getElementById('reqMethod').value = 'GET';
            document.getElementById('reqBody').value = '';
        }

        async function sendRequest() {
            const url = document.getElementById('reqUrl').value.trim();
            const method = document.getElementById('reqMethod').value;
            const body = document.getElementById('reqBody').value.trim();

            if (!url) { alert('请输入 URL'); return; }

            currentController = new AbortController();
            document.getElementById('sendBtn').disabled = true;
            document.getElementById('cancelBtn').disabled = false;
            document.getElementById('responseBody').textContent = '请求中...';
            document.getElementById('responseBody').className = 'response-area loading';
            document.getElementById('responseStatus').innerHTML = '';
            document.getElementById('responseTimer').textContent = '';

            const startTime = performance.now();

            try {
                const options = {
                    method: method,
                    signal: currentController.signal,
                    headers: {}
                };

                if (body && method !== 'GET') {
                    options.headers['Content-Type'] = 'application/json';
                    options.body = body;
                }

                const response = await fetch(url, options);
                const duration = (performance.now() - startTime).toFixed(0);

                const statusClass = response.ok ? 'status-2xx' :
                    response.status < 500 ? 'status-4xx' : 'status-5xx';

                document.getElementById('responseStatus').innerHTML =
                    '<span class="status-badge ' + statusClass + '">' + response.status + ' ' + response.statusText + '</span>';

                const contentType = response.headers.get('content-type') || '';
                let result;
                if (contentType.includes('json')) {
                    result = JSON.stringify(await response.json(), null, 2);
                } else {
                    result = await response.text();
                }

                document.getElementById('responseBody').textContent = result;
                document.getElementById('responseBody').className = 'response-area';
                document.getElementById('responseTimer').textContent =
                    '耗时: ' + duration + 'ms | 大小: ' + (result.length / 1024).toFixed(1) + ' KB';

            } catch (error) {
                const statusClass = error.name === 'AbortError' ? 'status-error' : 'status-5xx';
                document.getElementById('responseStatus').innerHTML =
                    '<span class="status-badge ' + statusClass + '">' + error.name + '</span>';
                document.getElementById('responseBody').textContent = error.message;
                document.getElementById('responseBody').className = 'response-area';
            } finally {
                document.getElementById('sendBtn').disabled = false;
                document.getElementById('cancelBtn').disabled = true;
                currentController = null;
            }
        }

        function cancelRequest() {
            if (currentController) {
                currentController.abort();
            }
        }
    </script>
</body>
</html>

五、浏览器兼容性

浏览器 支持版本 AbortController
Chrome 42+ 66+
Firefox 39+ 57+
Safari 10.1+ 12.1+
Edge 14+ 16+
Opera 29+ 53+
IE 不支持 不支持

六、注意事项与最佳实践

1. 错误处理

代码示例

// Fetch 只在网络错误时 reject,HTTP 错误不会
async function safeFetch(url, options) {
    try {
        const response = await fetch(url, options);
        if (!response.ok) {
            throw new Error('HTTP ' + response.status + ': ' + response.statusText);
        }
        return await response.json();
    } catch (error) {
        if (error.name === 'AbortError') throw error;
        throw new Error('请求失败: ' + error.message);
    }
}

2. 超时处理

代码示例

function fetchWithTimeout(url, options = {}, timeout = 10000) {
    const controller = new AbortController();
    const id = setTimeout(() => controller.abort(), timeout);

    return fetch(url, { ...options, signal: controller.signal })
        .then(response => {
            clearTimeout(id);
            return response;
        })
        .catch(error => {
            clearTimeout(id);
            if (error.name === 'AbortError') {
                throw new Error('请求超时');
            }
            throw error;
        });
}

3. Cookie 配置

代码示例

// 发送 Cookie
fetch('/api/data', { credentials: 'include' });

// 同源发送 Cookie(默认)
fetch('/api/data', { credentials: 'same-origin' });

// 不发送 Cookie
fetch('/api/data', { credentials: 'omit' });

七、代码规范示例

代码示例

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

    async request(url, options = {}) {
        const fullURL = this.baseURL + url;
        let config = { ...this.defaultOptions, ...options };

        // 请求拦截器
        for (const interceptor of this.interceptors.request) {
            config = await interceptor(config);
        }

        let response = await fetch(fullURL, config);

        // 响应拦截器
        for (const interceptor of this.interceptors.response) {
            response = await interceptor(response);
        }

        if (!response.ok) {
            throw new Error('HTTP ' + response.status);
        }

        return response;
    }

    get(url, options = {}) {
        return this.request(url, { ...options, method: 'GET' });
    }

    post(url, body, options = {}) {
        return this.request(url, {
            ...options,
            method: 'POST',
            headers: { 'Content-Type': 'application/json', ...options.headers },
            body: JSON.stringify(body)
        });
    }

    put(url, body, options = {}) {
        return this.request(url, {
            ...options,
            method: 'PUT',
            headers: { 'Content-Type': 'application/json', ...options.headers },
            body: JSON.stringify(body)
        });
    }

    delete(url, options = {}) {
        return this.request(url, { ...options, method: 'DELETE' });
    }
}

// 使用
const api = new HttpClient('https://api.example.com', {
    credentials: 'include'
});

const data = await api.get('/users');
const result = await api.post('/users', { name: '张三' });

八、常见问题与解决方案

常见问题

Q1:Fetch 不发送 Cookie?

解决方案:设置 credentials: 'include'(跨域)或 credentials: 'same-origin'(同源)。

Q2:如何获取上传进度?

解答:Fetch API 不支持上传进度。如需进度监控,使用 XMLHttpRequest。

Q3:CORS 错误怎么处理?

解决方案:确保服务器设置了正确的 CORS 头(Access-Control-Allow-Origin 等),或使用代理服务器。

Q4:如何重试失败的请求?

解决方案:使用循环和指数退避策略实现请求重试。

代码示例

async function fetchWithRetry(url, options = {}, retries = 3) {
    for (let i = 0; i < retries; i++) {
        try {
            const response = await fetch(url, options);
            if (response.ok) return response;
        } catch (error) {
            if (i === retries - 1) throw error;
            await new Promise(r => setTimeout(r, 1000 * Math.pow(2, i)));
        }
    }
}

九、总结

Fetch API 是现代 Web 开发中进行 HTTP 请求的标准方案,基于 Promise 设计,语法简洁直观。使用时需注意:HTTP 错误不会导致 Promise reject(需手动检查 response.ok)、默认不发送 Cookie(需配置 credentials)、不支持上传进度(需用 XHR)、需要配合 AbortController 实现请求取消和超时。通过封装 HTTP 客户端类可以实现拦截器、重试、超时等高级功能。

标签: Fetch API 网络请求 Promise HTTP AbortController

本文涉及AI创作

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

list快速访问

上一篇: HTML5 API:HTML5 Performance Observe - 完整教程与代码示例 下一篇: HTML5 API:HTML5 Blob API - 完整教程与代码示例

poll相关推荐