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~~~

相关推荐
冬夜戏雪1 天前
实习面经记录(十)
java·前端·javascript
爱学习的程序媛1 天前
【Web前端】JavaScript设计模式全解析
前端·javascript·设计模式·web
心软小念1 天前
金三银四,全网最详细的软件测试面试题总结
软件测试·面试·职场和发展
小码哥_常1 天前
从SharedPreferences到DataStore:Android存储进化之路
前端
老黑1 天前
开源工具 AIDA:给 AI 辅助开发加一个数据采集层,让 AI 从错误中自动学习(Glama 3A 认证)
前端·react.js·ai·nodejs·cursor·vibe coding·claude code
jessecyj1 天前
Spring boot整合quartz方法
java·前端·spring boot
苦瓜小生1 天前
【前端】|【js手撕】经典高频面试题:手写实现function.call、apply、bind
java·前端·javascript
Wilber的技术分享1 天前
【LeetCode高频手撕题 2】面试中常见的手撕算法题(小红书)
笔记·算法·leetcode·面试
天若有情6731 天前
前端HTML精讲03:页面性能优化+懒加载,搞定首屏加速
前端·性能优化·html
踩着两条虫1 天前
AI驱动的Vue3应用开发平台深入探究(十):物料系统之内置组件库
android·前端·vue.js·人工智能·低代码·系统架构·rxjava