《手写mini React》 Schedule

188 阅读3分钟

这段代码创建了一个调度器(Scheduler)类,可以根据任务的优先级调度任务的执行。它使用了堆(通过 peek、pop、push、compare 和 swap 函数)来维护任务队列的优先级和顺序。然后,它使用 scheduleCallback 方法来添加任务,并在适当的时间执行它们。

在示例中,任务按照优先级和延迟被添加到调度器中,并且根据它们的优先级和延迟执行。由于不同任务具有不同的优先级和延迟,因此它们的执行顺序可能会有所不同。

const NoPriority = 0;
const ImmediatePriority = 1;
const UserBlockingPriority = 2;
const NormalPriority = 3;
const LowPriority = 4;
const IdlePriority = 5;
// 获取当前时间
function getCurrentTime() { 
  return  Date.now();
}
class Scheduler {
    constructor() {
        this.timerQueue = []; // 延时任务队列
        this.taskQueue = []; // 任务队列
        this.startTime = -1; // 任务开始时间
        this.isPerformingWork = false; // 当前是否在执行任务
    }
    // 开启一个定时器
    requestHostTimeout(cbk, time) {
      // 设置定时器,在指定的时间后执行回调函数
      setTimeout(()=>{
        // 绑定this到回调函数的调用中
        cbk.bind(this)( getCurrentTime());
      }, time);
    }

    // 定时结束执行任务
    handleTimeout(curTime) {
      // 调用advanceTimers函数,传入当前时间作为参数
      this.advanceTimers(curTime);
      // 调用schedulePerformWorkUntilDeadline函数
      this.schedulePerformWorkUntilDeadline();
    }

    // 判断当前任务是否终止
    shouldYieldToHost() {
      // 获取当前时间与开始时间的差值
      const timeElapsed = getCurrentTime() - startTime;
      // 如果差值小于帧间隔时间
      if (timeElapsed < frameInterval) {
        // 返回false
        return false;
      }
      // 返回true
      return true;
    }

    // 将延迟结束的任务添加到任务队列 
    advanceTimers(curTime) {
      // 获取当前时间最早的定时任务
      let timerTask = this.peek(this.timerQueue);
      while (timerTask) {
        if(timerTask.startTime <= curTime) {
          // 如果定时任务的开始时间已经过去了
          // 延迟时间结束
          this.pop(this.timerQueue);
          timerTask.sortIndex = timerTask.expirationTime;
          // 将该定时任务加入任务队列
          this.push(this.taskQueue, timerTask)
        } else {
          // 如果定时任务的开始时间还未到,则结束循环
          return
        } 
        // 获取下一个定时任务
        timerTask = this.peek(this.timerQueue);
      }
    }

    // 将任务添加到队列中并进行调度
    scheduleCallback(priorityLevel, callback, options) {
        // 获取当前时间
        // const curTime = Date.now();
        const curTime = getCurrentTime();
        let timeout;

        // 根据优先级设置超时时间
        switch (priorityLevel) {
            case ImmediatePriority:
              timeout = -1;
              break;
            case UserBlockingPriority:
              timeout = 250;
              break;
            case IdlePriority:
              timeout = 1073741823;
              break;
            case LowPriority:
              timeout = 10000;
              break;
            case NormalPriority:
            default:
              timeout = 5000;
              break;
        }

        let startTime = curTime;
        // 如果提供了选项且选项的delay属性为数字类型,则将startTime更新为当前时间加上选项的delay值
        if(options && typeof options.delay === 'number') {
            startTime += options.delay;
        }
        const expirationTime = startTime + timeout;

        const task = {
            callback,
            priorityLevel,
            startTime,
            expirationTime
        }

        if(startTime > curTime) {
          // 延迟任务加入延迟对列
          task.sortIndex = task.startTime;
          this.push(this.timerQueue,task);
          if(this.peek(this.taskQueue) === null && !this.isPerformingWork) {
            // 全为延迟任务,开启一个定时器,带任务延迟结束开始
            const timerTask = this.peek(this.timerQueue);
            this.requestHostTimeout(this.handleTimeout, timerTask.startTime - curTime)
          }
        } else {
          task.sortIndex = task.expirationTime;
          this.push(this.taskQueue, task);
          // 执行任务
          // 执行任务
          if(!this.isPerformingWork) {
            this.schedulePerformWorkUntilDeadline();
          }
        }
    }

    // 调度执行任务
    schedulePerformWorkUntilDeadline() {
      // 设置正在执行工作的标志为true
      this.isPerformingWork = true;
      // 延迟执行,设置延迟时间为0毫秒
      setTimeout(() => {
        // 获取当前时间作为开始时间
        this.startTime = getCurrentTime();
        // 执行工作循环
        this.workLoop();
        // 设置正在执行工作的标志为false
        this.isPerformingWork = false;
      },0)
    }

