代码实现
class Scheduler {
constructor(limit) {
this.limit = limit // 最大并发数
this.queue = [] // 等待队列
this.running = 0 // 当前运行中的任务数
}
add(task) {
return new Promise(resolve => {
// 将任务包装后加入队列
this.queue.push(() => task().then(resolve))
this.run()
})
}
run() {
// 达到并发上限或无任务时返回
if (this.running >= this.limit || !this.queue.length) return
// 执行任务
this.running++
const task = this.queue.shift()
task().finally(() => {
this.running--
this.run() // 执行下一个任务
})
}
}
执行流程可视化
class Scheduler {
constructor(limit) {
this.limit = limit
this.queue = []
this.running = 0
}
add(task) {
return new Promise(resolve => {
console.log(`添加任务到队列,当前队列长度: ${this.queue.length + 1}`)
this.queue.push(() => task().then(resolve))
this.run()
})
}
run() {
if (this.running >= this.limit || !this.queue.length) {
console.log(`运行状态: running=${this.running}, 队列长度=${this.queue.length}`)
return
}
this.running++
const task = this.queue.shift()
console.log(`开始执行任务,当前并发: ${this.running}/${this.limit}`)
task().finally(() => {
console.log(`任务完成,当前并发: ${this.running-1}/${this.limit}`)
this.running--
this.run()
})
}
}
// 测试
const scheduler = new Scheduler(2)
const createTask = (name, delay) => () =>
new Promise(resolve => {
setTimeout(() => {
console.log(`${name} 完成`)
resolve(name)
}, delay)
})
scheduler.add(createTask('任务1', 1000))
scheduler.add(createTask('任务2', 500))
scheduler.add(createTask('任务3', 300))
scheduler.add(createTask('任务4', 400))
/* 输出示例:
添加任务到队列,当前队列长度: 1
开始执行任务,当前并发: 1/2
添加任务到队列,当前队列长度: 1
开始执行任务,当前并发: 2/2
添加任务到队列,当前队列长度: 1
运行状态: running=2, 队列长度=1
添加任务到队列,当前队列长度: 2
运行状态: running=2, 队列长度=2
任务2 完成
任务完成,当前并发: 1/2
开始执行任务,当前并发: 2/2
任务3 完成
任务完成,当前并发: 1/2
开始执行任务,当前并发: 2/2
任务4 完成
任务完成,当前并发: 1/2
任务1 完成
任务完成,当前并发: 0/2
*/
详细分析
1. 构造函数
constructor(limit) {
this.limit = limit // 并发限制数
this.queue = [] // 任务队列(存储包装后的函数)
this.running = 0 // 当前正在执行的任务数
}
2. add 方法
add(task) {
return new Promise(resolve => {
// 关键:将任务包装成可执行函数并加入队列
this.queue.push(() => task().then(resolve))
this.run()
})
}
关键点:
- 返回 Promise,外部可以等待任务完成
- 任务被包装:
() => task().then(resolve) - 包装函数执行时会调用原任务,并在完成后 resolve 外部 Promise
3. run 方法
run() {
if (this.running >= this.limit || !this.queue.length) return
this.running++
const task = this.queue.shift()
task().finally(() => {
this.running--
this.run() // 递归调用,执行下一个任务
})
}
关键点:
- 检查是否达到并发上限
- 从队列取出任务并执行
- 任务完成后减少计数并继续执行
使用示例
示例1:限制请求并发
class Scheduler {
constructor(limit) {
this.limit = limit
this.queue = []
this.running = 0
}
add(task) {
return new Promise(resolve => {
this.queue.push(() => task().then(resolve))
this.run()
})
}
run() {
if (this.running >= this.limit || !this.queue.length) return
this.running++
const task = this.queue.shift()
task().finally(() => {
this.running--
this.run()
})
}
}
// 模拟 API 请求
function fetchUser(id) {
return () => new Promise(resolve => {
console.log(`开始请求用户 ${id}`)
setTimeout(() => {
console.log(`用户 ${id} 数据返回`)
resolve({ id, name: `User${id}` })
}, Math.random() * 2000)
})
}
const scheduler = new Scheduler(3)
const userIds = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
// 添加所有请求
const promises = userIds.map(id =>
scheduler.add(fetchUser(id))
)
// 等待所有请求完成
Promise.all(promises).then(results => {
console.log('所有用户数据:', results)
})
示例2:文件上传控制
class Scheduler {
constructor(limit) {
this.limit = limit
this.queue = []
this.running = 0
}
add(task) {
return new Promise(resolve => {
this.queue.push(() => task().then(resolve))
this.run()
})
}
run() {
if (this.running >= this.limit || !this.queue.length) return
this.running++
const task = this.queue.shift()
task().finally(() => {
this.running--
this.run()
})
}
}
// 模拟文件上传
function uploadFile(fileName, size) {
return () => new Promise(resolve => {
const startTime = Date.now()
console.log(`开始上传: ${fileName} (${size}MB)`)
// 模拟上传耗时
setTimeout(() => {
const duration = Date.now() - startTime
console.log(`完成上传: ${fileName},耗时 ${duration}ms`)
resolve({ fileName, size, duration })
}, size * 500) // 每MB 500ms
})
}
const scheduler = new Scheduler(2)
const files = [
{ name: 'video.mp4', size: 10 },
{ name: 'image.jpg', size: 2 },
{ name: 'document.pdf', size: 1 },
{ name: 'music.mp3', size: 5 },
{ name: 'archive.zip', size: 8 }
]
files.forEach(file => {
scheduler.add(uploadFile(file.name, file.size))
.then(result => console.log(`${result.fileName} 上传成功`))
})
示例3:爬虫并发控制
class Scheduler {
constructor(limit) {
this.limit = limit
this.queue = []
this.running = 0
}
add(task) {
return new Promise(resolve => {
this.queue.push(() => task().then(resolve))
this.run()
})
}
run() {
if (this.running >= this.limit || !this.queue.length) return
this.running++
const task = this.queue.shift()
task().finally(() => {
this.running--
this.run()
})
}
}
// 模拟爬虫
function crawlUrl(url) {
return () => new Promise(resolve => {
console.log(`[${new Date().toLocaleTimeString()}] 爬取: ${url}`)
setTimeout(() => {
console.log(`[${new Date().toLocaleTimeString()}] 完成: ${url}`)
resolve({ url, data: `内容来自 ${url}` })
}, Math.random() * 2000)
})
}
const scheduler = new Scheduler(3)
const urls = [
'https://example.com/page1',
'https://example.com/page2',
'https://example.com/page3',
'https://example.com/page4',
'https://example.com/page5',
'https://example.com/page6',
'https://example.com/page7',
'https://example.com/page8'
]
const results = []
urls.forEach(url => {
scheduler.add(crawlUrl(url))
.then(result => results.push(result))
})
// 监听完成
setTimeout(() => {
console.log(`\n共爬取 ${results.length} 个页面`)
}, 10000)
边界情况测试
class Scheduler {
constructor(limit) {
this.limit = limit
this.queue = []
this.running = 0
}
add(task) {
return new Promise(resolve => {
this.queue.push(() => task().then(resolve))
this.run()
})
}
run() {
if (this.running >= this.limit || !this.queue.length) return
this.running++
const task = this.queue.shift()
task().finally(() => {
this.running--
this.run()
})
}
}
// 测试各种边界情况
console.log('=== 边界测试 ===\n')
// 1. limit = 0
const scheduler1 = new Scheduler(0)
scheduler1.add(() => Promise.resolve('test'))
.then(console.log)
console.log('limit=0: 任务永远不会执行')
// 2. limit = 1 (串行)
const scheduler2 = new Scheduler(1)
const startTime = Date.now()
scheduler2.add(() => new Promise(r => setTimeout(() => r('任务1'), 1000)))
scheduler2.add(() => new Promise(r => setTimeout(() => r('任务2'), 1000)))
scheduler2.add(() => new Promise(r => setTimeout(() => r('任务3'), 1000)))
.then(() => {
const duration = Date.now() - startTime
console.log(`串行执行总耗时: ${duration}ms (约3000ms)`)
})
// 3. 任务失败处理
const scheduler3 = new Scheduler(2)
scheduler3.add(() => Promise.reject('错误'))
.catch(e => console.log('捕获到错误:', e))
scheduler3.add(() => Promise.resolve('成功'))
.then(r => console.log('成功:', r))
// 4. 动态添加任务
const scheduler4 = new Scheduler(2)
setTimeout(() => {
console.log('动态添加任务')
scheduler4.add(() => Promise.resolve('动态任务'))
}, 1000)
改进版本
改进1:支持任务优先级
class PriorityScheduler extends Scheduler {
add(task, priority = 0) {
return new Promise(resolve => {
const wrappedTask = () => task().then(resolve)
// 按优先级插入队列
let index = this.queue.findIndex(item => item.priority < priority)
if (index === -1) index = this.queue.length
this.queue.splice(index, 0, { task: wrappedTask, priority })
this.run()
})
}
run() {
if (this.running >= this.limit || !this.queue.length) return
this.running++
const { task } = this.queue.shift()
task().finally(() => {
this.running--
this.run()
})
}
}
改进2:支持任务超时
class TimeoutScheduler extends Scheduler {
add(task, timeout = null) {
return new Promise((resolve, reject) => {
const wrappedTask = () => {
if (timeout) {
return Promise.race([
task(),
new Promise((_, reject) =>
setTimeout(() => reject(new Error('任务超时')), timeout)
)
]).then(resolve, reject)
}
return task().then(resolve, reject)
}
this.queue.push(wrappedTask)
this.run()
})
}
}
改进3:支持进度回调
class ProgressScheduler extends Scheduler {
constructor(limit) {
super(limit)
this.total = 0
this.completed = 0
}
add(task) {
this.total++
return new Promise(resolve => {
this.queue.push(() => task().then(result => {
this.completed++
this.onProgress?.(this.completed, this.total)
resolve(result)
}))
this.run()
})
}
onProgress(callback) {
this.onProgress = callback
return this
}
}
// 使用
const scheduler = new ProgressScheduler(3)
scheduler.onProgress((completed, total) => {
console.log(`进度: ${completed}/${total} (${Math.round(completed/total*100)}%)`)
})
改进4:支持暂停/恢复
class PausableScheduler extends Scheduler {
constructor(limit) {
super(limit)
this.paused = false
}
pause() {
this.paused = true
}
resume() {
this.paused = false
this.run()
}
run() {
if (this.paused) return
super.run()
}
add(task) {
return new Promise(resolve => {
this.queue.push(() => task().then(resolve))
this.run()
})
}
}
与其他并发控制对比
// 1. Promise.all - 无并发限制
const all = Promise.all(tasks.map(t => t()))
// 2. Promise.allSettled - 无并发限制
const settled = Promise.allSettled(tasks.map(t => t()))
// 3. 你的 Scheduler - 有并发限制
const scheduler = new Scheduler(3)
tasks.forEach(t => scheduler.add(t))
// 4. p-limit 库
const pLimit = require('p-limit')
const limit = pLimit(3)
const promises = tasks.map(t => limit(() => t()))
性能分析
class Scheduler {
constructor(limit) {
this.limit = limit
this.queue = []
this.running = 0
}
add(task) {
return new Promise(resolve => {
this.queue.push(() => task().then(resolve))
this.run()
})
}
run() {
if (this.running >= this.limit || !this.queue.length) return
this.running++
const task = this.queue.shift()
task().finally(() => {
this.running--
this.run()
})
}
}
// 性能测试
async function performanceTest() {
const tasks = Array(100).fill().map((_, i) =>
() => new Promise(r => setTimeout(() => r(i), Math.random() * 100))
)
// 测试不同并发限制
for (const limit of [1, 5, 10, 20]) {
const scheduler = new Scheduler(limit)
const start = Date.now()
await Promise.all(tasks.map(task => scheduler.add(task)))
const duration = Date.now() - start
console.log(`并发限制 ${limit}: ${duration}ms`)
}
}
performanceTest()