标准答案

  1. 每个任务需要 id、callback、priority、status 和 cancel 方法或取消标记。
  2. 入队时按优先级排序,优先级高的任务先执行。
  3. 执行前检查任务是否被取消,被取消就跳过。
  4. 如果任务是异步的,要处理并发上限、失败状态和 finally 后继续调度。
  5. 复杂场景还要支持时间片、超时、暂停恢复、重试和任务依赖。

题目解析

任务调度器的核心不是简单 setTimeout,而是“哪些任务先跑、哪些任务可以不跑、跑到一半如何继续推进”。优先级和取消是两个基本控制点。

取消通常不是强行杀掉已经执行的同步函数,而是在任务执行前或异步任务的检查点跳过后续逻辑。对于 fetch 这类 API,可以配合 AbortController 做真正取消。

如果要贴近前端框架,还可以说明时间片和让出主线程:长任务不能一直占用主线程,否则会影响输入响应和渲染。

代码示例

简化版调度器可以按优先级取任务,并在执行前检查取消状态。

JavaScript
function createScheduler() {
  const queue = []

  function add(task, priority = 0) {
    const item = { task, priority, canceled: false }
    queue.push(item)
    queue.sort((a, b) => b.priority - a.priority)
    return () => {
      item.canceled = true
    }
  }

  async function run() {
    while (queue.length) {
      const item = queue.shift()
      if (!item.canceled) {
        await item.task()
      }
    }
  }

  return { add, run }
}

常见误区

  • 只实现队列先进先出,没有体现优先级。
  • 取消时直接从数组删除但没有处理已经开始执行的异步任务。
  • 长任务连续执行,不考虑让出主线程和用户交互响应。

作者信息