在前端开发的面试中,关于如何控制并发的问题经常被提及。无论是处理网络请求、文件上传还是其他异步操作,有效地管理并发都是确保应用性能和用户体验的关键。本文将通过一个简单的例子来探讨如何使用JavaScript实现并发控制,并解释其中涉及的核心概念。
一. 问题的提出
想象这样一个场景:你需要同时发起多个网络请求,但出于对服务器负载或客户端性能的考虑,你希望限制同时进行的最大请求数量。例如,如果一次性发送过多的请求,可能会导致服务器过载,或者客户端资源耗尽,影响用户体验。因此,我们需要一种机制来控制并发执行的任务数量。假设每次只能允许两个请求发送,这两个请求完成后其他请求才能发送,我们要怎么实现呢?
思考过程
- 为什么需要控制并发? 当我们有大量任务需要执行时(比如批量上传图片),如果不加以控制,所有任务会同时开始,这可能导致系统资源耗尽,响应变慢,甚至崩溃。
- 目标是什么? 我们的目标是设计一个可以动态调整并发数的机制,使得我们可以控制任意时刻最多有多少个任务正在执行。
二. 设计思路
要想保证每次只能两个任务进行,那我们首先能想到哪种数据结构呢?当然是数组了,先存入两个要执行的任务,当这个数组里有任务完成后,再添加进新的任务。这样每次都保证数组里的两个任务执行,从而实现并发控制。
核心思考点
- 任务队列:我们需要一个地方来存放所有的待执行任务。这里我们可以使用数组作为我们的任务队列。
- 状态管理:我们需要跟踪当前正在运行的任务数量。可以通过一个计数器变量来实现这一点。
- 调度逻辑:当某个任务完成时,我们应该从队列中取出下一个任务并开始执行它。为了实现这一逻辑,我们需要监听任务完成的事件,并根据情况调用调度函数。
三. 实现难点
1. 监听任务完成
由于每个任务都是异步的(比如网络请求),我们需要知道什么时候这些任务完成了。JavaScript中的Promise
对象提供了一种很好的方式来处理异步操作的结果。通过.then()
方法,我们可以在任务成功完成时得到通知;通过.catch()
方法,我们可以在任务失败时得到通知。
2. 动态调度任务
一旦我们知道了一个任务已经完成,我们就需要触发下一轮的任务调度。这意味着我们需要重新检查当前是否还有空闲的并发槽位,并从任务队列中取出新的任务来执行。
代码实现
js
class Limit {
constructor(paralleCount = 2) {
this.tasks = [];
this.runningCount = 0;
this.paralleCount = paralleCount;
}
add(task) {
return new Promise((resolve, reject) => {
this.tasks.push({task, resolve, reject});
this._run();
});
}
_run() {
while (this.runningCount < this.paralleCount && this.tasks.length) {
const {task, resolve, reject} = this.tasks.shift();
this.runningCount++;
task().then(() => { // 一个任务完毕了
resolve()
this.runningCount--
this._run()
}).catch((err) => {
reject(err)
this.runningCount--
this._run()
})
}
}
}
const limit = new Limit(2);
function addTask(time, name) {
limit.add(() => ajax(time))
.then(() => console.log(`任务${name}完成`))
.catch(() => console.log(`任务${name}出错`));
}
运行结果如下:
通过上述分析和实现,我们不仅解决了并发控制的问题,还深入理解了其背后的原理和设计思想。这种模式不仅可以应用于网络请求,还可以扩展到任何需要控制并发执行的任务场景中。