关于Scheduler 类,一个并发控制调度器

代码实现

js 复制代码
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()  // 执行下一个任务
        })
    }
}

执行流程可视化

js 复制代码
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. 构造函数

js 复制代码
constructor(limit) {
    this.limit = limit      // 并发限制数
    this.queue = []         // 任务队列(存储包装后的函数)
    this.running = 0        // 当前正在执行的任务数
}

2. add 方法

js 复制代码
add(task) {
    return new Promise(resolve => {
        // 关键:将任务包装成可执行函数并加入队列
        this.queue.push(() => task().then(resolve))
        this.run()
    })
}

关键点

  • 返回 Promise,外部可以等待任务完成
  • 任务被包装:() => task().then(resolve)
  • 包装函数执行时会调用原任务,并在完成后 resolve 外部 Promise

3. run 方法

js 复制代码
run() {
    if (this.running >= this.limit || !this.queue.length) return
    
    this.running++
    const task = this.queue.shift()
    task().finally(() => {
        this.running--
        this.run()  // 递归调用,执行下一个任务
    })
}

关键点

  • 检查是否达到并发上限
  • 从队列取出任务并执行
  • 任务完成后减少计数并继续执行

使用示例

示例1:限制请求并发

js 复制代码
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:文件上传控制

js 复制代码
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:爬虫并发控制

js 复制代码
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)

边界情况测试

js 复制代码
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:支持任务优先级

js 复制代码
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:支持任务超时

js 复制代码
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:支持进度回调

js 复制代码
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:支持暂停/恢复

js 复制代码
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()
        })
    }
}

与其他并发控制对比

js 复制代码
// 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()))

性能分析

js 复制代码
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()
相关推荐
好雨知时节t2 小时前
sleep 函数在React项目中的运用
javascript
xw-busy-code2 小时前
Prettier 学习笔记
javascript·笔记·学习·prettier
电商API&Tina2 小时前
电商数据采集API接口||合规优先、稳定高效、数据精准
java·javascript·数据库·python·json
酉鬼女又兒2 小时前
零基础快速入门前端DOM 操作核心知识与实战解析(完整汇总版)(可用于备赛蓝桥杯Web应用开发)
开发语言·前端·javascript·职场和发展·蓝桥杯·js
KongHen024 小时前
uniapp-x实现自定义tabbar
前端·javascript·uni-app·unix
数据潜水员4 小时前
三层统计最小力度的四种方法
javascript·vue.js
汪子熙4 小时前
TS2320 错误的本质、触发场景与在 Angular / RxJS 项目中的系统化应对
前端·javascript·angular.js
我命由我123454 小时前
React - BrowserRouter 与 HashRouter、push 模式与 replace 模式、编程式导航、withRouter
开发语言·前端·javascript·react.js·前端框架·html·ecmascript
Devin_chen4 小时前
ES6 Class 渐进式详解
前端·javascript