Skip to content

Scheduler手写思路

基本流程

  1. 新建一个task,处理startTime(根据options.delay判断是taskQueue内容还是timeQueue内容),处理expirationTime(根据优先级设置不同的过期时间),加入到对应的queue中
  2. 然后触发requestHostCallback(flushWork): 进行boolean值锁的设置,scheduledHostCallback=flushWork,然后触发schedulePerformWorkUtilDeadLine()
  3. schedulePerformWorkUtilDeadLine: MessageChannel触发post.postMessage(null)
  4. 宏任务触发performWorkUntilDeadline: 执行之前requestHostCallback设置的callback(scheduledHostCallback=flushWork)进行执行,判断返回结果,如果返回结果显示hasWork(一般是时间不够导致的work包含的多个task没有执行完毕),再次MessageChannel触发post.postMessage(null)
  5. performWorkUntilDeadline -> flushWork,实际就是workLoop()

宏任务具体内容

flushWork本质就是执行workLoop + 一些全局变量的重置 + 错误的处理

workLoop:

  • taskQueue中不断取出task进行执行,首先设置task.callback=null
  • 如果task.callback返回function,说明还没执行完毕,那么task.callback=continueCallback
  • 如果task.callback没有返回function,执行完毕,则继续从taskQueue的栈顶取出task进行执行
  • 在每一次可能的场景中,判断是否需要将timeQueue取出task放入到taskQueue进行执行(startTime是否已经到达)

workLoop中断

当【taskQueue的栈顶元素task.expirationTime大于当前时间】 && 【!hasTimeRemaining || shouldYieldToHost()没有时间可以继续执行任务】

系统没有时间给你执行,而且当前任务还可以拖一拖的情况下,hasWork=true触发新一轮宏任务

中断while循环,currentTask不为空,直接返回true,hasWork=true=>再次MessageChannel触发post.postMessage(null)

workLooptaskQueue执行完毕后的处理-倒计时处理timerQueue

  • 新增task时:当taskQueue为空时,我们增加一个新的task,但是task.startTime大于当前时间,因此taskQueue不需要执行,这个时候进行倒计时,timeQueue的栈顶进行startTime-当前时间的setTimeout倒计时
  • workLoop执行时:当taskQueue为空时,taskQueue不需要执行,这个时候进行倒计时,timeQueue的栈顶进行startTime-当前时间的setTimeout倒计时

使用requestHostTimeout传递处理函数handleTimeout+当前的倒计时ms

注意两件事:我们调用requestHostTimeout时记得先取消掉原来的倒计时;新增task可能不是栈顶,需要进行判断!

handleTimeout主要负责两个事情:

  • 检测是否可以将timerQueue -> taskQueue
  • 如果目前有task,则触发requestHostCallback(flushWork);如果没有task,说明timerQueue -> taskQueue,这个task不知道为什么,在倒计时期间变为废弃的!则重新从timerQueue中获取栈顶进行倒计时

timerQueue -> taskQueue的时机

  • 当每次处理完一个task的时候,我们需要触发advanceTimers检测是否需要将timerQueue -> taskQueue,比如刚开始触发workLoop,比如从taskQueue处理完成一个task之后
  • 同时也存在timerQueue中有废弃数据的情况(通过timerTask.callback为null)进行数据的剔除

taskQueue二叉堆实现思路

  1. 使用index=0作为占位符,root=1为根节点,那么children=2、3对应的children(4, 5)(6, 7),我们可以轻易算出parentchildren之间的关系
  2. 涉及到上浮和下沉两种操作
  • 当我们增加元素时,我们需要进行上浮不停对比currentNodeparentNode的关系,然后不停swap切换
  • 当我们删除栈顶元素时,我们可以将末尾的元素换到栈顶,然后将整体长度size减去1,然后对目前新的栈顶元素进行下沉
    • 需要比对leftChildrightChild哪个比较小(最小堆),然后swap切换,然后将当前的index切换到leftChild/rightChild比较小的index
    • 不停比对currentNodechildLeftNode/childrightNode,然后不停swap切换

总结

ensureRootIsScheduled的分析中,我们知道最终触发的下面的代码

js
newCallbackNode = scheduleCallback(
    schedulerPriorityLevel,
    performConcurrentWorkOnRoot.bind(null, root)
)

其中,scheduleCallback触发的就是newTask加入到taskQueue或者timerQueue,然后触发对应的宏任务

而在宏任务中,我们会不断触发task.callback,然后判断是否有hasWork,如果有则继续触发宏任务,没有则结束!

而这里的task.callback即使上面代码中传入的performConcurrentWorkOnRoot.bind(null, root)!

因此我们认为scheduleCallback就是一个调度器,不停调度去处理performConcurrentWorkOnRoot.bind(null, root)执行!