前言
并发就是很多请求同时发送,当发送的请求过多时,就会造成服务器过载,网络拥堵等问题,大量请求同时通过网络传输,可能导致网络带宽饱和,从而影响所有请求的响应时间。
控制并发就是控制同时发送请求的数量,比如有一个页面里面有100个请求同时发送,如何控制其每次只能发送着5个请求。如何实现呢?
控制并发的代码实现
设计思路:假设有十个并发任务,控制并发数为2,维护一个任务队列,将这十个任务添加进这个给队列里面,然后取两个任务出队列并执行,当其中一个执行完后再取一个任务出队列接上去执行。
使用 ajax 函数模拟发送请求任务,然后再用 addTask 函数接受任务执行时间和任务名称,将任务(通过 ajax 函数模拟的异步操作)添加到 Limit 类的队列中,在任务完成后,执行回调函数打印任务完成的消息。
function ajax(time) {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve()
}, time)
})
}
// ajax(1000)
// ajax(2000)
// ...
class Limit {
constructor() {
}
add() {
}
}
const limit = new Limit()
function addTask(time, name) {
limit
.add(() => ajax(time)) // 放入队列但不执行
.then(() => {
console.log(`任务${name}完成`); // 为了更好地看出来执行效果
})
}
addTask(10000, 1)
addTask(2000, 2)
addTask(5000, 3)
addTask(1000, 4)
addTask(7000, 5)
这样,就可以通过调用 addTask 函数来添加多个任务,然后在 Limit 类中实现的并发控制逻辑来执行。
然后在 Limit 类中维护一个任务队列,并接受并发数量。add 函数将该任务添加到任务队列,然后执行函数 _run,函数 _run 里面先判断当前正在执行的任务数量是否小于最大并发数,如果小于,也就是还没有达到并发数量,就可以取出队列里面的第一个任务来执行了,当其中一个执行完后就接着去任务执行,再调用 _run 函数。代码如下:
class Limit {
constructor(paralleCount = 2) {
this.runningCount = 0 // 正在执行的任务数量
this.tasks = [] // 任务队列
this.paralleCount = paralleCount // 存储传入的最大并发数
}
add(task) {
this.tasks.push(task)
this._run()
}
_run() {
// 是否达到并发数量
while (this.runningCount < this.paralleCount && this.tasks.length) {
const task = this.tasks.shift() // 将任务队列里面的第一个任务取出来
this.runningCount++
task().then(() => { // 执行任务
this.runningCount-- // 执行完后正在执行的任务数量 -1
this._run() // 递归执行
})
}
}
}
const limit = new Limit(2)
接下来就是考虑 Promise 里面的 resolve 和 reject 函数的调用了,我们可以在任务添加到任务队列的时候将 resolve 和 reject 与task任务一起添加进任务队列里面。这样当执行完一个任务时,就可以返回resolve或reject了。如下:
class Limit {
constructor(paralleCount = 2) {
this.runningCount = 0 // 正在执行的任务数量
this.tasks = [] // 任务队列
this.paralleCount = paralleCount // 存储传入的最大并发数
}
add(task) {
return new Promise((resolve, reject) => {
// 将resolve, reject跟着task任务一起添加进队列中
this.tasks.push({
task,
resolve,
reject
})
this._run()
})
}
_run() {
while (this.runningCount < this.paralleCount && this.tasks.length) {
// 将任务队列里面的第一个任务取出来,并将 resolve, reject解构出来
const { task, resolve, reject } = this.tasks.shift()
this.runningCount++
task()
.then(() => { // 执行
resolve()
this.runningCount-- // 执行完后正在执行的任务数量 -1
this._run() // 递归执行
})
.catch(() => {
reject()
this.runningCount--
this._run()
})
}
}
}
const limit = new Limit(2)
其中需要注意的就是:
- 保证所有的任务都受控制(给任务包裹一层函数)
- 创建添加任务的 add 函数,并将 add 的 resolve,reject 同任务一起存放
- 创建执行任务的 run 函数,在每个任务执行完毕后执行 add 函数的 reslove,reject,最后递归执行 run 函数
完整代码:
function ajax(time) {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve()
}, time)
})
}
// ajax(1000)
// ajax(2000)
// ...
class Limit {
constructor(paralleCount = 2) {
this.runningCount = 0 // 正在执行的任务数量
this.tasks = [] // 任务队列
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-- // 执行完后正在执行的任务数量 -1
this._run() // 递归执行
})
.catch(() => {
reject()
this.runningCount--
this._run()
})
}
}
}
const limit = new Limit(2)
function addTask(time, name) {
limit
.add(() => ajax(time))
.then(() => {
console.log(`任务${name}完成`);
})
}
addTask(10000, 1)
addTask(2000, 2)
addTask(5000, 3)
addTask(1000, 4)
addTask(7000, 5)