Fetch API 高阶用法:请求拦截、响应处理与超时控制

Fetch API 高阶玩法:拦截、变形与超时大作战

Fetch API,这玩意儿,前端工程师天天打交道,就像老朋友一样。你可能已经用它发送过无数个GET、POST请求,熟练得像呼吸一样自然。但老朋友也得常联系,不然时间长了,难免会有些生疏。今天咱们就来聊聊 Fetch API 的一些“高阶玩法”,让你对这位老朋友有更深的了解,关键时刻能派上大用场。

咱们今天的主题是:请求拦截、响应处理与超时控制。听起来有点学术,但其实一点都不难。想象一下,你是一个餐厅的服务员,Fetch API 就是你,餐厅厨房是后端服务器,顾客就是你的前端代码。

  • 请求拦截:就像你在顾客点完菜后,先检查一下厨房的食材够不够,或者顾客有没有特殊要求,然后再把菜单交给厨师。
  • 响应处理:厨师做完菜,你端上来之前,先看看菜品卖相如何,有没有少放盐,然后再呈现给顾客。
  • 超时控制:顾客等太久会不高兴,所以你要设置一个上菜时间,超过时间就给顾客打个折,或者推荐一道更快的手抓饼。

这样是不是一下子就明白了?好,接下来咱们就深入探讨一下这些“高阶玩法”。

拦截请求:当个称职的“拦截器”

在现实生活中,拦截器无处不在。比如高速公路上的收费站,机场的安检等等。在前端开发中,我们也可以使用拦截器来处理请求,比如:

  • 统一添加请求头:比如 Authorization (token),Content-Type等,省去每个请求都手动添加的麻烦。
  • 请求参数的修改:在请求发送之前,对参数进行统一处理,比如加密、格式化等等。
  • 请求的权限控制:根据用户的权限,决定是否允许发送请求。
  • 请求的日志记录:记录每个请求的信息,方便调试和分析。

那怎么实现呢? Fetch API 本身并没有提供原生的拦截器功能。但是,我们可以通过一些技巧来实现类似的效果,核心思想就是“包装 Fetch 函数”。

// 原始的 fetch 函数
const originalFetch = window.fetch;

// 包装后的 fetch 函数
window.fetch = async (...args) => {
  // 1. 请求拦截:在请求发送前进行处理
  const request = args[0]; // 请求的 URL 或 Request 对象
  const options = args[1] || {}; // 请求的配置选项

  // 统一添加 Authorization 请求头
  const token = localStorage.getItem('token'); // 假设 token 存储在 localStorage 中
  if (token) {
    options.headers = {
      ...options.headers,
      'Authorization': `Bearer ${token}`
    };
  }

  // 记录请求日志
  console.log('Request:', request, options);

  // 2. 发送请求
  try {
    const response = await originalFetch(...args);

    // 3. 响应处理:在响应返回后进行处理 (后面会详细讲)
    return response;
  } catch (error) {
    // 统一处理错误
    console.error('Request Error:', error);
    throw error; // 别忘了抛出错误,不然调用方就不知道出错了
  }
};

这段代码做了什么呢?

  1. 保存原始的 Fetch 函数: const originalFetch = window.fetch; 这句话把浏览器原生的fetch函数存了起来,防止被覆盖后找不着北。
  2. 重写 Fetch 函数: window.fetch = async (...args) => { ... }; 这行代码把浏览器的fetch函数重新定义了,以后你代码里用的fetch都是这个被我们“动过手脚”的函数了。
  3. 请求拦截: 在新的fetch函数里,我们拿到请求的参数(URL 和配置),然后就可以为所欲为了。比如,我们这里统一添加了 Authorization 请求头,从localStorage里取出token,加到请求头里。以后你再用fetch发请求,就不用每次都手动加token了,省事儿!
  4. 发送请求: const response = await originalFetch(...args); 这行代码才是真正发送请求的地方。我们调用之前保存的原始fetch函数,把参数传进去,然后等待服务器返回响应。
  5. 错误处理: try...catch 语句包裹了整个请求过程,如果请求过程中发生了错误,我们可以在catch里统一处理。比如,记录错误日志,或者显示一个友好的错误提示。

现在,你每次使用 fetch 发送请求,都会先经过这个“拦截器”,统一添加请求头,记录日志,处理错误。是不是感觉代码一下子变得高大上了?

举个栗子:

假设你有一个用户认证系统,用户登录后会得到一个 token,你需要把这个 token 放在每个请求的 Authorization 头里。有了这个拦截器,你只需要在用户登录成功后,把 token 存到 localStorage 里,然后就不用管了。拦截器会自动帮你把 token 加到每个请求头里,简直不要太方便!

// 用户登录成功后
localStorage.setItem('token', 'your_awesome_token');

// 发送请求,不需要手动添加 Authorization 头
fetch('/api/users')
  .then(response => response.json())
  .then(data => console.log(data));

响应处理:给返回值化个妆

后端返回的数据,有时候并不是我们想要的格式。比如,有的后端喜欢用 codemessage 来表示状态码和错误信息,有的后端喜欢直接返回数据。为了统一处理这些差异,我们可以在响应返回后,对数据进行一些处理。

// 还是上面的拦截器代码,我们只关注响应处理部分

