题目:
JS实现一个带并发限制的异步调度器Scheduler,保证同时运行的任务最多有两个。完善代码中Scheduler类,使得以下程序能正确输出
class Scheduler {
add(promiseCreator) { ... }
// ...
}
const timeoutFn = (time) => new Promise(resolve => {
setTimeout(resolve, time)
})
const scheduler = new Scheduler()
const addTaskFn = (time, order) => {
scheduler.add(() => timeoutFn(time))
.then(() => console.log(order))
}
addTaskFn(1000, '1')
addTaskFn(500, '2')
addTaskFn(300, '3')
addTaskFn(400, '4')
// output: 2 3 1 4
// 一开始,1、2两个任务进入队列
// 500ms时,2完成,输出2,任务3进队
// 800ms时,3完成,输出3,任务4进队
// 1000ms时,1完成,输出1
// 1200ms时,4完成,输出4
分析:
- 函数来回嵌套引用, 规定的的时间内题都读不明白, 很懵逼!~ 稳住No慌! (虽然我第1次也慌了)
- 在函数变量的后面加上一个
Fn,xxx与成xxxFn, 这样看着舒服点 - 因为
scheduler.add()可以.then返回的一定是一个 promise 实例 - 如果按照正常执行 4 个 promise 同时输出, 时间最短的先输出 -> 3 4 2 1, 所以肯定有一个栈在管理着每一个
() => timeoutFn(time)来控制scheduler.add().then(...)的执行时机! - (肌肉记忆)只有
new Promise里的 callback 函数执行了resolve()时, 实例的.then才会执行! 何时执行resolve说了算! - (肌肉记忆)让栈循环可以用
for也可以用递归! - 题中只能同时执行 2 个 promise, 时间刚刚好能对得上, 1、2执行(3、4等待) 500ms 2完毕3执行 800ms(500ms+300ms) 3完毕4执行, 1000ms时 1完毕(此时4已经执行了200ms), 再过200ms(也就是1200ms) 4完毕. 最终输出 -> 2 3 1 4 刚好满足题目要求
解:
class Scheduler {
constructor() {
this.tasks = []
this.num = 0
}
add(promiseCreaterFn) {
return new Promise((resolve, reject) => {
// 标记的思想非常重要! 让 resolve 在其它作用域执行
promiseCreaterFn.resolve = resolve
this.tasks.push(promiseCreaterFn)
this.run();
});
}
// 每执行一次 addTaskFn( ... ), 会产生多个 run() 的词法作用域
// 因为有 .then异步, 相当于 4 个 run() 在执行
// 只是第三第四个 run 被 if条件 阻止了
run() {
if (this.num < 2 && this.tasks.length) {
this.num++
const promiseCreaterFn = this.tasks.shift()
promiseCreaterFn()
// 执行到这里, 后面的第2、3、4个 run() 也已经开始执行了
.then(res => {
promiseCreaterFn.resolve()
})
.finally(() => {
this.num--
// 递归: 其中一个promise执行完了, 再进入计数this.num 执行下一个promise
// 执行 addTaskFn( ... ), 最终也会执行 this.run 方法
this.run();
})
}
};
}
const timeoutFn = (time) => new Promise(resolve => {
setTimeout(resolve, time)
})
const scheduler = new Scheduler()
const addTaskFn = (time, order) => {
scheduler.add(() => timeoutFn(time)) // promiseCreaterFn
.then(() => console.log(order))
}
addTaskFn(1000, '1') // 执行顺序三
addTaskFn(500, '2') // 执行顺序一
addTaskFn(300, '3') // 执行顺序二
addTaskFn(400, '4') // 执行顺序四
// output: 2 3 1 4
// 一开始,1、2两个任务进入队列
// 500ms时,2完成,输出2,任务3进队
// 800ms时,3完成,输出3,任务4进队
// 1000ms时,1完成,输出1
// 1200ms时,4完成,输出4