浅析浏览器 `fetch` 请求

112 阅读5分钟

引言

在 Web 应用 开发中,网络请求是前端开发的重要组成部分。随着技术的不断进步,fetch API 作为一种新兴的网络请求方式,逐渐取代了传统的 XMLHttpRequest(XHR)和第三方库 axios

概念

fetch 是一个用于访问和操纵 HTTP 管道的部分(包括响应)的 JavaScript API。它返回一个 Promise,可以处理异步操作,使得代码更加简洁和现代化。

核心概念

  1. Resource: 需要获取的资源,可以是一个 URL 或者一个 Request 对象。
  2. Request: 描述请求的配置对象,包括 URL、方法、头部信息等。
  3. Response: 描述响应的配置对象,包括状态码、头部信息和响应体。

使用方法

基本请求

fetch("https://api.example.com/data")
  .then((response) => response.json())
  .then((data) => console.log(data))
  .catch((error) => console.error("Error:", error));

在这个例子中,我们使用 fetch 发起一个 GET 请求,并处理返回的 JSON 数据。

带参数的请求

const requestOptions = {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
  },
  body: JSON.stringify({ key: "value" }),
};

fetch("https://api.example.com/data", requestOptions)
  .then((response) => response.json())
  .then((data) => console.log(data))
  .catch((error) => console.error("Error:", error));

在这个例子中,我们使用 fetch 发起一个 POST 请求,并发送 JSON 数据。

流式请求与非流式请求

非流式请求

非流式请求是指整个请求和响应过程是完整的,适用于数据量较小的情况。

fetch("https://api.example.com/small-data")
  .then((response) => response.json())
  .then((data) => console.log(data));

在这个例子中,我们使用 fetch 发起一个 GET 请求,并处理返回的 JSON 数据。

流式请求

流式请求允许数据分块传输,适用于数据量较大的情况,可以提高性能和响应速度。

fetch("https://api.example.com/large-data").then((response) => {
  const reader = response.body.getReader();
  const decoder = new TextDecoder("utf-8");

  reader.read().then(function processText({ done, value }) {
    if (done) {
      console.log("Stream is complete");
      return;
    }

    const chunk = decoder.decode(value, { stream: true });
    console.log("Received chunk:", chunk);
    return reader.read().then(processText);
  });
});

在这个例子中,我们使用 fetch 发起一个 GET 请求,并处理返回的流式数据。

fetch 与 XHR、axios 的区别

fetch vs XHR

  1. 语法: fetch 使用 Promise,语法更简洁;XHR 使用回调函数,代码较复杂。
  2. 错误处理: fetch 只在网络错误时拒绝 Promise,HTTP 错误状态(如 404)不会导致拒绝;XHR 在任何错误时都会调用 onerror
  3. 流处理: fetch 支持流式请求,而 XHR 不支持。

fetch vs axios

  1. 库 vs API: fetch 是浏览器内置 API,无需安装;axios 是第三方库,需要安装。
  2. 自动转换: axios 会自动将 JSON 数据转换为 JavaScript 对象,而 fetch 需要手动处理。
  3. 拦截器: axios 支持请求和响应拦截器,fetch 不支持。
  4. 取消请求: axios 支持取消请求,fetch 需要使用 AbortController

优缺点分析

优点

  1. 基于 Promise: fetch 使用 Promise 处理异步操作,使得代码更加简洁和易读。
  2. 更现代的语法: fetch 的语法更加现代化,易于理解和使用。
  3. 流式处理: fetch 支持流式请求和响应,适用于处理大文件或实时数据流。
  4. 内置跨域支持: fetch 默认支持跨域请求(CORS),无需额外配置。
  5. 更好的错误处理: fetch 只在网络错误时拒绝 Promise,HTTP 错误状态不会导致拒绝。

缺点

  1. 不支持旧版浏览器: fetch 是一个较新的 API,不被所有浏览器支持。
  2. HTTP 错误处理复杂: fetch 只在网络错误时拒绝 Promise,HTTP 错误状态需要手动处理。
  3. 不支持请求取消: fetch 本身不支持请求取消,需要使用 AbortController
  4. 不支持自动转换 JSON 数据: fetch 不会自动将 JSON 数据转换为 JavaScript 对象。
  5. 不支持拦截器: fetch 不支持请求和响应拦截器。

底层原理

基于 Promise

fetch 返回一个 Promise,这使得异步操作的处理更加简洁和现代化。当请求完成时,Promise 会根据请求的结果(成功或失败)被解析为 Response 对象或拒绝。

基于 XMLHttpRequest

fetch 是基于浏览器的原生 XMLHttpRequest 对象(XHR)实现的。XHR 是一种传统的数据请求方式,而 fetch API 则代表了现代 Web 开发的新兴标准。

实现细节

  • 创建请求: 调用 fetch() 函数时,浏览器会创建一个新的 XMLHttpRequest 对象来发起请求。
  • 发送请求: 通过 XMLHttpRequest 对象的 open()send() 方法发送请求。
  • 处理响应: 请求完成后,XMLHttpRequest 对象的 onreadystatechange 事件会被触发,fetch API 通过封装这个事件处理程序来处理响应。

流式处理的实现

流式处理是 fetch 的一个重要特性,它允许数据分块传输,适用于处理大文件或实时数据流。fetch 使用 ReadableStream API 来实现流式处理。

fetch("https://api.example.com/large-data").then((response) => {
  const reader = response.body.getReader();
  const decoder = new TextDecoder("utf-8");

  reader.read().then(function processText({ done, value }) {
    if (done) {
      console.log("Stream is complete");
      return;
    }

    const chunk = decoder.decode(value, { stream: true });
    console.log("Received chunk:", chunk);
    return reader.read().then(processText);
  });
});

在这个例子中,我们使用 fetch 发起一个 GET 请求,并处理返回的流式数据。

错误处理的实现

fetch 只在网络错误时拒绝 Promise,HTTP 错误状态(如 404)不会导致拒绝。开发者需要手动检查响应状态并进行相应处理。

fetch("https://api.example.com/data")
  .then((response) => {
    if (!response.ok) {
      throw new Error("Network response was not ok");
    }
    return response.json();
  })
  .then((data) => console.log(data))
  .catch((error) => console.error("Error:", error));

在这个例子中,我们使用 fetch 发起一个 GET 请求,并处理返回的 JSON 数据。

请求取消的实现

fetch 本身不支持请求取消,需要使用 AbortController 来实现。

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

fetch("https://api.example.com/data", { signal })
  .then((response) => response.json())
  .then((data) => console.log(data))
  .catch((error) => {
    if (error.name === "AbortError") {
      console.log("Fetch aborted");
    } else {
      console.error("Error:", error);
    }
  });

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

在这个例子中,我们使用 fetch 发起一个 GET 请求,并演示了如何取消请求。

结论

fetch API 提供了一种现代、简洁且强大的方式来处理网络请求。它的 Promise 基础和流式处理能力使其在许多场景下优于传统的 XHR 和第三方库 axios。然而,开发者在使用 fetch 时需要注意其兼容性、错误处理和请求取消等方面的限制。

通过深入了解 fetch 请求的概念、使用方法和优缺点,开发者可以更好地选择合适的网络请求方式,提升前端开发的效率和性能。

叁考资料

希望本文能帮助你更好地理解和应用 fetch 请求。如果有任何问题或建议,欢迎在评论区留言!