“This is the 22nd day of my participation in the Gwen Challenge in November. Check out the details: The Last Gwen Challenge in 2021.”
After reading the last article, many people may feel confused and still can’t understand what scheduler library can do and why, because I haven’t looked at the entire library code at that time, so I don’t have the whole picture. Today, take a moment to review the entire Scheduler. Js code to summarize the core concepts of Scheduler.
Reference to a global object
As mentioned in the previous article, React does not directly use setTimeout and other global objects. Instead, it uses another alias variable to reference the object. This alias prevents users from polluting the global variables and causing react exceptions. Scheduler.js fetching an alias reference has the following types:
The original object | The alias variable |
---|---|
performance | localPerformance |
Date | localDate |
setTimeout | localSetTimeout |
clearTimeout | localClearTimeout |
setImmediate | localSetImmediate |
The global variable
Scheduler defines a number of global variables that have important semantics. The key global variables are listed here for ease of understanding
- IsInputPending: Determines whether input events are being processed. IsInputPending is a function that is essentially a non-standard DOM API
navigator.scheduling.isInputPending
- IsHostTimeoutScheduled: Whether requestHostTimeout has been executed but hostTimeout has not started
- IsHostCallbackScheduled: Whether requestHostCallback has been executed
- IsPerformingWork: Whether workLoop logic is being processed
- IsSchedulerPaused: Debugging related
- IsMessageLoopRunning: Indicates whether the information loop is running
- ScheduledHostCallback: callback of a scheduled task
- CurrentTask: currentTask to be executed
- CurrentPriorityLevel: Priority of the current task
- EnableIsInputPending: When packing, if enableNewReconciler is configured to be true, it is also true. It is good to keep it true when we understand the code.
- EnableIsInputPendingContinuous: same as above
- EnableProfiling: Switch to test performance. Used to allow measuring the frequency and cost of rendering the entire React application. Not the logic we care about, encountered the code can be skipped.
- EnableSchedulerDebugging: Whether to enable debugging
Priority queue
Scheduler uses priority queues, a minimum-heap data structure that provides push, POP, and PEEK. Peek is the heap head of the smallest heap, and the sortIndex field of the object is used as the comparison basis.
There are two priority queues for storing tasks globally, taskQueue and timerQueue
Task structure
var newTask = {
id: taskIdCounter++,
callback,
priorityLevel,
startTime,
expirationTime,
sortIndex: -1,
};
Copy the code
Callback contains the actual operation of the task. When callback is executed, it means that the task is scheduled and completed. So to speak, the function of Scheduler is to control the execution timing of callback.
The ID is the unique identifier of the task. Each new task is incremented by taskIdCounter, ensuring that there are no duplicate ids for different tasks.
SortIndex is the priority of the task in the priority queue, which is implemented with a minimum heap.
PriorityLevel is also used to indicate the priority. The higher the priority, the smaller the value. ImmediatePriority, UserBlockingPriority, NormalPriority, LowPriority, and IdlePriority should be ImmediatePriority, UserBlockingPriority, NormalPriority, LowPriority, and IdlePriority, with numbers 1, 2, 3, 4, and 5 respectively.
The expirationTime of a task is related to its priorityLevel. The smaller the priorityLevel is, the earlier the expirationTime is. For ImmediatePriority, the expiration time is equal to the start time minus 1, meaning that it can be immediately dispatched
operation
unstable_scheduleCallback
- Create a task based on the incoming callback and priorityLevel
- If it is a delayed task, place the task in the timerQueue. If the taskQueue header is empty and the new task is at the head of the timerQueue, set isHostTimeoutScheduled to true. RequestHostTimeout is then executed to schedule a timeout, after which cancelHostTimeout is cancelHostTimeout to clear the existing timeout if isHostTimeoutScheduled is already true.
- If the task is not delayed, it is placed in the task queue. If isPerformingWork is false and isHostCallbackScheduled is also false, set isHostCallbackScheduled to true, And requestHostCallback(flushWork) is executed.
The timeout related
- RequestHostTimeout: Used localSetTimeout to set a timer and asynchronously execute a callback after a specified period of time
- CancelHostTimeout: Clears the timer set by requestHostTimeout using localClearTimeout
The core
- AdvanceTimers: Continuously fetch the queue head tasks from timerQueue, mainly doing two things: 1. If the callback of the queue header task is null, it is removed from the queue. 2. If the startTime of a non-null task is smaller than the current time, move the task from timerQueue to taskQueue with sortIndex updated to be the same as the expirationTime field. Repeat the loop until there are no tasks whose callback is null and startTime is less than the current time; that is, when advanceTimers call ends, timerQueue high-priority tasks will enter the taskQueue at the time when they start
- FlushWork: If a timer callback set by requestHostTimeout has not been executed, the flushWork is cleared, the callback is discarded, and the workLoop is executed. Save the worksite before executing the workLoop — save currentPriorityLevel, isPerformingWork = true. Restore the worksite after executing workLoop — restore the saved value to the global variable currentPriorityLevel. The return value is the return value of the workLoop, indicating whether any tasks have not been executed
- WorkLoop: Call advanceTimers and then in aThe while loopPerform some operations. After exiting the loop, check whether currentTask is null. If the currentTask is null, it indicates that the task queue has no content that can be scheduled. Fetch a firstTimer from timerQueue, and the task may not start
firstTimer.startTime
, execute requestHostTimeout to implement when the time is upfirstTimer.startTime
HandleTimeout is executed asynchronously - HandleTimeout: Callback logic for requestHostTimeout. Set the global isHostTimeoutScheduled variable to false and execute advanceTimers. If isHostCallbackScheduled is true, end; Otherwise, if taskQueue still has tasks, isHostCallbackScheduled is set to true and requestHostCallback(flushWork) is executed. If taskQueue runs out of tasks, the queue head is retrieved from timerQueue. If not null, requestHostTimeout is executed, and handleTimeout is executed again
- RequestHostCallback: Will enter the assigned to global variables scheduledHostCallback, if global variables isMessageLoopRunning to false, is set to true, and perform schedulePerformWorkUntilDeadline
- Asynchronous execution performWorkUntilDeadline schedulePerformWorkUntilDeadline: as soon as possible. There are three asynchronous strategies, prioritizing Using localSetImmediate, using MessageChannel if setImmediate is not supported by the engine, and using localSetTimeout if that still fails
- PerformWorkUntilDeadline: If global variables scheduledHostCallback is not null, then execute it, and get the return value, according to whether there is any task need to be performed, if it is true, continue to call schedulePerformWorkUntilDeadline, Otherwise, reset scheduledHostCallback to NULL and set isMessageLoopRunning to false
WorkLoop’s while loop
- One of the loop conditions is that currentTask is not null
- Exit the loop if the task expires longer than the current time and there is no time left or if some case needs to be broken immediately
- Update currentPriorityLevel for currentTask priorityLevel
- CurrentTask callback is executed if it is a function, then advanceTimers is called, otherwise the queue header is removed from the taskQueue. Currenttask.callback If it is a function, the return value is assigned to currenttask.callback (currenttask.callback is reset to null before callback is executed)
- If currentTask is equal to the header of the taskQueue, the header is removed from the taskQueue
- Fetches the head task from the taskQueue and assigns it to currentTask