中止一个或多个 Web 请求

ajax 有主动结束请求的方法 Ajax.abort
Axios CancelToken
fetch ws 没有主动结束请求的方法

使用js的一个方法AbortController

AbortController 是一个 JavaScript API,用于中止一个或多个 Web 请求。
提供了一种标准化的方式来取消异步操作。

AbortController 由两部分组成:

AbortController:控制器对象,用于发送中止信号
AbortSignal:信号对象,用于监听中止事件

在fetch中的使用

// 1. 创建 AbortController 实例
const controller = new AbortController();
const signal = controller.signal;

// 2. 将 signal 传递给可中止的操作
fetch('/api/data', { signal })
  .then(response => response.json())
  .then(data => console.log(data))
  .catch(err => {
    if (err.name === 'AbortError') {
      console.log('请求被中止');
    } else {
      console.error('请求错误:', err);
    }
  });

// 3. 中止请求
controller.abort(); // 这会触发 AbortError

主要应用场景

取消 Fetch 请求
取消 Axios 请求
中止任何支持 signal 的异步操作
清理事件监听器
取消长时间运行的任务

与 Fetch API 结合使用

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

// 设置超时自动取消
const timeoutId = setTimeout(() => controller.abort(), 5000);

fetch('https://api.example.com/data', { signal })
  .then(response => {
    clearTimeout(timeoutId);
    return response.json();
  })
  .then(data => console.log(data))
  .catch(err => {
    if (err.name === 'AbortError') {
      console.log('请求超时被取消');
    } else {
      console.error('请求错误:', err);
    }
  });

与 Axios 结合使用

const controller = new AbortController();

axios.get('/api/data', {
  signal: controller.signal
})
.then(response => console.log(response.data))
.catch(err => {
  if (axios.isCancel(err)) {
    console.log('请求被取消:', err.message);
  } else {
    console.error('请求错误:', err);
  }
});

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

复用同一个信号

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

// 多个请求使用同一个信号
fetch('/api/data1', { signal });
fetch('/api/data2', { signal });
fetch('/api/data3', { signal });

// 一次性取消所有请求
controller.abort();

监听中止事件

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

signal.addEventListener('abort', () => {
  console.log('中止信号已触发');
  // 执行清理操作
});

controller.abort(); // 会触发上面的监听器

自定义可中止操作

function doTask(signal) {
  return new Promise((resolve, reject) => {
    const timeoutId = setTimeout(() => resolve('任务完成'), 5000);
    
    // 监听中止信号
    signal.addEventListener('abort', () => {
      clearTimeout(timeoutId);
      reject(new DOMException('任务被中止', 'AbortError'));
    });
    
    if (signal.aborted) {
      clearTimeout(timeoutId);
      reject(new DOMException('任务被中止', 'AbortError'));
    }
  });
}

const controller = new AbortController();
doTask(controller.signal)
  .then(console.log)
  .catch(err => {
    if (err.name === 'AbortError') {
      console.log('任务被手动中止');
    }
  });

// 2秒后中止任务
setTimeout(() => controller.abort(), 2000);

浏览器兼容性

现代浏览器(Chrome 66+, Firefox 57+, Safari 12.1+, Edge 16+)都支持
Node.js 从 v15.0.0 开始支持
对于旧环境可以使用 polyfill

在 AbortController 之前,常用的取消请求方式:

XMLHttpRequest 的 abort()
Axios 的 CancelToken

最佳实践

及时清理:不再需要的控制器应该被清理
错误处理:总是检查错误类型是否为 AbortError
避免内存泄漏:组件卸载时取消所有未完成的请求
合理使用:不要过度使用,只在必要时取消请求

http1 最多同时发送6个请求,也就是说 如果开了6ws, 那么页面上的任何接口的请求都发不出去

主动中断 XHR(fetch/axios)请求和SSE(Server-Sent Events)请求(笔记)

  1. fetch XHR 请求的主动中断
    使用 AbortController,fetch 原生支持 AbortController 来中断请求。
// 创建一个 AbortController 实例
const controller = new AbortController();
const signal = controller.signal;

// 发起 fetch 请求时传入 signal
fetch('https://jsonplaceholder.typicode.com/todos/1', { signal })
  .then(response => response.json())
  .then(data => console.log(data))
  .catch(err => {
    if (err.name === 'AbortError') {
      console.log('请求被中断');
    } else {
      console.error('请求出错', err);
    }
  });

// 需要中断请求时调用
controller.abort();
  1. axios XHR 请求的主动中断
    2.1 使用 CancelToken(axios v0.22及以下)
