面试官:给我实现一个异步调度器,要支持任务暂停、取消和重试。我:😭

436 阅读5分钟

这个问题面试的时候很常见,10 次面试可能会遇到 8 次。

"写个异步调度器?" - 这题简单,我熟!

"那再加个暂停功能?" - 嗯...也行!

"取消功能呢?" - 还可以搞定...

"错误重试和事件通知呢?" - 😅 面试官,你是不是想招架构师啊...

虽然基础的异步调度器实现起来不难,但面试官往往会步步深入,考察你对异步任务控制的理解深度。

最近经常被面试官问到了这个问题,写这篇文章记录一下。毕竟现在的面试官都喜欢问这种"看似简单实则复杂"的问题 😭

🤖 需求分析

先别慌,我们一步步来,先看最基本的需求:

  1. 支持并发的异步调度器,最多允许2两任务进行处理
  2. 最后输出结果 2 3 1 4

看起来很简单对吧?就是控制一下并发数,然后按顺序执行任务...

等等!面试官的笑容逐渐诡异了起来...

const timeout = (time) =>
  new Promise((resolve) => {
    setTimeout(resolve, time)
  })

const scheduler = new Scheduler(2)
const addTask = (time, order) => {
  scheduler
    .add(() => timeout(time))
    .then(() => console.log(order))
    .catch((err) => console.log(`任务 ${order} 被取消:`, err))
}

addTask(1000, '1')
addTask(500, '2')
addTask(300, '3')
addTask(400, '4')

scheduler.start()

但在实际应用中,面试官往往会继续追问: "如果用户切换页面了呢?" "如果要取消任务呢?" "任务失败了怎么办?" "怎么知道所有任务都完成了?"

好吧...看来一个简单的调度器并不能让面试官满意 😅

scheduler.cancel() // 取消所有任务

scheduler.pause() // 暂停任务执行

scheduler.eventEmitter.on('allTasksFinished', () => {
  console.log('所有任务执行完成!')
})

🤖 具体实现

在这里,我们先理解核心原理,再一步步添加新功能。每个功能点都很简单,但组合起来就构成了一个强大的调度器。

面试官:我看你对这个挺熟的啊? 我:那当然...(心虚)

🚀 核心实现

异步调度器的核心就是任务队列和执行控制:

  1. add 函数:返回一个 Promise,这样可以让调用方感知任务执行结果
  2. add 函数:构建一个任务推到队列,而不是立即执行
  3. run 函数:每个任务执行前检查运行数是否小于并发数,控制并发量
  4. run 函数:一个任务执行完,自动从队列取出新任务执行,保持任务连续性
  5. start 函数:开始执行队列中的任务,这是整个调度的入口

面试官:嗯,基础的思路不错,那我们来点难的...

🚀 功能点一:怎么取消所有任务?

取消功能需要在几个关键点做处理:

  1. Scheduler 类:加一个标识 isCanceled 控制整体状态
  2. cancel 函数:将 isCanceled 置为 true,切断任务继续执行的可能
  3. run 函数:用 setTimeout 包裹执行逻辑,让取消有机会在任务执行前生效
  4. add 函数:检查 isCanceled,如果已取消就直接 reject

这样可以确保取消及时生效,不会有任务漏执行。

面试官:不错不错,那要是想暂停呢?

🚀 功能点二:怎么暂停和恢复执行任务?

暂停功能相对简单:

  1. Scheduler 类:添加 isPaused 标识
  2. pause 函数:将 isPaused 置为 true
  3. run 函数:增加暂停检查,暂停时直接返回不执行任务

这样任务队列会保持当前状态,直到恢复执行。

面试官:(露出了满意的笑容)那如果任务执行失败了呢?

🚀 功能点三:错误重试

在实际应用中,网络请求等异步操作经常会失败,所以需要支持错误重试:

retry 函数:包装原始任务,提供重试次数和延迟时间的控制,确保任务最大程度执行成功。

面试官:最后一个问题,怎么知道所有任务都完成了?

🚀 功能点四:任务完成事件

使用发布订阅模式,让调用方可以监听调度器的状态:

  1. 初始化时创建 eventEmitter 实例
  2. 在关键节点触发事件(如所有任务完成时)
  3. 调用方通过 on 方法注册回调

🤖 完整代码

下面是完整的实现,包含了所有功能点:

class Scheduler {
  constructor(limit) {
    this.limit = limit
    this.runningTasks = 0
    this.queue = []
    this.isCancelled = false
    this.isPaused = false
    this.eventEmitter = new EventEmitter()
  }

  add(promiseMaker, time = 0, delay = 0) {
    return new Promise((resolve, reject) => {
      const task = async () => {
        if (this.isCancelled) {
          reject('task has been cancelled')
          return
        }

        try {
          const res = await retry(promiseMaker, time, delay)
          resolve(res)
        } catch (e) {
          reject(e)
        }
      }

      this.queue.push(task)
    })
  }

  start() {
    this.isCancelled = false
    this.isPaused = false
    for (let i = 0; i < this.limit && this.runningTasks < this.queue.length; i++) {
      this.run()
    }
  }

  run() {
    setTimeout(async () => {
      if (this.isPaused) return
      if (this.queue.length && this.runningTasks < this.limit) {
        const task = this.queue.shift()
        this.runningTasks++
        await task()
        this.runningTasks--
        this.run()
      } else if (this.queue.length === 0 && this.runningTasks === 0) {
        this.eventEmitter.emit('allTasksFinished')
      }
    })
  }

  cancel() {
    this.isCancelled = true
  }

  pause() {
    this.isPaused = true
  }
}
function retry(fn, times, delay) {
  let _times = times
  return new Promise(async (resolve, reject) => {
    const run = async () => {
      try {
        const res = await fn()
        resolve(res)
      } catch (e) {
        if (_times === 0) {
          reject(e)
          return
        }
        _times--
        setTimeout(fn, delay)
      }
    }

    run()
  })
}

🤖 实际应用场景

面试官:说了这么多,给我讲讲实际项目中在哪用过? 我:这个...还真用过!(赶紧搬出例子)

  1. 文件上传 - 控制并发数,支持暂停/取消
  2. 数据批量处理 - 分批执行,避免卡顿
  3. 页面资源加载 - 控制加载优先级
  4. 后台任务处理 - 错误重试,状态监控

面试官:嗯...还不错,这轮就到这里吧。 我内心:总算过关了 😌

🤖 总结

看似简单的异步调度器,能够延伸出这么多知识点:

  • 异步任务控制
  • 并发控制
  • 错误处理
  • 事件通知

所以下次面试被问到类似问题,不要慌,记住:面试官想要的不是完美答案,而是你解决问题的思路

不过现在的面试都卷成这样了吗?一个调度器都要问这么多... 😭

🤖 相关

面试官:听说你简历里写的精通 React 源码,那你给我讲讲 React Scheduler 呗?我:😡 工资加 2K

面试官:你说你做过组件库,肯定了解过复杂组件状态管理的useSyncExternalStore吧?我:😭

面试官:你跟我说 setState 是同步的,它不是异步的吗?背错面试题了吧你!我:😭

面试官:你说你开发过组件库,那你怎么会不知道受控组件?面试就到这里吧。我:😭

版权归许泽川所有

如需转载,请提前询问本人的许可