Promise 题

123 阅读2分钟
题目:

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 刚好满足题目要求

image.png

解:
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