import axios from 'axios';

const CancelToken = axios.CancelToken;
let cancel;

// 发起请求
axios.get('https://jsonplaceholder.typicode.com/todos/1', {
  cancelToken: new CancelToken(function executor(c) {
    cancel = c;
  })
}).then(res => {
  console.log(res.data);
}).catch(thrown => {
  if (axios.isCancel(thrown)) {
    console.log('请求被取消');
  } else {
    console.error('请求出错', thrown);
  }
});

// 需要中断请求时调用
cancel('手动取消请求');

2.2 axios v1.0+ 推荐用 AbortController

const controller = new AbortController();

axios.get('https://jsonplaceholder.typicode.com/todos/1', {
  signal: controller.signal
}).then(res => {
  console.log(res.data);
}).catch(err => {
  if (err.code === 'ERR_CANCELED') {
    console.log('请求被取消');
  } else {
    console.error('请求出错', err);
  }
});

// 需要中断请求时调用
controller.abort();
  1. SSE(Server-Sent Events)请求的主动中断
    SSE 通常用 EventSource 或自定义 fetch+流的方式实现。
    3.1 原生 EventSource
const es = new EventSource('/sse-url');
es.onmessage = (event) => {
  console.log('收到消息:', event.data);
};
// 需要中断时
es.close();

3.2 fetch+流(如你的 MyStream 实现)
你需要保存 AbortController 实例,并在需要时调用 abort()。

class MyStream {
  constructor() {
    this.controller = null;
    this.reader = null;
  }

  async getStream(url, onData, onEnd) {
    this.controller = new AbortController();
    const response = await fetch(url, { signal: this.controller.signal });
    const reader = response.body.getReader();
    this.reader = reader;

    // 读取流数据
    while (true) {
      const { value, done } = await reader.read();
      if (done) break;
      onData(value);
    }
    onEnd();
  }

  abortStream() {
    if (this.reader) this.reader.cancel();
    if (this.controller) this.controller.abort();
  }
}

// 使用
const stream = new MyStream();
stream.getStream('/sse-url', data => console.log(data), () => console.log('end'));
// 需要中断时
stream.abortStream();
  1. 结合 Vue 组件的实际用法
data() {
  return {
    controller: null,
  }
},
methods: {
  fetchData() {
    this.controller = new AbortController();
    fetch('/api/data', { signal: this.controller.signal })
      .then(res => res.json())
      .then(data => { /* ... */ })
      .catch(err => {
        if (err.name === 'AbortError') {
          console.log('请求被中断');
        }
      });
  },
  abortFetch() {
    if (this.controller) this.controller.abort();
  }
}

总结

fetch/axios v1+ 推荐用 AbortController,调用 controller.abort() 主动中断。
axios v0.22- 用 CancelToken。
SSE 用 EventSource.close() 或自定义流时用 AbortController 和 reader.cancel()。
高频请求场景,一定要保存 controller/reader 实例,及时中断不再需要的请求,避免资源泄漏和页面卡死。

