Skip to content
js并发请求示例demo

简单版本(不考虑请求失败后的重试)

html
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <script>
        /**
         * 并发请求函数
         * @param {Array} urls - 要请求的url数组
         * @returns {Number} maxNum - 最大并发数
         */
        const concurRequest = (urls, maxNum) => {
            if(urls.length === 0){
                return Promise.resolve([])
            }
            return new Promise((resolve) => {
                const results = [] // 存储所有请求的结果
                let nextIndex = 0 // 下一个要请求的url索引
                // 发送下一个请求,并将请求结果存储在result数组中
                const _request = async () => {
                    if(nextIndex >= urls.length) return;
                    const i = nextIndex;
                    const url = urls[nextIndex++]
                    const resp = await fetch(url);
                    results[i] = resp;
                    // 过滤无效数据,过滤null、undefined、空字符串、空数组、空对象
                    const cleanResults = arr.filter(item => {
                        if (!item) return false; // 过滤 null、undefined、空字符串
                        if (Array.isArray(item) && item.length === 0) return false; // 过滤空数组
                        if (typeof item === 'object') {
                            return Object.keys(item).length > 0; // 过滤空对象
                        }
                        return true; // 其他认为是有效数据
                    });
                    if(cleanResults.length === urls.length){
                        resolve(results);
                        return;
                    }
                    _request() // 递归调用_request函数,发送下一个请求
                }

                // 启动并发请求
                for(let i = 0; i < Math.min(maxNum, urls.length); i++){
                    _request();
                }
            })
        }

        // 测试
        const urls = [
            'https://v.api.aa1.cn/api/yiyan/index.php',
            'https://v.api.aa1.cn/api/chinaip/',
            'https://v.api.aa1.cn/api/api-wenan-qg/index.php?aa1=json',
            'https://v.api.aa1.cn/api/api-tx/index.php?wpon=json',
            'https://v.api.aa1.cn/api/tiangou/index.php',
            'https://v.api.aa1.cn/api/yiyan/index.php',
            'https://v.api.aa1.cn/api/chinaip/',
            'https://v.api.aa1.cn/api/api-wenan-qg/index.php?aa1=json',
            'https://v.api.aa1.cn/api/api-tx/index.php?wpon=json',
            'https://v.api.aa1.cn/api/tiangou/index.php',
            'https://v.api.aa1.cn/api/yiyan/index.php',
            'https://v.api.aa1.cn/api/chinaip/',
            'https://v.api.aa1.cn/api/api-wenan-qg/index.php?aa1=json',
            'https://v.api.aa1.cn/api/api-tx/index.php?wpon=json',
            'https://v.api.aa1.cn/api/tiangou/index.php',
            'https://v.api.aa1.cn/api/yiyan/index.php',
            'https://v.api.aa1.cn/api/chinaip/',
            'https://v.api.aa1.cn/api/api-wenan-qg/index.php?aa1=json',
            'https://v.api.aa1.cn/api/api-tx/index.php?wpon=json',
            'https://v.api.aa1.cn/api/tiangou/index.php',
            'https://v.api.aa1.cn/api/yiyan/index.php',
            'https://v.api.aa1.cn/api/chinaip/',
            'https://v.api.aa1.cn/api/api-wenan-qg/index.php?aa1=json',
            'https://v.api.aa1.cn/api/api-tx/index.php?wpon=json',
            'https://v.api.aa1.cn/api/tiangou/index.php',
            'https://v.api.aa1.cn/api/yiyan/index.php',
            'https://v.api.aa1.cn/api/chinaip/',
            'https://v.api.aa1.cn/api/api-wenan-qg/index.php?aa1=json',
            'https://v.api.aa1.cn/api/api-tx/index.php?wpon=json',
            'https://v.api.aa1.cn/api/tiangou/index.php',
            'https://v.api.aa1.cn/api/yiyan/index.php',
            'https://v.api.aa1.cn/api/chinaip/',
            'https://v.api.aa1.cn/api/api-wenan-qg/index.php?aa1=json',
            'https://v.api.aa1.cn/api/api-tx/index.php?wpon=json',
            'https://v.api.aa1.cn/api/tiangou/index.php',
            'https://v.api.aa1.cn/api/yiyan/index.php',
            'https://v.api.aa1.cn/api/chinaip/',
            'https://v.api.aa1.cn/api/api-wenan-qg/index.php?aa1=json',
            'https://v.api.aa1.cn/api/api-tx/index.php?wpon=json',
            'https://v.api.aa1.cn/api/tiangou/index.php',
            'https://v.api.aa1.cn/api/yiyan/index.php',
            'https://v.api.aa1.cn/api/chinaip/',
            'https://v.api.aa1.cn/api/api-wenan-qg/index.php?aa1=json',
            'https://v.api.aa1.cn/api/api-tx/index.php?wpon=json',
            'https://v.api.aa1.cn/api/tiangou/index.php',
            'https://v.api.aa1.cn/api/yiyan/index.php',
            'https://v.api.aa1.cn/api/chinaip/',
            'https://v.api.aa1.cn/api/api-wenan-qg/index.php?aa1=json',
            'https://v.api.aa1.cn/api/api-tx/index.php?wpon=json',
            'https://v.api.aa1.cn/api/tiangou/index.php',
            'https://v.api.aa1.cn/api/yiyan/index.php',
            'https://v.api.aa1.cn/api/chinaip/',
            'https://v.api.aa1.cn/api/api-wenan-qg/index.php?aa1=json',
            'https://v.api.aa1.cn/api/api-tx/index.php?wpon=json',
            'https://v.api.aa1.cn/api/tiangou/index.php',
            'https://v.api.aa1.cn/api/yiyan/index.php',
            'https://v.api.aa1.cn/api/chinaip/',
            'https://v.api.aa1.cn/api/api-wenan-qg/index.php?aa1=json',
            'https://v.api.aa1.cn/api/api-tx/index.php?wpon=json',
            'https://v.api.aa1.cn/api/tiangou/index.php',
            'https://v.api.aa1.cn/api/yiyan/index.php',
            'https://v.api.aa1.cn/api/chinaip/',
            'https://v.api.aa1.cn/api/api-wenan-qg/index.php?aa1=json',
            'https://v.api.aa1.cn/api/api-tx/index.php?wpon=json',
            'https://v.api.aa1.cn/api/tiangou/index.php'
        ]

        // 测试没问题,并发请求是5个
        concurRequest(urls, 5).then(res => {
            console.log(res)
        })
    </script>