    // 执行任务
    workLoop() {
      // 调用advanceTimers函数
      this.advanceTimers();

      // 获取当前任务
      let curTask = this.peek(this.taskQueue);

      // 循环执行任务
      while(curTask) {
        const callback = curTask.callback;

        // 判断回调函数是否为函数类型
        if(typeof callback === 'function') {
          const continuationCallback = callback();

          // 判断返回的函数是否为函数类型
          if(typeof continuationCallback === 'function') {
            // 若函数未执行完成,重新赋值给任务
            // 函数未执行完成,重新赋值给任务
            curTask.callback = continuationCallback;
          } else {
            // 任务执行完成
            // 任务执行完成
            this.pop(this.taskQueue);
          }

          // 调用advanceTimers函数
          this.advanceTimers();
        } else {
          // 任务已完成或 无效,从任务队列中移除
          // 任务已完成或 无效 移除
          this.pop(this.taskQueue);
        }

        // 获取下一个任务
        curTask = this.peek(this.taskQueue);
      }

      if (curTask !== null) {
        // 若还有任务未完成,则继续执行workLoop函数
        return this.workLoop();
      } else {
        const firstTimer = this.peek(this.timerQueue);
        if (firstTimer !== null) {
          const curTime =  getCurrentTime();
          this.requestHostTimeout(this.handleTimeout, firstTimer.startTime - curTime);
        }
        return false;
      }
    }

    compare (a, b) {
      return a.sortIndex < b.sortIndex;
    }

    swap(heap,index, idx) {
      [heap[index],heap[idx]] = [heap[idx], heap[index]]
    }

    push(heap, value) {
      // 将新元素添加到堆中
      // 向上调整
      heap.push(value);
      let index = heap.length - 1;
      let parentIdx = (index - 1) >> 1;
      while(parentIdx >= 0) {
        if(this.compare(heap[index], heap[parentIdx])) {
          // 如果当前元素大于其父元素,则交换它们
          this.swap(heap,index, parentIdx);
          index = parentIdx;
          parentIdx = (index - 1) >> 1;
        } else {
          // 否则,退出循环
          break;
        }
      }
    }

    pop(heap) {
        // 向下调整
        // 如果堆的长度为1,直接返回pop出来的值
        // 向下调整
        if(heap.length === 1) return heap.pop();

        // 交换堆顶元素和堆底元素
        this.swap(heap, 0, heap.length - 1);

        // pop出堆顶元素并赋值给val
        const val = heap.pop();

        // 初始化父节点索引为0
        let parentIdx = 0;

        // 初始化左子节点索引和右子节点索引
        let leftIdx =  parentIdx * 2 + 1;
        let rightIdx = parentIdx * 2 + 2;

        // 获取堆的当前长度减1
        const length = heap.length - 1;

        // 当左子节点和右子节点都存在时,执行循环
        while(leftIdx < length || rightIdx < length) {
            // 如果左子节点不小于父节点且左子节点不小于父节点,说明左子节点满足最小堆性质,结束循环
            if(!this.compare(heap[leftIdx], heap[parentIdx]) && !this.compare(heap[rightIdx], heap[parentIdx])) {
                break;
            // 如果右子节点小于父节点且右子节点大于左子节点,则交换右子节点和父节点,更新父节点索引和右子节点索引,继续执行循环
            } else{
                if(rightIdx < length && 
                    this.compare(heap[rightIdx],heap[parentIdx]) && 
                    this.compare(heap[rightIdx],heap[leftIdx])) {
                        this.swap(heap, rightIdx, parentIdx);
                        parentIdx = rightIdx;
                        leftIdx = parentIdx * 2 + 1;
                        rightIdx = parentIdx * 2 + 2;
                    } else if (this.compare(heap[leftIdx],heap[parentIdx])) {
                        this.swap(heap, leftIdx, parentIdx);
                        parentIdx = leftIdx;
                        leftIdx = parentIdx * 2 + 1;
                        rightIdx = parentIdx * 2 + 2;
                    }
            }
        }

        // 返回pop出来的值
        return val;
    }

    peek(heap) {
      return heap[0] || null;
    }
}

const s = new Scheduler();

const task1 = () =>{
  console.log('task1  start');
  console.log('task1 end');
}

const task2 = () =>{
  console.log('task2  start');
  console.log('task2  end');
}

const task3 = () =>{
  console.log('task3  start');
  console.log('task3  end');
}

const task4 = () =>{
  console.log('task4 delay start');
  console.log('task4 delay end');
}

s.scheduleCallback(4, task1);
s.scheduleCallback(6, task2);
s.scheduleCallback(1, task3);
s.scheduleCallback(1, task4 ,{
  delay: 3000
})
// 执行顺序为: task3 task2 task1 task4

将任务添加,根据优先级进行计算,分别将任务加入到延时队列和任务队列,延时任务结束后进入到任务队列,任务队列是一个小顶堆,每次取堆顶执行