<think>我们正在解决前端多个XHR请求并发导致某个请求挂起的问题。根据提供的引用和一般知识,可能的原因和解决方案如下:可能的原因:1.浏览器对同一域名的并发请求数有限制(HTTP/1.1下一般为6个,HTTP/2可以多路复用,没有此限制)。如果超过限制,后续请求会被挂起,直到前面的请求完成。2.请求阻塞:例如,某个请求耗时过长,请求被阻塞(如等待跨域预检请求的响应)。3.资源竞争:多个请求同时竞争同一资源(如带宽、CPU)导致某些请求被延迟。4.代码问题:例如,请求没有正确发出回调处理不当。解决方案:1.升级到HTTP/2:使用HTTP/2协议,因为它支持多路复用,可以避免HTTP/1.1的并发限制。2.域名分片(domainsharding):将资源分布在多个子域名下,以突破浏览器的并发请求数限制(但HTTP/2下不推荐)。3.优化请求:减少不必要的请求,合并请求(如将多个请求合并为一个请求),使用数据压缩。4.优先级控制:对请求进行优先级排序,重要的请求先发出,者使用异步队列控制并发数量。5.使用WebWorkers:将一些计算密集型任务放到WebWorkers中执行,避免阻塞主线程,从而可能减少对请求发送的影响。6.超时和重试机制:为每个请求设置超时,并在超时后重试,避免单个请求长时间挂起影响其他请求。7.检查跨域问题:如果是跨域请求,确保服务器正确返回了CORS(跨域资源共享)响应头,避免预检请求阻塞。根据引用[3],我们可以使用Promise和setTimeout来设置请求超时,避免长时间挂起。例如:```javascriptfunctionfetchWithTimeout(url,options,timeout=5000){returnPromise.race([fetch(url,options),newPromise((_,reject)=>setTimeout(()=>reject(newError('timeout')),timeout))]);}```此外,引用[1]提到浏览器请求和渲染的机制,提醒我们注意页面资源请求的并发管理。如果问题仍然存在,可以使用浏览器开发者工具的网络面板(Network)分析请求的状态,找出被挂起的请求的原因。相关问题:1.如何检测前端请求被挂起的原因?2.浏览器对同一域名的并发请求数限制是多少?3.HTTP/2如何解决HTTP/1.1的并发限制问题?4.如何实现请求的优先级调度?5.跨域请求中预检请求(preflight)是如何工作的?</think>### 前端多个 XHR 请求并发导致请求挂起的解决方案 当多个 XHR 请求并发时,某个请求挂起通常由以下原因引起: #### 1. **浏览器并发连接限制** - **问题**:HTTP/1.1 协议下,浏览器对**同一域名**有并发请求限制(Chrome/Firefox 默认 6 个)[^1] - **解决方案**: ```javascript // 域名分片:将请求分散到不同子域名 const API_HOSTS = ["api1.example.com", "api2.example.com"]; const getHost = () => API_HOSTS[Math.floor(Math.random() * API_HOSTS.length)]; fetch(`https://${getHost()}/data`); ``` - 升级到 HTTP/2(支持多路复用,消除并发限制) - 合并请求:将多个请求聚合成一个 #### 2. **请求优先级管理** - **问题**:关键请求被低优先级请求阻塞 - **解决方案**: ```javascript // 使用 AbortController 中止非关键请求 const lowPriorityController = new AbortController(); // 高优先级请求 fetch('/critical-data', { priority: 'high' }); // 低优先级请求(可中止) fetch('/non-critical', { signal: lowPriorityController.signal }); // 需要时中止 lowPriorityController.abort(); ``` #### 3. **请求队列控制** - **问题**:无限制并发导致资源竞争 - **解决方案**: ```javascript class RequestQueue { constructor(maxConcurrent = 4) { this.queue = []; this.activeCount = 0; this.maxConcurrent = maxConcurrent; } add(requestFn) { return new Promise((resolve, reject) => { this.queue.push({ requestFn, resolve, reject }); this.#next(); }); } #next() { if (this.activeCount >= this.maxConcurrent || !this.queue.length) return; this.activeCount++; const { requestFn, resolve, reject } = this.queue.shift(); requestFn().then(resolve).catch(reject).finally(() => { this.activeCount--; this.#next(); }); } } // 使用示例 const queue = new RequestQueue(3); // 最大并发3 queue.add(() => fetch('/api1')); queue.add(() => fetch('/api2')); ``` #### 4. **超时与重试机制** - **问题**:单个请求阻塞导致连锁失败 - **解决方案**: ```javascript const fetchWithTimeout = (url, timeout = 8000) => { const controller = new AbortController(); const timeoutId = setTimeout(() => controller.abort(), timeout); return fetch(url, { signal: controller.signal }) .finally(() => clearTimeout(timeoutId)) .catch(err => { if (err.name === 'AbortError') { console.warn(`请求超时: ${url}`); } throw err; }); }; ``` #### 5. **资源类型优化** - **问题**:大文件请求阻塞 API 请求 - **解决方案**: - 静态资源使用 CDN 不同域名 - 图片/视频采用懒加载 - 使用 `rel="preconnect"` 预连接: ```html <link rel="preconnect" href="https://api.example.com"> ``` #### 6. **监控与诊断** ```javascript // 记录请求耗时 const start = performance.now(); fetch('/data') .then(() => { const duration = performance.now() - start; if (duration > 2000) console.warn('慢请求警告'); }); ``` - 使用 Chrome DevTools 的 **Network** 面板分析请求瀑布流 - 检查服务器响应头的 `Keep-Alive` 和 `Connection` 设置 ### 相关问题 1. HTTP/2 的多路复用如何解决并发限制问题? 2. 如何在前端实现请求的自动重试机制? 3. Service Worker 能否用于优化并发请求管理? 4. 浏览器开发者工具中如何识别被挂起的请求? 5. WebSocket 是否受浏览器并发请求限制的影响? > 通过结合域名分片、请求队列、优先级控制和超时机制,可显著降低请求挂起概率。实际应用中需根据具体场景选择组合策略[^1][^3]。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值