</body>

</html>

考虑请求失败后的重试版本和重试间隔

html
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <script>
        /**
         * 并发请求函数(支持失败重试)
         * @param {Array} urls - 要请求的url数组
         * @param {Number} maxNum - 最大并发数
         * @param {Number} retryTimes - 最大重试次数(默认0)
         * @param {Number} retryDelay - 重试间隔(毫秒,默认1000)
         * @returns {Promise} - 返回所有请求的结果数组
         */
        const concurRequest = (urls, maxNum, retryTimes = 0, retryDelay = 1000) => {
            if(urls.length === 0){
                return Promise.resolve([])
            }
            return new Promise((resolve) => {
                const results = [] // 存储所有请求的结果
                let nextIndex = 0 // 下一个要请求的url索引
                let completedCount = 0 // 已完成的请求数量
                
                // 延迟函数
                const delay = (ms) => new Promise(resolve => setTimeout(resolve, ms));
                
                // 带重试机制的请求函数
                const fetchWithRetry = async (url, currentRetry = 0) => {
                    try {
                        const resp = await fetch(url);
                        // 检查响应是否成功(200-299状态码)
                        if (!resp.ok) {
                            throw new Error(`HTTP error! status: ${resp.status}`);
                        }
                        return resp;
                    } catch (error) {
                        // 如果还有重试次数,就延迟后重试
                        if (currentRetry < retryTimes) {
                            await delay(retryDelay);
                            return fetchWithRetry(url, currentRetry + 1);
                        }
                        // 重试次数用尽,返回错误
                        return { error: error.message, url };
                    }
                };
                
                // 发送下一个请求
                const _request = async () => {
                    if(nextIndex >= urls.length) return;
                    const i = nextIndex;
                    const url = urls[nextIndex++]
                    
                    try {
                        const resp = await fetchWithRetry(url);
                        results[i] = resp;
                    } catch (error) {
                        results[i] = { error: error.message, url };
                    } finally {
                        completedCount++;
                        if(completedCount === urls.length){
                            resolve(results);
                            return;
                        }
                        _request() // 递归调用,继续处理下一个请求
                    }
                }

                // 启动并发请求
                for(let i = 0; i < Math.min(maxNum, urls.length); i++){
                    _request();
                }
            })
        }


        // 测试
        const urls = [
            'https://v.api.aa1.cn/api/yiyan/index.php',
            'https://v.api.aa1.cn/api/chinaip/',
            'https://v.api.aa1.cn/api/api-wenan-qg/index.php?aa1=json',
            'https://v.api.aa1.cn/api/api-tx/index.php?wpon=json',
            'https://v.api.aa1.cn/api/tiangou/index.php',
            'https://v.api.aa1.cn/api/yiyan/index.php',
            'https://v.api.aa1.cn/api/chinaip/',
            'https://v.api.aa1.cn/api/api-wenan-qg/index.php?aa1=json',
            'https://v.api.aa1.cn/api/api-tx/index.php?wpon=json',
            'https://v.api.aa1.cn/api/tiangou/index.php',
            'https://v.api.aa1.cn/api/yiyan/index.php',
            'https://v.api.aa1.cn/api/chinaip/',
            'https://v.api.aa1.cn/api/api-wenan-qg/index.php?aa1=json',
            'https://v.api.aa1.cn/api/api-tx/index.php?wpon=json',
            'https://v.api.aa1.cn/api/tiangou/index.php',
            'https://v.api.aa1.cn/api/yiyan/index.php',
            'https://v.api.aa1.cn/api/chinaip/',
            'https://v.api.aa1.cn/api/api-wenan-qg/index.php?aa1=json',
            'https://v.api.aa1.cn/api/api-tx/index.php?wpon=json',
            'https://v.api.aa1.cn/api/tiangou/index.php',
            'https://v.api.aa1.cn/api/yiyan/index.php',
            'https://v.api.aa1.cn/api/chinaip/',
            'https://v.api.aa1.cn/api/api-wenan-qg/index.php?aa1=json',
            'https://v.api.aa1.cn/api/api-tx/index.php?wpon=json',
            'https://v.api.aa1.cn/api/tiangou/index.php',
            'https://v.api.aa1.cn/api/yiyan/index.php',
            'https://v.api.aa1.cn/api/chinaip/',
            'https://v.api.aa1.cn/api/api-wenan-qg/index.php?aa1=json',
            'https://v.api.aa1.cn/api/api-tx/index.php?wpon=json',
            'https://v.api.aa1.cn/api/tiangou/index.php',
            'https://v.api.aa1.cn/api/yiyan/index.php',
            'https://v.api.aa1.cn/api/chinaip/',
            'https://v.api.aa1.cn/api/api-wenan-qg/index.php?aa1=json',
            'https://v.api.aa1.cn/api/api-tx/index.php?wpon=json',
            'https://v.api.aa1.cn/api/tiangou/index.php',
            'https://v.api.aa1.cn/api/yiyan/index.php',
            'https://v.api.aa1.cn/api/chinaip/',
            'https://v.api.aa1.cn/api/api-wenan-qg/index.php?aa1=json',
            'https://v.api.aa1.cn/api/api-tx/index.php?wpon=json',
            'https://v.api.aa1.cn/api/tiangou/index.php',
            'https://v.api.aa1.cn/api/yiyan/index.php',
            'https://v.api.aa1.cn/api/chinaip/',
            'https://v.api.aa1.cn/api/api-wenan-qg/index.php?aa1=json',
            'https://v.api.aa1.cn/api/api-tx/index.php?wpon=json',
            'https://v.api.aa1.cn/api/tiangou/index.php',
            'https://v.api.aa1.cn/api/yiyan/index.php',
            'https://v.api.aa1.cn/api/chinaip/',
            'https://v.api.aa1.cn/api/api-wenan-qg/index.php?aa1=json',
            'https://v.api.aa1.cn/api/api-tx/index.php?wpon=json',
            'https://v.api.aa1.cn/api/tiangou/index.php',
            'https://v.api.aa1.cn/api/yiyan/index.php',
            'https://v.api.aa1.cn/api/chinaip/',
            'https://v.api.aa1.cn/api/api-wenan-qg/index.php?aa1=json',
            'https://v.api.aa1.cn/api/api-tx/index.php?wpon=json',
            'https://v.api.aa1.cn/api/tiangou/index.php',
            'https://v.api.aa1.cn/api/yiyan/index.php',
            'https://v.api.aa1.cn/api/chinaip/',
            'https://v.api.aa1.cn/api/api-wenan-qg/index.php?aa1=json',
            'https://v.api.aa1.cn/api/api-tx/index.php?wpon=json',
            'https://v.api.aa1.cn/api/tiangou/index.php',
            'https://v.api.aa1.cn/api/yiyan/index.php',
            'https://v.api.aa1.cn/api/chinaip/',
            'https://v.api.aa1.cn/api/api-wenan-qg/index.php?aa1=json',
            'https://v.api.aa1.cn/api/api-tx/index.php?wpon=json',
            'https://v.api.aa1.cn/api/tiangou/index.php'
        ]

        // 测试没问题,请求结果500的重试了两次,加上首次总共3次,并发请求是5个,并且重试的间隔是2秒
        concurRequest(urls, 5, 2, 2000).then(res => {
            console.log(res)
        })
    </script>
</body>

</html>