Fetch API

fetch基本用法

Fetch API是现代的网络请求接口。

基本语法

// 基本用法
fetch("/api/data")
    .then(response => response.json())
    .then(data => console.log(data))
    .catch(error => console.error(error));

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

fetch返回值

// fetch返回Promise,解析为Response对象
const response = await fetch("/api/data");

console.log(response.ok);       // 是否成功(status 200-299)
console.log(response.status);   // HTTP状态码
console.log(response.statusText);// 状态文本
console.log(response.headers);  // 响应头
console.log(response.url);      // 最终URL
console.log(response.redirected);// 是否重定向
console.log(response.type);     // 类型:basic, cors, opaque

Request对象

创建和配置请求。

创建Request

// 使用Request对象
const request = new Request("/api/data", {
    method: "GET",
    headers: {
        "Content-Type": "application/json"
    }
});

fetch(request)
    .then(response => response.json())
    .then(data => console.log(data));

// 直接传递配置
fetch("/api/data", {
    method: "GET",
    headers: {
        "Content-Type": "application/json"
    }
});

Request属性

const request = new Request("/api/data", {
    method: "POST",
    headers: {
        "Content-Type": "application/json"
    },
    body: JSON.stringify({ name: "东巴文" }),
    mode: "cors",
    credentials: "include",
    cache: "no-cache",
    redirect: "follow",
    referrer: "no-referrer"
});

console.log(request.url);        // URL
console.log(request.method);     // 方法
console.log(request.headers);    // 请求头
console.log(request.body);       // 请求体
console.log(request.mode);       // 模式
console.log(request.credentials);// 凭证
console.log(request.cache);      // 缓存模式
console.log(request.redirect);   // 重定向模式

Response对象

处理服务器响应。

Response属性

const response = await fetch("/api/data");

// 状态信息
console.log(response.status);      // 200
console.log(response.statusText);  // "OK"
console.log(response.ok);          // true

// 响应头
console.log(response.headers.get("Content-Type"));
console.log(response.headers.get("Content-Length"));

// 遍历响应头
for (const [key, value] of response.headers) {
    console.log(key, value);
}

响应体解析

const response = await fetch("/api/data");

// JSON
const json = await response.json();

// 文本
const text = await response.text();

// Blob
const blob = await response.blob();

// ArrayBuffer
const buffer = await response.arrayBuffer();

// FormData
const formData = await response.formData();

// 注意:响应体只能读取一次
// const json = await response.json();
// const text = await response.text();  // 错误:已读取

克隆Response

const response = await fetch("/api/data");

// 克隆响应
const cloned = response.clone();

// 可以分别读取
const json = await response.json();
const text = await cloned.text();

Headers对象

管理请求和响应头。

创建Headers

// 创建Headers对象
const headers = new Headers();
headers.append("Content-Type", "application/json");
headers.append("Authorization", "Bearer token");

// 使用对象初始化
const headers2 = new Headers({
    "Content-Type": "application/json",
    "Authorization": "Bearer token"
});

// 直接使用对象
fetch("/api/data", {
    headers: {
        "Content-Type": "application/json"
    }
});

Headers方法

const headers = new Headers();

// 添加
headers.append("X-Custom", "value");

// 设置(覆盖)
headers.set("Content-Type", "application/json");

// 获取
console.log(headers.get("Content-Type"));

// 检查
console.log(headers.has("Authorization"));

// 删除
headers.delete("X-Custom");

// 遍历
for (const [key, value] of headers) {
    console.log(key, value);
}

// 转换为对象
const obj = Object.fromEntries(headers);

请求配置

配置fetch请求选项。

method和body

// GET请求
fetch("/api/data");

// POST请求
fetch("/api/users", {
    method: "POST",
    headers: {
        "Content-Type": "application/json"
    },
    body: JSON.stringify({
        name: "东巴文",
        email: "info@db-w.cn"
    })
});

// PUT请求
fetch("/api/users/1", {
    method: "PUT",
    headers: {
        "Content-Type": "application/json"
    },
    body: JSON.stringify({
        name: "新名称"
    })
});

// DELETE请求
fetch("/api/users/1", {
    method: "DELETE"
});

mode选项

// cors: 跨域请求(默认)
fetch("https://api.db-w.cn/data", {
    mode: "cors"
});

// no-cors: 不发送CORS请求
fetch("https://other.com/data", {
    mode: "no-cors"
});

// same-origin: 仅同源
fetch("/api/data", {
    mode: "same-origin"
});

credentials选项

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

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

// include: 始终发送Cookie
fetch("https://api.db-w.cn/data", {
    credentials: "include"
});

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" });

处理响应

处理各种响应类型。

处理JSON

async function fetchJSON(url) {
    const response = await fetch(url);
    
    if (!response.ok) {
        throw new Error(`HTTP ${response.status}`);
    }
    
    return response.json();
}

// 使用
const data = await fetchJSON("/api/data");
console.log(data);

处理文本

async function fetchText(url) {
    const response = await fetch(url);
    return response.text();
}

