浏览器并发请求

并发请求

概念:在同一时间内发出多个请求

并发请求可以针对不同的资源或者服务,其目的是为了提高文件传输效率或者请求效率。并发是计算机领域中的一个重要概念,它指的是在同一时间内处理多个任务或者请求的能力。这种能力是现代计算机系统设计的核心,它影响着系统的性能、可伸缩性和稳定性。

控制并发请求上限

1. 请求计数器

ini 复制代码
/**
* requestList 任务队列
* limits 并发数量上限
* callback 回调函数
*/

function sendRequest(requestList, limits, callback) {
    // 当前执行队列
    const requestListWrapperedQueue = [];
    // 初次执行时的并发数
    const concurrentNum = Math.min(limits, requestList.length);
    // 完整的执行队列
    const returnPromises = [];
    // 当前任务并发数
    let concurrentCount = 0;
    // 初次启动任务
    const runTaskNeeded = () => {
        let i = 0;
        while (i < concurrentNum) {
            i++;
            runTask();
        }
    };
    // 取出单个任务并执行
    const runTask = async () => {
        const task = requestListWrapperedQueue.shift();
        if (task) {
            try {
                concurrentCount++;
                const res = await task();
            } catch (err) {
                return err;
            } finally {
                concurrentCount--;
                if (concurrentCount < limits &&requestListWrapperedQueue.length > 0) {
                    runTask();
                }
            }
        }
    };
    runTaskNeeded();
}

2. Promise方式

scss 复制代码
function sendRequest(requestList, limits, callback) {
    // 任务队列
    const promises = [];
    // 当前的并发池
    const pool = new Set([]);
    
    const runTask = async () => {
        // for await of 循环执行并发池
        for (let requestItem of requestList) {
            if (pool.size >= limits) {
                await Promise.race(pool).catch((err) => err);
            }
            const promise = requestItem();
            const cb = () => {
                pool.delete(promise);
            };
            promise.then(cb, cb);
            pool.add(promise);
            promises.push(promise);
        }
        // 通过allSettled获取异步任务的执行结果
        Promise.allSettled(promises).then(callback,callback);
    };
    runTask();
}
  • 减少变量记录当前并发执行的请求数量
  • 利用Set数据结构避免重复触发同一个请求
  • 通过Promise.race将请求池数量降到限制以下
  • 通过.then中的回调函数完成任务的清除
  • 通过Promise.allSettled获取所有的异步结果

控制单个请求重复次数

ini 复制代码
/**
* requestList 任务队列
* limits 并发数量上限
* callback 回调函数
* retryTimes 任务重试次数
*/

function sendRequest(requestList, limits, callback, retryTimes) {
    const requestListWrapperedQueue = [];
    const concurrentNum = Math.min(limits, requestList.length);
    const returnPromises = [];
    let concurrentCount = 0;
    // 1. 包裹一层promise,并且将相关信息重新包装放入请求队列
    const wrapePromise = (requestItem) => {
        return new Promise((resolve, reject) => {
            requestListWrapperedQueue.push({
                requestItem,
                resolve,
                reject,
                remainRetryTime: retryTimes,
            });
        });
    };
    const runTaskNeeded = () => {
        let i = 0;
        while (i < concurrentNum) {
            i++;
            runTask();
        }
    };
    const runTask = async () => {
        const task = requestListWrapperedQueue.shift();
        if (task) {
            const { requestItem, resolve, reject, remainRetryTime } = task;
            try {
                concurrentCount++;
                const res = await requestItem();
                resolve(res);
            } catch (err) {
                // 2. 判断是否可以再进行下次请求
                if (remainRetryTime > 0) {
                    requestListWrapperedQueue.push(requestItem);
                    // 3. 更新剩余次数
                    task.remainRetryTime--;
                } else {
                    reject(err);
                }
            } finally {
                concurrentCount--;
                if (concurrentCount < limits &&requestListWrapperedQueue.length > 0) {
                    runTask();
                }
            }
        }
    };
    // 4. 构建执行队列,将每个任务都用promise包装
    const init = () => {
        for (let requestItem of requestList) {
            const wrapperedPromise = wrapePromise(requestItem);
            returnPromises.push(wrapperedPromise);
        }
    };
    const start = () => {
        init();
        runTaskNeeded();
    };
    start();
    // 5. 通过allSettled获取异步任务的执行结果
    Promise.allSettled(returnPromises).then(callback, callback);
}

