fetch 是现代浏览器提供的一个强大的网络请求 API,用于发起 HTTP 请求并处理响应。它基于 Promise,提供了比传统的 XMLHttpRequest 更简洁、更灵活的接口。

1. fetch 的基本语法

fetch(resource, options)
  .then(response => {
    // 处理响应
  })
  .catch(error => {
    // 处理错误
  });

其中:

  • resource:可以是 URL 字符串或 Request 对象。

  • options:可选的配置对象,包含请求方法、头信息、请求体等。

2. fetch 的核心特性

2.1 返回 Promise 对象

fetch 方法返回一个 Promise,这使得我们可以使用 .then().catch() 链式调用,或结合 async/await 来处理异步请求。

// 基本使用示例
fetch('https://api.example.com/data')
  .then(response => {
    if (!response.ok) {
      throw new Error('Network response was not ok');
    }
    return response.json(); // 解析 JSON 数据
  })
  .then(data => {
    console.log(data); // 使用解析后的数据
  })
  .catch(error => {
    console.error('There was a problem with the fetch operation:', error);
  });
2.2 状态处理特点

重要特性:fetch 的 Promise 不会因为 HTTP 错误状态码(如 404、500)而被拒绝,只有当网络请求本身失败时才会拒绝 (如断网、跨域问题等)。因此,必须手动检查 response.ok 属性。

response.ok 是一个布尔值,当响应状态码在 200-299 范围内时为 true。

3. fetch 的请求配置

fetch 支持多种配置选项,通过第二个参数传入:

fetch('https://api.example.com/data', {
  method: 'POST', // GET, POST, PUT, DELETE, PATCH 等
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer token123'
  },
  body: JSON.stringify({ name: 'John', age: 30 }), // 请求体,仅用于 POST, PUT 等
  credentials: 'include', // 包含 cookies,处理跨域认证
  cache: 'no-cache', // 缓存策略
  redirect: 'follow', // 重定向处理
  referrerPolicy: 'no-referrer', // 引用策略
  mode: 'cors', // 模式:cors, no-cors, same-origin
  signal: abortController.signal // 用于中止请求
})
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error('Error:', error));

4. 响应对象的处理方法

fetch 返回的响应对象提供了多种方法来解析不同类型的响应数据:

这些方法都是异步的,返回 Promise,需要再次使用 .then() 或 await 来获取解析后的数据。

5. fetch 的错误处理

由于 fetch 只会在网络错误时才会 reject Promise,因此需要手动处理 HTTP 错误状态码:

fetch('https://api.example.com/data')
  .then(response => {
    // 检查响应状态
    if (!response.ok) {
      throw new Error(`HTTP error! Status: ${response.status}`);
    }
    return response.json();
  })
  .then(data => console.log(data))
  .catch(error => {
    // 这里会捕获网络错误和手动抛出的 HTTP 错误
    console.error('Fetch error:', error);
  });

6. 高级用法

6.1 使用 async/await 简化代码
async function fetchData() {
  try {
    const response = await fetch('https://api.example.com/data');
    if (!response.ok) {
      throw new Error(`HTTP error! Status: ${response.status}`);
    }
    const data = await response.json();
    console.log(data);
    return data;
  } catch (error) {
    console.error('Fetch error:', error);
    throw error;
  }
}

// 调用函数
fetchData();
6.2 中止正在进行的请求

使用 AbortController 可以中止正在进行的 fetch 请求:

// 创建中止控制器
const controller = new AbortController();
const signal = controller.signal;

// 5 秒后中止请求
// 执行setTimeoout后不会形成阻塞,后面的fetch函数会立即执行。
// 这是由JavaScript的异步非阻塞特性决定的。
// 当定时器触发时,会调用AbortController实例的abort()方法
// 这会触发与该控制器关联的signal对象发出中止信号
// fetch请求监听到这个信号后,会立即终止正在进行的网络请求
setTimeout(() => controller.abort(), 5000);

fetch('https://api.example.com/large-data', {
  signal // 传入信号
})
.then(response => response.json())
.then(data => console.log(data))
.catch(error => {
  if (error.name === 'AbortError') {
    console.log('Fetch aborted');
  } else {
    console.error('Fetch error:', error);
  }
});
// 作用意义
// 1. 防止网络请求因服务器响应缓慢而无限期等待。
// 2. 避免长时间占用浏览器资源。
// 3. 提高用户体验,及时处理超时情况。
// 4. 在代码中通过catch捕获AbortError来区分是请求中止还是其他错误。
6.3 自定义请求对象

可以使用 Request 构造函数创建请求对象,实现更复杂的请求配置:

const request = new Request('https://api.example.com/data', {
  method: 'POST',
  headers: new Headers({
    'Content-Type': 'application/json'
  }),
  body: JSON.stringify({ key: 'value' })
});

fetch(request)
  .then(response => response.json())
  .then(data => console.log(data));
6.4 使用 Headers 处理请求头
const headers = new Headers();
headers.append('Content-Type', 'application/json');
headers.append('Authorization', 'Bearer token123');

fetch('https://api.example.com/data', {
  method: 'GET',
  headers: headers
})
.then(response => response.json())
.then(data => console.log(data));

7. fetch 与其他请求方式的比较

8. 浏览器兼容性

fetch 在所有现代浏览器中都有很好的支持,但在 IE 中不支持。如果需要兼容 IE,可以使用 fetch polyfill,如 whatwg-fetch。

9. 实际应用示例

9.1 获取用户数据
async function getUserData(userId) {
  try {
    const response = await fetch(`https://api.example.com/users/${userId}`);
    if (!response.ok) {
      throw new Error(`Failed to fetch user: ${response.status}`);
    }
    return await response.json();
  } catch (error) {
    console.error('Error fetching user data:', error);
    return null;
  }
}

// 使用
const user = await getUserData(123);
if (user) {
  console.log('User name:', user.name);
}
9.2 提交表单数据
async function submitForm(formData) {
  try {
    const response = await fetch('https://api.example.com/submit', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json'
      },
      body: JSON.stringify(formData)
    });
    
    if (!response.ok) {
      throw new Error(`Form submission failed: ${response.status}`);
    }
    
    return await response.json();
  } catch (error) {
    console.error('Error submitting form:', error);
    throw error;
  }
}

// 使用
const result = await submitForm({
  name: 'Alice',
  email: 'alice@example.com'
});
console.log('Submission result:', result);

10. 注意事项

1. 默认不发送 Cookie:需要设置 credentials: 'include' 才会在请求中包含 Cookie。

2. 需要手动处理 HTTP 错误状态码:即使返回 404、500 等错误,fetch 也会 resolve Promise。

3. 跨域请求限制:受浏览器同源策略限制,需要服务器设置正确的 CORS 头。

4. 不支持 progress 事件:如果需要上传进度,可能需要使用 XMLHttpRequest 或新的 Streams API。

5. 请求超时处理:fetch 没有内置的超时设置,需要使用 AbortController 手动实现。

通过掌握 fetch API,你可以在 JavaScript 中更加高效地处理网络请求,编写更加现代、简洁的异步代码。