const html = await fetchText("/page.html");
document.getElementById("content").innerHTML = html;

处理Blob

// 下载文件
async function downloadFile(url, filename) {
    const response = await fetch(url);
    const blob = await response.blob();
    
    const link = document.createElement("a");
    link.href = URL.createObjectURL(blob);
    link.download = filename;
    link.click();
    
    URL.revokeObjectURL(link.href);
}

// 显示图片
async function loadImage(url) {
    const response = await fetch(url);
    const blob = await response.blob();
    
    const img = document.createElement("img");
    img.src = URL.createObjectURL(blob);
    document.body.appendChild(img);
}

Fetch与Promise

fetch返回Promise。

Promise链

fetch("/api/user/1")
    .then(response => {
        if (!response.ok) {
            throw new Error(`HTTP ${response.status}`);
        }
        return response.json();
    })
    .then(user => {
        console.log(user);
        return fetch(`/api/posts/${user.id}`);
    })
    .then(response => response.json())
    .then(posts => {
        console.log(posts);
    })
    .catch(error => {
        console.error("错误:", error);
    });

Promise.all

// 并行请求
const [users, posts, comments] = await Promise.all([
    fetch("/api/users").then(r => r.json()),
    fetch("/api/posts").then(r => r.json()),
    fetch("/api/comments").then(r => r.json())
]);

Fetch错误处理

正确处理fetch错误。

HTTP错误

// fetch不会因HTTP错误状态而reject
fetch("/api/not-found")
    .then(response => {
        console.log(response.status);  // 404
        console.log(response.ok);      // false
        // Promise仍然是fulfilled
    });

// 需要手动检查
async function fetchWithError(url) {
    const response = await fetch(url);
    
    if (!response.ok) {
        const error = new Error(`HTTP ${response.status}`);
        error.response = response;
        throw error;
    }
    
    return response.json();
}

网络错误

// 网络错误会reject
fetch("https://nonexistent.domain")
    .catch(error => {
        console.error("网络错误:", error);
    });

// 完整错误处理
async function safeFetch(url, options = {}) {
    try {
        const response = await fetch(url, options);
        
        if (!response.ok) {
            const error = new Error(`HTTP ${response.status}`);
            error.response = response;
            throw error;
        }
        
        return response;
    } catch (error) {
        if (error.name === "AbortError") {
            console.error("请求被中止");
        } else if (error.name === "TypeError") {
            console.error("网络错误或URL无效");
        }
        throw error;
    }
}

Fetch跨域

处理跨域请求。

CORS请求

// 跨域请求
fetch("https://api.db-w.cn/data", {
    mode: "cors",
    credentials: "include",
    headers: {
        "Content-Type": "application/json"
    }
})
.then(response => response.json())
.then(data => console.log(data));

// 服务器需要设置:
// Access-Control-Allow-Origin: *
// Access-Control-Allow-Credentials: true
// Access-Control-Allow-Headers: Content-Type

简单请求vs预检

// 简单请求:直接发送
fetch("https://api.db-w.cn/data", {
    method: "GET"
});

// 预检请求:先发送OPTIONS
fetch("https://api.db-w.cn/data", {
    method: "POST",
    headers: {
        "Content-Type": "application/json",
        "X-Custom-Header": "value"
    },
    body: JSON.stringify({ data: "东巴文" })
});

Fetch上传文件

使用fetch上传文件。

基本上传

const input = document.querySelector('input[type="file"]');

input.addEventListener("change", async function() {
    const file = this.files[0];
    
    const formData = new FormData();
    formData.append("file", file);
    
    const response = await fetch("/api/upload", {
        method: "POST",
        body: formData
    });
    
    const result = await response.json();
    console.log(result);
});

上传进度

// fetch不支持上传进度,需要使用XMLHttpRequest
function uploadWithProgress(file, onProgress) {
    return new Promise((resolve, reject) => {
        const xhr = new XMLHttpRequest();
        
        xhr.upload.onprogress = (e) => {
            if (e.lengthComputable) {
                onProgress(e.loaded / e.total * 100);
            }
        };
        
        xhr.onload = () => {
            if (xhr.status === 200) {
                resolve(JSON.parse(xhr.responseText));
            } else {
                reject(new Error(`HTTP ${xhr.status}`));
            }
        };
        
        xhr.onerror = () => reject(new Error("网络错误"));
        
        const formData = new FormData();
        formData.append("file", file);
        
        xhr.open("POST", "/api/upload");
        xhr.send(formData);
    });
}

// 使用
await uploadWithProgress(file, (percent) => {
    console.log(`上传进度: ${percent.toFixed(0)}%`);
});

下一步

掌握了Fetch API后,让我们继续学习:

  1. WebSocket - 学习实时通信
  2. ES6核心特性 - 学习ES6新特性
  3. ES7-ES12特性 - 学习新版本特性

东巴文(db-w.cn) - 让编程学习更简单

🎯 东巴文寄语:Fetch API是现代JavaScript网络请求的标准方式,基于Promise设计,语法简洁优雅。在 db-w.cn,我们帮你掌握现代网络请求技术!