浏览器的并发请求

浏览器的并发请求是指浏览器能够同时发送的 HTTP 请求的数量。

为了避免对服务器造成过大的负载压力,浏览器对并发请求数量做了限制。

浏览器 HTTP1.0 HTTP1.1 |
IE6、7 2 4
IE8 6 6
火狐 6 6
Safari 4 4
谷歌 6 6
Opera 4 4

HTTP0.9、HTTP1.0每个请求都单独建立一个TCP连接,请求完成后连接断开; HTTP1.1可以持久连接,TCP建立连接后不会立即关闭,多个请求可以复用同一个TCP连接,而且多个请求可以并行发送。

浏览器对于并发请求的规则如下所示(同一域名下生效):

  • 相同的 GET 请求的并发数是1,上一个请求结束,才会执行下一个请求,否则置入队列等待发送
  • 不同的 GET/POST 请求的并发数量是6,当发送的请求数量达到6个,并且都没有得到响应时,后面的请求会置入队列等待发送

HTTP1.1持久链接和HTTP2.0多路复用的区别:

  • HTTP1.1持久链接:多个请求共用一个TCP链接,但是同时只能发送一个请求
  • HTTP2.0多路复用:在一个TCP链接中,可以同时发送多个请求

中断已发送的请求

1. 中断axios请求

javascript 复制代码
import axios from 'axios';
// 创建一个 CancelToken.source 实例
const { token, cancel } = axios.CancelToken.source();
// 发送请求时,将 cancelToken 传递给 Axios 的 request 方法
axios.request({
    url:'https://api.example.com/data',
    method: 'get',
    cancelToken: token
})
.then(response => {
... ...
})
.catch(error => {
    if(axios.isCancel(error)) {
        console.log("请求已取消",error.message);
    }else {
        console.error('请求出错',error);
    }
})
cancel('请求取消原因');

当调用 cancel 函数时,会导致与 token 相关联的请求被取消。在请求被取消时,Axios 会抛出一个 Cancel 错误,我们可以通过axios.isCancel(error) 方法来判断是否是取消请求导致的错误,并进行相应的处理。

2. 中断fetch请求

ini 复制代码
// 创建一个 AbortController 实例
const controller = new AbortController();
const signal = controller.signal;
// 发送请求时,将 signal 传递给 fetch 或 XMLHttpRequest
fetch('https://api.example.com/data', { signal })
.then(response => {})
.catch(error => {
    if (error.name === 'AbortError'){
        console.log("请求已取消");
    } else {
        console.error( '请求出错',error);
    }
})
// 取消请求
controller.abort();

当调用 controller.abort() 方法时,会导致与 signal 相关联的请求被取消。在请求被取消时,fetch 或 XMLHttpRequest会抛出一个 AbortError 错误,我们可以在 catch 中捕获这个错误并进行相应的处理。

通过使用 AbortController 和 Abortsiqnal,我们可以在浏览器中比较方便地取消已经发送的请求,避免不必要的网络流量和资源浪费。

相关推荐
热爱编程的小曾24 分钟前
sqli-labs靶场 less 8
前端·数据库·less
gongzemin36 分钟前
React 和 Vue3 在事件传递的区别
前端·vue.js·react.js
Apifox1 小时前
如何在 Apifox 中通过 Runner 运行包含云端数据库连接配置的测试场景
前端·后端·ci/cd
树上有只程序猿1 小时前
后端思维之高并发处理方案
前端
庸俗今天不摸鱼2 小时前
【万字总结】前端全方位性能优化指南(十)——自适应优化系统、遗传算法调参、Service Worker智能降级方案
前端·性能优化·webassembly
黄毛火烧雪下2 小时前
React Context API 用于在组件树中共享全局状态
前端·javascript·react.js
Apifox2 小时前
如何在 Apifox 中通过 CLI 运行包含云端数据库连接配置的测试场景
前端·后端·程序员
一张假钞2 小时前
Firefox默认在新标签页打开收藏栏链接
前端·firefox
高达可以过山车不行2 小时前
Firefox账号同步书签不一致(火狐浏览器书签同步不一致)
前端·firefox
m0_593758102 小时前
firefox 136.0.4版本离线安装MarkDown插件
前端·firefox