前端开发中取消请求的正确姿势与实际踩坑经验分享

淑丽 Dev 优化 阅读 2,950
赞 11 收藏
二维码
手机扫码查看
反馈

直接上代码,取消请求其实没那么难

先说重点,前端取消请求最常见的场景就是用户切换页面或者重复操作时,避免不必要的请求浪费资源。下面是我亲测有效的几种方法,建议直接用。

前端开发中取消请求的正确姿势与实际踩坑经验分享

// Axios CancelToken 的用法
const CancelToken = axios.CancelToken;
let cancel;

axios.get('https://jztheme.com/api/data', {
  cancelToken: new CancelToken(function executor(c) {
    cancel = c;
  })
});

// 在需要取消的地方调用
cancel('手动取消请求');

上面这段代码看着简单,但实际项目里用起来还是有不少坑的。比如 cancel 函数的调用时机,还有多个请求如何管理的问题。

这个场景最好用:React组件卸载时自动取消

在 React 项目中,我经常遇到一个场景:组件还没加载完就切换了页面,这时候接口返回的数据会报错 “Can’t perform a React state update on an unmounted component”。解决方案如下:

import { useEffect, useState } from 'react';
import axios from 'axios';

function MyComponent() {
  const [data, setData] = useState(null);
  const source = axios.CancelToken.source();

  useEffect(() => {
    axios.get('https://jztheme.com/api/data', {
      cancelToken: source.token
    }).then(res => {
      // 防止组件已卸载时更新状态
      if (!source.isCancelled()) {
        setData(res.data);
      }
    }).catch(err => {
      if (!axios.isCancel(err)) {
        console.error(err);
      }
    });

    return () => {
      source.cancel('组件卸载,取消请求');
    };
  }, []);

  return <div>{data ? JSON.stringify(data) : '加载中...'}</div>;
}

这里有个小细节要注意:isCancelled 方法其实是我自己扩展的一个判断函数,因为 axios 本身没有提供这样的方法。实现很简单:

axios.CancelToken.source().isCancelled = function() {
  return this.reason !== undefined;
};

踩坑提醒:这三点一定注意

1. 别忘了处理并发请求的情况。如果你有多个请求,建议用 Map 来管理 CancelToken:

const requestMap = new Map();

function addRequest(key, cancelFunc) {
  requestMap.set(key, cancelFunc);
}

function cancelRequest(key) {
  if (requestMap.has(key)) {
    requestMap.get(key)('取消特定请求');
    requestMap.delete(key);
  }
}

2. 不要滥用取消功能。有些同学喜欢给每个请求都加取消,其实没必要。像登录、支付这种关键请求就别取消了。

3. 谨慎处理取消后的状态。特别是当你的 UI 状态依赖于请求结果时,取消后要有相应的 fallback 处理。

高级技巧:封装通用的请求管理器

在大型项目里,我更倾向于封装一个统一的请求管理器,这样可以更好地控制所有请求:

class RequestManager {
  constructor() {
    this.queue = new Map();
  }

  add(config) {
    const CancelToken = axios.CancelToken;
    config.cancelToken = new CancelToken(c => {
      this.queue.set(config.url, c);
    });
    return config;
  }

  remove(url) {
    this.queue.delete(url);
  }

  cancelAll() {
    for (const [url, cancel] of this.queue.entries()) {
      cancel(取消请求: ${url});
    }
    this.queue.clear();
  }

  cancel(url) {
    if (this.queue.has(url)) {
      const cancel = this.queue.get(url);
      cancel(取消请求: ${url});
      this.queue.delete(url);
    }
  }
}

// 使用示例
const manager = new RequestManager();

axios.interceptors.request.use(config => manager.add(config));

// 在路由切换时
manager.cancelAll();

这个方案虽然看起来复杂点,但在实际项目中特别实用。尤其是单页应用,页面切换时可以一键取消所有进行中的请求。

补充:AbortController 是个好东西

其实现代浏览器已经提供了原生的 AbortController API,比 axios 自带的 CancelToken 更优雅:

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

fetch('https://jztheme.com/api/data', { signal })
  .then(res => res.json())
  .then(data => console.log(data))
  .catch(err => {
    if (err.name === 'AbortError') {
      console.log('请求已中止');
    }
  });

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

这个方式兼容性也不错,主流浏览器基本都支持。而且语法更简洁,推荐新项目使用这种方式。

结尾唠叨几句

以上就是我在实际项目中总结的一些取消请求的经验。从最初的到处写 try-catch,到现在封装统一的请求管理器,踩过的坑真不少。记得有一次忘记处理取消后的 loading 状态,导致整个页面都卡在 loading 状态,被产品经理追着骂。

这个技术的拓展用法还有很多,比如结合 Redux 管理请求状态,或者在 GraphQL 中实现更细粒度的请求控制。这些内容我后续会继续分享。

以上是我个人对取消请求的完整讲解,有更优的实现方式欢迎评论区交流。

本文章不代表JZTHEME立场,仅为作者个人观点 / 研究心得 / 经验分享,旨在交流探讨,供读者参考。
发表评论

暂无评论