window.fetch = async (...args) => {
  // ... (省略请求拦截部分)

  try {
    const response = await originalFetch(...args);

    // 2. 响应处理:在响应返回后进行处理
    if (!response.ok) {
      // 处理 HTTP 错误
      throw new Error(`HTTP error! status: ${response.status}`);
    }

    const contentType = response.headers.get('content-type');
    if (contentType && contentType.includes('application/json')) {
      // 如果是 JSON 数据,先解析成 JSON 对象
      const data = await response.json();

      // 统一处理状态码
      if (data.code !== 200) {
        // 假设 code 200 表示成功
        throw new Error(data.message || 'Request failed');
      }

      // 返回真正的数据
      return data.data; // 假设 data.data 才是真正的数据
    } else {
      // 如果不是 JSON 数据,直接返回 response 对象
      return response;
    }
  } catch (error) {
    // ... (省略错误处理部分)
  }
};

这段代码做了什么呢?

  1. 检查 HTTP 状态码: if (!response.ok) { ... } 这行代码检查服务器返回的 HTTP 状态码是否正常。如果状态码不是 200-299 之间,就表示请求出错了,我们直接抛出一个错误。
  2. 判断响应类型: const contentType = response.headers.get('content-type'); 这行代码获取响应的 Content-Type 头,判断响应的数据类型。
  3. 处理 JSON 数据: 如果响应是 JSON 数据,我们就先用 response.json() 方法把数据解析成 JSON 对象。然后,我们可以根据后端返回的数据格式,对数据进行统一处理。比如,如果后端用 codemessage 来表示状态码和错误信息,我们可以检查 code 是否为 200,如果不是,就抛出一个错误。最后,我们返回真正的数据(假设后端把数据放在 data.data 字段里)。
  4. 处理非 JSON 数据: 如果响应不是 JSON 数据,我们就直接返回 response 对象,让调用方自己处理。

有了这个响应处理,你就可以统一处理后端返回的数据格式,不用在每个请求里都写重复的代码。

举个栗子:

假设你的后端返回的数据格式如下:

{
  "code": 200,
  "message": "success",
  "data": {
    "name": "John Doe",
    "age": 30
  }
}

有了上面的响应处理,你只需要这样写代码:

fetch('/api/user')
  .then(data => {
    // data 就是 { name: "John Doe", age: 30 },不再需要手动解析 code 和 message
    console.log(data.name); // John Doe
  })
  .catch(error => {
    console.error(error); // 如果 code 不是 200,会抛出错误
  });

是不是感觉清爽了很多?

超时控制:不能让用户等的花儿都谢了

网络请求,最怕的就是“卡壳”。用户点了提交按钮,结果页面一直转圈圈,等了半天也没反应,体验简直差到极点。所以,我们需要对请求设置一个超时时间,如果超过时间还没返回,就自动取消请求,给用户一个友好的提示。

Fetch API 本身并没有提供原生的超时控制功能,但是我们可以使用 AbortController 来实现。

async function fetchWithTimeout(url, options = {}, timeout = 5000) {
  const controller = new AbortController();
  const signal = controller.signal;

  // 设置定时器,超时后取消请求
  const timeoutId = setTimeout(() => {
    controller.abort(); // 取消请求
  }, timeout);

  try {
    const response = await fetch(url, { ...options, signal });
    clearTimeout(timeoutId); // 清除定时器,防止请求已经成功返回,定时器还在执行
    return response;
  } catch (error) {
    clearTimeout(timeoutId); // 也要清除定时器,防止内存泄漏

    if (error.name === 'AbortError') {
      // 请求超时
      throw new Error('Request timed out');
    }

    throw error; // 其他错误
  }
}

这段代码做了什么呢?

  1. 创建 AbortController: const controller = new AbortController(); AbortController 是一个用于取消 Web 请求的 API。
  2. 获取 signal: const signal = controller.signal; signal 对象用于与 fetch 请求关联,当调用 controller.abort() 时,signal 会发出一个取消信号,fetch 请求就会被取消。
  3. 设置定时器: setTimeout(() => { ... }, timeout); 我们使用 setTimeout 函数设置一个定时器,当超过 timeout 时间后,就调用 controller.abort() 方法取消请求。
  4. 发送请求: const response = await fetch(url, { ...options, signal }); 我们把 signal 对象传递给 fetch 函数,这样 fetch 请求就和 AbortController 关联起来了。
  5. 处理错误: 如果请求被取消,fetch 函数会抛出一个 AbortError 错误。我们可以在 catch 语句中捕获这个错误,并进行相应的处理。
  6. 清除定时器: clearTimeout(timeoutId); 非常重要!当请求成功返回或者发生其他错误时,我们需要清除定时器,防止定时器还在执行,导致内存泄漏。

举个栗子:

fetchWithTimeout('/api/data', {}, 3000) // 设置超时时间为 3 秒
  .then(response => response.json())
  .then(data => console.log(data))
  .catch(error => {
    if (error.message === 'Request timed out') {
      // 处理超时错误
      console.error('请求超时,请稍后再试');
    } else {
      // 处理其他错误
      console.error(error);
    }
  });

这样,如果请求超过 3 秒还没有返回,就会自动取消请求,并显示“请求超时,请稍后再试”的提示。用户的体验是不是好多了?

总结

今天我们一起学习了 Fetch API 的三个高阶玩法:请求拦截、响应处理和超时控制

  • 请求拦截 就像一个门卫,可以统一处理请求头、参数等等。
  • 响应处理 就像一个化妆师,可以统一处理后端返回的数据格式。
  • 超时控制 就像一个闹钟,可以防止用户等太久。

掌握了这些技巧,你就可以更加灵活地使用 Fetch API,写出更加健壮、易维护的代码。当然,这些只是 Fetch API 的冰山一角,还有很多其他的技巧等待你去探索。希望这篇文章能给你带来一些启发,让你在前端开发的道路上越走越远!

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注