js异步请求池,控制并发请求数量

Q:并发请求数量过大,会在短时间内发送大量的网络请求,并且占用大量的系统资源,可能会造成接口阻塞,浏览器卡死现象,怎么才能控制并发请求数量防止频繁渲染呢?

可以考虑使用异步请求池,来控制同一时间并发请求的数量,我们可以通过维护一个请求队列来实现:

1.创建类并维护变量

定义一个PromisePool的类,接收conncurrency参数来控制并发请求的数量;

同时我们需要存储正在执行的请求数running,请求队列queue,所有的请求结果result。

js 复制代码
class PromisePool {
    constructor(concurrency) {
        this.concurrency = concurrency;
        this.running = 0;
        this.queue = [];
        this.results = [];
    }
}

2.定义类方法

  • 首先我们需要将请求传入实例对象中,则需要定义add方法,返回一个promise对象,通过.then()方法接收接口返回值;
  • 其次,将请求传入后需要控制请求的执行,则需要定义run方法,也返回一个promise对象,核心逻辑是根据running和concurrensy判断当前请求需要执行或进入queue队列;
  • 如果当前running < concurrency,则需要从队列中取请求执行,这里需要定义next()方法;
js 复制代码
class PromisePool {
    constructor(concurrency) {
        this.concurrency = concurrency;
        this.running = 0;
        this.queue = [];
        this.results = [];
        this.taskCallbacks = [];
    }

    add(...tasks) {
        const taskPromises = tasks.map(task => this.run(task))
        return Promise.all(taskPromises);
    }

    run(task) {
        return new Promise((resolve, reject) => {
            const warppedTask = async () => {
                try {
                    const result = await task();
                    this.results.push(result);
                    resolve(result);
                } catch (error) {
                    reject(error);
                } finally {
                    this.running--;
                    this.next();
                }
            }
            if (this.running < this.concurrency) {
                this.running++;
                warppedTask();
            } else {
                this.queue.push(warppedTask);
            }
        })
    }

    next() {
        if (this.queue.length > 0 && this.running < this.concurrency) {
            const task = this.queue.shift();
            this.running++;
            task();
        }
    }
}

3.使用

js 复制代码
const task = [
    () => new Promise(resolve => {
        setTimeout(() => {
            resolve('task1');
        }, 1000)
    }),
    () => new Promise(resolve => {
        setTimeout(() => {
            resolve('task2');
        }, 2000)
    }),
    () => new Promise(resolve => {
        setTimeout(() => {
            resolve('task3');
        }, 3000)
    }),
    () => new Promise(resolve => {
        setTimeout(() => {
            resolve('task4');
        }, 4000)
    }),
    () => new Promise(resolve => {
        setTimeout(() => {
            resolve('task5');
        }, 1000)
    }),
    () => new Promise(resolve => {
        setTimeout(() => {
            resolve('task6');
        }, 4000)

    }),
    () => new Promise(resolve => {
        setTimeout(() => {
            resolve('task7');
        }, 1000)
    }),
]
pool.add(task[0]).then(result => {
    console.log('任务完成:', result);
});
pool.add(task[1]).then(result => {
    console.log('任务完成:', result);
});
pool.add(task[2]).then(result => {
    console.log('任务完成:', result);
});
pool.add(task[3]).then(result => {
    console.log('任务完成:', result);
});
pool.add(task[4]).then(result => {
    console.log('任务完成:', result);
});
pool.add(task[5]).then(result => {
    console.log('任务完成:', result);
});

pool.add(() => new Promise(resolve => {
    setTimeout(() => {
        resolve('单独任务');
    }, 1000)
})).then(result => {
    console.log('任务4完成:', result);
});

输出:

4.传入数组,监听返回

这里通过注册任务完成的回调函数实现,在每个接口resolve时调用函数

js 复制代码
class PromisePool {
    constructor(concurrency) {
        this.concurrency = concurrency;
        this.running = 0;
        this.queue = [];
        this.results = [];
        this.taskCallbacks = [];
    }

    onTaskDone(callback) {
        this.taskCallbacks.push(callback);
        return this;
    }

    add(...tasks) {
        const taskPromises = tasks.map(task => this.run(task))
        return Promise.all(taskPromises);
    }

    run(task) {
        return new Promise((resolve, reject) => {
            const warppedTask = async () => {
                try {
                    const result = await task();
                    this.results.push(result);
                    this.taskCallbacks.forEach(cb => cb(result));
                    resolve(result);
                } catch (error) {
                    reject(error);
                } finally {
                    this.running--;
                    this.next();
                }
            }
            if (this.running < this.concurrency) {
                this.running++;
                warppedTask();
            } else {
                this.queue.push(warppedTask);
            }
        })
    }

    next() {
        if (this.queue.length > 0 && this.running < this.concurrency) {
            const task = this.queue.shift();
            this.running++;
            task();
        }
    }
}

let pool = new PromisePool(3);

const task = [
    () => new Promise(resolve => {
        setTimeout(() => {
            resolve('task1');
        }, 1000)
    }),
    () => new Promise(resolve => {
        setTimeout(() => {
            resolve('task2');
        }, 2000)
    }),
    () => new Promise(resolve => {
        setTimeout(() => {
            resolve('task3');
        }, 3000)
    }),
    () => new Promise(resolve => {
        setTimeout(() => {
            resolve('task4');
        }, 4000)
    }),
    () => new Promise(resolve => {
        setTimeout(() => {
            resolve('task5');
        }, 1000)
    }),
    () => new Promise(resolve => {
        setTimeout(() => {
            resolve('task6');
        }, 4000)

    }),
    () => new Promise(resolve => {
        setTimeout(() => {
            resolve('task7');
        }, 1000)
    }),
]

调用函数

js 复制代码
// 注册任务完成回调(实时获取结果)
pool.onTaskDone(result => {
    console.log('实时结果:', result);
});

pool.add(...task).then(result => {
    console.log('任务完成:', result);
});

结果

end~~~

相关推荐
cindershade2 分钟前
前端国际化工程实践:语言包拆分、动态加载与日期数字格式统一
前端
kisbad10 分钟前
Day 036|OpenAI Agents SDK 快速开始:今天跑通第一个 Agent
java·前端·javascript
qq_2676128914 分钟前
Gitee CodePecker行业场景化落地指南:金融、车联网与IoT双引擎选型与工程化路径
前端·gitee·自动化
初晨未凉22 分钟前
elementui自定义内容图片预览
前端
Dr.kangder30 分钟前
嵌入式面试总结(十六)——AMBA总线
面试·职场和发展·架构·嵌入式·虚拟化·总线
Dr.kangder1 小时前
嵌入式面试总结(十九)——内存泄露
单片机·算法·面试·职场和发展·架构·硬件架构
Dr.kangder1 小时前
嵌入式面试总结(十七)——DMA总线
面试·职场和发展·架构·嵌入式·总线
无糖可可果1 小时前
从零读懂一个 Next.js 全栈笔记应用
前端
Maxkim1 小时前
DeepSeek Harness 源码深度分析:像 VS Code 一样插件化的 Agent 框架
前端·架构
喜欢睡觉2 小时前
从零看懂一个 Next.js 笔记应用
前端