scheduler 源码分析

scheduler 模块用于管理重绘完成后回调的执行逻辑。在 scheduler 中,requestHostCallback 函数用于实现在重绘完成后、视线程的空闲程度以及任务预设的超时时间,以在特定的时机执行任务。在 requestHostCallback 的基础上,scheduler 使用双向链表构建含有优先级的原子任务节点,再输出添加并执行任务节点、打断任务节点执行的 api。

requestHostCallback

scheduler 模块支持三种方式实现 requestHostCallback, cancelHostCallback, shouldYieldToHost, getCurrentTime 函数:一、通过 window._schedMock 或 global._schedMock 数组注入;二、通过 setTimeout 实现(实现上当 requestHostCallback 的首参 —— 任务函数尚在执行过程中,scheduler 将 requestHostCallback 作为 setTimeout 的回调函数,以使在新添加任务函数间接添加到 macrotasks 队列中,可参看源码);三、基于 MessageChannel, requestAnimationFrame 实现(MessageChannel 用于探知线程是否空闲,requestAnimationFrame 用于与重绘机制契合)。本文着重介绍第三种方式,并用 rAF 指代 requestAnimationFrame。

基于 MessageChannel, requestAnimationFrame 实现的上述四种函数的功能点为:

函数名 意义
requestHostCallback(callback, absoluteTimeout) 在重绘完成后执行任务,并与线程的空闲程度相契合,详情见下文
cancelHostCallback 用于取消任务
shouldYieldToHost 用于判断任务是否超时、需要被打断
getCurrentTime 使用 performance 或 Date 对象获取当前时间

requestHostCallback 执行的流程图为:

上图中所用的缓存值有:

缓存 意义
scheduledHostCallback 待执行的回调,即上图中的任务,会被刷新
timeoutTime 任务允许的超时时间点
isAnimationFrameScheduled rAF 轮询启动状态
isMessageEventScheduled 消息发送中标识
isFlushingHostCallback 任务执行中标识
activeFrameTime 任务允许的超时时间点
timeoutTime 一帧的期望执行时长,预设为 33 ms,在视图刷新过快时会更新,值将变小
previousFrameTime 前一帧执行时长
frameDeadline 下一帧期望完成时间点,用于判断重绘后 js 线程是否空闲,还是长期占用

实现机制上,scheduler 首先实现 requestAnimationFrameWithTimeout 函数。该函数封装 requestAnimationFrame 函数,以使任务在重绘完后才予执行;但如果一些视图操作在后台执行,requestAnimationFrame 的回调将得不到执行,因此 scheduler 使用 setTimeout 兜底。流程图中启动 rAF 轮询就是指调用 requestAnimationFrameWithTimeout; animateTick 作为 requestAnimationFrameWithTimeout 回调。在 animateTick 的实现中,若 scheduledHostCallback 非 null,scheduler 将持续调用 requestAnimationFrameWithTimeout。因此本文将这一过程称为 rAF 轮询。

scheduler 考虑了重绘完成后、线程可能处于紧张的场景,因此所添加的任务并没有直接作为 requestAnimationFrameWithTimeout 的回调。scheduler 使用 MessageChannel 添加 macrotasks 队列作桥接,等待线程空闲,然后再执行任务。在 animateTick 中,scheduler 将计算下一帧期望完成时间点 previousFrameTime,然后通过 port.postMessage 方法发送消息。等到 port1 接受到消息时,schdulear 将 previousFrameTime 与 currentTime 作比较:当 previousFrameTime 小于等于 currentTime 时,scheduler 认为线程不是空闲的,对于超时的任务将立即执行,对于未超时的任务将在下次重绘后予以处理;当 previousFrameTime 大于 currentTime 时,线程就是空闲的,scheduler 将立即执行。这一处理机制在 port1.onMessage 监听函数中实现(作为 macrotasks,port1 接受消息的时机将随着线程的空闲程度起变化)。

有了上述的处理逻辑,scheduler 在外围制作 requestHostCallback(callback, absoluteTimeout) 接口:当前如果有任务正在执行中(意为当前没有重绘任务,重绘线程是空闲的)或者所添加的任务需要立即执行,scheduler 直接调用 port.postMessage 发送消息,跳过 rAF 轮询,以使任务得到即时执行;否则,如果 rAF 轮询未启动,调用 requestAnimationFrameWithTimeout(animationTick) 启动轮询。

在 requestHostCallback 接口之外,cancelHostCallback 通过 scheduledHostCallback 置为 null,以中断任务的执行;shouldYieldToHost 用于比较 frameDeadline 是否小于等于 currentTime(在一帧时间外),以此推断线程是否空闲,好添加并处理新任务。以下是 requestHostCallback 函数实现的源码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
var requestAnimationFrameWithTimeout = function(callback) {
// schedule rAF and also a setTimeout
rAFID = localRequestAnimationFrame(function(timestamp) {
// cancel the setTimeout
localClearTimeout(rAFTimeoutID);
callback(timestamp);
});
rAFTimeoutID = localSetTimeout(function() {
// cancel the requestAnimationFrame
localCancelAnimationFrame(rAFID);
callback(getCurrentTime());
}, ANIMATION_FRAME_TIMEOUT);
};

var channel = new MessageChannel();
var port = channel.port2;
channel.port1.onmessage = function(event) {
isMessageEventScheduled = false;

var prevScheduledCallback = scheduledHostCallback;
var prevTimeoutTime = timeoutTime;
scheduledHostCallback = null;
timeoutTime = -1;

var currentTime = getCurrentTime();

var didTimeout = false;

// 线程不空闲
if (frameDeadline - currentTime <= 0) {
// There's no time left in this idle period. Check if the callback has
// a timeout and whether it's been exceeded.
if (prevTimeoutTime !== -1 && prevTimeoutTime <= currentTime) {
// Exceeded the timeout. Invoke the callback even though there's no
// time left.
didTimeout = true;
} else {
// No timeout.
if (!isAnimationFrameScheduled) {
// Schedule another animation callback so we retry later.
isAnimationFrameScheduled = true;
requestAnimationFrameWithTimeout(animationTick);
}
// Exit without invoking the callback.
scheduledHostCallback = prevScheduledCallback;
timeoutTime = prevTimeoutTime;
return;
}
}

if (prevScheduledCallback !== null) {
isFlushingHostCallback = true;
try {
prevScheduledCallback(didTimeout);
} finally {
isFlushingHostCallback = false;
}
}
};

var animationTick = function(rafTime) {
if (scheduledHostCallback !== null) {
// Eagerly schedule the next animation callback at the beginning of the
// frame. If the scheduler queue is not empty at the end of the frame, it
// will continue flushing inside that callback. If the queue *is* empty,
// then it will exit immediately. Posting the callback at the start of the
// frame ensures it's fired within the earliest possible frame. If we
// waited until the end of the frame to post the callback, we risk the
// browser skipping a frame and not firing the callback until the frame
// after that.
requestAnimationFrameWithTimeout(animationTick);
} else {
// No pending work. Exit.
isAnimationFrameScheduled = false;
return;
}

var nextFrameTime = rafTime - frameDeadline + activeFrameTime;
if (
nextFrameTime < activeFrameTime &&
previousFrameTime < activeFrameTime
) {
if (nextFrameTime < 8) {
// Defensive coding. We don't support higher frame rates than 120hz.
// If the calculated frame time gets lower than 8, it is probably a bug.
nextFrameTime = 8;
}
// If one frame goes long, then the next one can be short to catch up.
// If two frames are short in a row, then that's an indication that we
// actually have a higher frame rate than what we're currently optimizing.
// We adjust our heuristic dynamically accordingly. For example, if we're
// running on 120hz display or 90hz VR display.
// Take the max of the two in case one of them was an anomaly due to
// missed frame deadlines.
activeFrameTime =
nextFrameTime < previousFrameTime ? previousFrameTime : nextFrameTime;
} else {
previousFrameTime = nextFrameTime;
}
frameDeadline = rafTime + activeFrameTime;
if (!isMessageEventScheduled) {
isMessageEventScheduled = true;
port.postMessage(undefined);
}
};

requestHostCallback = function(callback, absoluteTimeout) {
scheduledHostCallback = callback;
timeoutTime = absoluteTimeout;
if (isFlushingHostCallback || absoluteTimeout < 0) {
// Don't wait for the next frame. Continue working ASAP, in a new event.
port.postMessage(undefined);
} else if (!isAnimationFrameScheduled) {
// If rAF didn't already schedule one, we need to schedule a frame.
// TODO: If this rAF doesn't materialize because the browser throttles, we
// might want to still have setTimeout trigger rIC as a backup to ensure
// that we keep performing work.
isAnimationFrameScheduled = true;
requestAnimationFrameWithTimeout(animationTick);
}
};

scheduler

callbackNode 及其执行机制

scheduler 以首尾相连的双向链表缓存 callbackNode,每个任务节点的 previous 属性指向上一个任务节点(firstCallbackNode 的 previous 属性指向 lastCallbackNode),next 属性指向下一个任务节点(lastCallbackNode 的 next 属性指向 firstCallbackNode)。链表中的 callbackNode 以优先级(以超时时间 expirationTime 属性表示)进行排序。以双向链表构建的数据,只需要缓存 firstCallbackNode 首任务节点。

scheduler 针对 callbackNode 设定五种优先级,分别是 ImmediatePriority, UserBlockingPriority, NormalPriority, LowPriority, IdlePriority。优先级会影响超时时间,以上五个优先级的超时时间分别为 -1, 250, 5000, 10000, 1073741823。因此对于 ImmediatePriority 优先级的任务节点,在使用 requestHostCallback 函数处理的过程中,该任务节点就不需要经过 rAF 回调机制,而是会经由 MessageChannel 直接被处理。

在 callbackNode 任务节点中,callback 属性为待执行的函数,priorityLevel 为执行优先级,expirationTime 为允许的超时时间点(以当前时间 + 超时时间的方式计算)。因为 requestHostCallback 每次调用时都会刷新 scheduledHostCallback 缓存,callbackNode.callback 并不能直接作为 requestHostCallback 的参数,那样会在两次调用过程中,造成前一个任务丢失。scheduler 实际使用 flushWork 函数作为 requestHostCallback 的参数(在 ensureHostCallbackIsScheduled 函数中处理),便于批量执行双向链表中的 callbackNode.callback 函数,也不至使任务丢失。

flushWork 函数基于 flushFirstCallback, flushImmediateWork,以下是这三个函数的实现机制:

flushFirstCallback 从双向链表中取出首个任务节点并执行。若首个任务节点的 callback 返回函数,使用该函数构建新的 callbackNode 任务节点,并将该任务节点插入双向链表中:若该任务节点的优先级最高、且不只包含一个任务节点,调用 ensureHostCallbackIsScheduled,在下一次重绘后酌情执行双向链表中的任务节点;否则只将新创建的任务节点添加到双向链表中。

基于 flushFirstCallback,flushImmediateWork 函数用于执行双向链表中所有优先级为 ImmediatePriority 的任务节点。如果双向链表不只包含优先级为 ImmediatePriority 的任务节点,flushImmediateWork 将调用 ensureHostCallbackIsScheduled 等待下次重绘后执行剩余的任务节点。

flushWork 作为 requestHostCallback 函数的参数,获得的首个实参 didTimeout 为是否超时的标识。如果超时,flushWork 通过调用 flushFirstCallback 批量执行所有未超时的任务节点;若果没有超时,flushWork 将在下一帧未完成前(通过 shouldYieldToHost 函数判断)尽可能地执行任务节点。等上述条件逻辑执行完成后,如果双向链表非空,调用 ensureHostCallbackIsScheduled 等待下次重绘后执行剩余的任务节点。特别的,当双向链表中还存在 ImmediatePriority 优先级的任务节点,flushWork 将调用 flushImmediateWork 批量执行这些任务节点。

因为 scheduler 使用首个任务节点的超时时间点作为 requestHostCallback 函数的次参(在 ensureHostCallbackIsScheduled 函数中处理)。因此,如果首个任务节点的优先级为 ImmediatePriority,flushWork 所获得参数 didTimeout 也将是否值,其执行逻辑将是执行所有优先级为 ImmediatePriority 的任务节点,再调用 ensureHostCallbackIsScheduled 等待下一次重绘时执行其余任务节点。如果首个任务节点的优先级为 UserBlockingPriority 等,flushWork 将执行同优先级的任务节点,再调用 ensureHostCallbackIsScheduled 等待下一次重绘时执行其余任务节点。所有对不同优先级的任务节点,scheduler 采用分段执行的策略。

缓存 意义
currentPriorityLevel 执行中任务节点的优先级,默认为 NormalPriority
currentExpirationTime 执行中任务节点的超时时间点
enableSchedulerDebugging debug 模式?
isExecutingCallback 任务节点正在批量执行中标识
isHostCallbackScheduled requestHostCallback 已执行并开启 rAF 轮询标识
isSchedulerPaused scheduler 是否被打断,将不会执行任务节点,由接口层更新该值
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
function ensureHostCallbackIsScheduled() {
if (isExecutingCallback) {
// Don't schedule work yet; wait until the next time we yield.
return;
}
// Schedule the host callback using the earliest expiration in the list.
var expirationTime = firstCallbackNode.expirationTime;
if (!isHostCallbackScheduled) {
isHostCallbackScheduled = true;
} else {
// Cancel the existing host callback.
cancelHostCallback();
}
requestHostCallback(flushWork, expirationTime);
}

function flushFirstCallback() {
var flushedNode = firstCallbackNode;

// Remove the node from the list before calling the callback. That way the
// list is in a consistent state even if the callback throws.
var next = firstCallbackNode.next;
if (firstCallbackNode === next) {
// This is the last callback in the list.
firstCallbackNode = null;
next = null;
} else {
var lastCallbackNode = firstCallbackNode.previous;
firstCallbackNode = lastCallbackNode.next = next;
next.previous = lastCallbackNode;
}

flushedNode.next = flushedNode.previous = null;

// Now it's safe to call the callback.
var callback = flushedNode.callback;
var expirationTime = flushedNode.expirationTime;
var priorityLevel = flushedNode.priorityLevel;
var previousPriorityLevel = currentPriorityLevel;
var previousExpirationTime = currentExpirationTime;
currentPriorityLevel = priorityLevel;
currentExpirationTime = expirationTime;
var continuationCallback;
try {
continuationCallback = callback();
} finally {
currentPriorityLevel = previousPriorityLevel;
currentExpirationTime = previousExpirationTime;
}

// A callback may return a continuation. The continuation should be scheduled
// with the same priority and expiration as the just-finished callback.
if (typeof continuationCallback === 'function') {
var continuationNode: CallbackNode = {
callback: continuationCallback,
priorityLevel,
expirationTime,
next: null,
previous: null,
};

// Insert the new callback into the list, sorted by its expiration. This is
// almost the same as the code in `scheduleCallback`, except the callback
// is inserted into the list *before* callbacks of equal expiration instead
// of after.
if (firstCallbackNode === null) {
// This is the first callback in the list.
firstCallbackNode = continuationNode.next = continuationNode.previous = continuationNode;
} else {
var nextAfterContinuation = null;
var node = firstCallbackNode;
do {
if (node.expirationTime >= expirationTime) {
// This callback expires at or after the continuation. We will insert
// the continuation *before* this callback.
nextAfterContinuation = node;
break;
}
node = node.next;
} while (node !== firstCallbackNode);

if (nextAfterContinuation === null) {
// No equal or lower priority callback was found, which means the new
// callback is the lowest priority callback in the list.
nextAfterContinuation = firstCallbackNode;
} else if (nextAfterContinuation === firstCallbackNode) {
// The new callback is the highest priority callback in the list.
firstCallbackNode = continuationNode;
ensureHostCallbackIsScheduled();
}

var previous = nextAfterContinuation.previous;
previous.next = nextAfterContinuation.previous = continuationNode;
continuationNode.next = nextAfterContinuation;
continuationNode.previous = previous;
}
}
}

function flushImmediateWork() {
if (
// Confirm we've exited the outer most event handler
currentEventStartTime === -1 &&
firstCallbackNode !== null &&
firstCallbackNode.priorityLevel === ImmediatePriority
) {
isExecutingCallback = true;
try {
do {
flushFirstCallback();
} while (
// Keep flushing until there are no more immediate callbacks
firstCallbackNode !== null &&
firstCallbackNode.priorityLevel === ImmediatePriority
);
} finally {
isExecutingCallback = false;
if (firstCallbackNode !== null) {
// There's still work remaining. Request another callback.
ensureHostCallbackIsScheduled();
} else {
isHostCallbackScheduled = false;
}
}
}
}

function flushWork(didTimeout) {
// Exit right away if we're currently paused

if (enableSchedulerDebugging && isSchedulerPaused) {
return;
}

isExecutingCallback = true;
const previousDidTimeout = currentDidTimeout;
currentDidTimeout = didTimeout;
try {
if (didTimeout) {
// Flush all the expired callbacks without yielding.
while (
firstCallbackNode !== null &&
!(enableSchedulerDebugging && isSchedulerPaused)
) {
// TODO Wrap i nfeature flag
// Read the current time. Flush all the callbacks that expire at or
// earlier than that time. Then read the current time again and repeat.
// This optimizes for as few performance.now calls as possible.
var currentTime = getCurrentTime();
if (firstCallbackNode.expirationTime <= currentTime) {
do {
flushFirstCallback();
} while (
firstCallbackNode !== null &&
firstCallbackNode.expirationTime <= currentTime &&
!(enableSchedulerDebugging && isSchedulerPaused)
);
continue;
}
break;
}
} else {
// Keep flushing callbacks until we run out of time in the frame.
if (firstCallbackNode !== null) {
do {
if (enableSchedulerDebugging && isSchedulerPaused) {
break;
}
flushFirstCallback();
} while (firstCallbackNode !== null && !shouldYieldToHost());
}
}
} finally {
isExecutingCallback = false;
currentDidTimeout = previousDidTimeout;
if (firstCallbackNode !== null) {
// There's still work remaining. Request another callback.
ensureHostCallbackIsScheduled();
} else {
isHostCallbackScheduled = false;
}
// Before exiting, flush all the immediate work that was scheduled.
flushImmediateWork();
}
}

apis

unstable_scheduleCallback

unstable_scheduleCallback(callback, deprecated_options) 基于 currentPriorityLevel 优先级计算超时时间点,将 callback 构建为一个 callbackNode 任务节点,并将该节点插入双向链表,启用 ensureHostCallbackIsScheduled 等待重绘后执行任务节点。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
function unstable_scheduleCallback(callback, deprecated_options) {
var startTime =
currentEventStartTime !== -1 ? currentEventStartTime : getCurrentTime();

var expirationTime;
if (
typeof deprecated_options === 'object' &&
deprecated_options !== null &&
typeof deprecated_options.timeout === 'number'
) {
// FIXME: Remove this branch once we lift expiration times out of React.
expirationTime = startTime + deprecated_options.timeout;
} else {
switch (currentPriorityLevel) {
case ImmediatePriority:
expirationTime = startTime + IMMEDIATE_PRIORITY_TIMEOUT;
break;
case UserBlockingPriority:
expirationTime = startTime + USER_BLOCKING_PRIORITY;
break;
case IdlePriority:
expirationTime = startTime + IDLE_PRIORITY;
break;
case LowPriority:
expirationTime = startTime + LOW_PRIORITY_TIMEOUT;
break;
case NormalPriority:
default:
expirationTime = startTime + NORMAL_PRIORITY_TIMEOUT;
}
}

var newNode = {
callback,
priorityLevel: currentPriorityLevel,
expirationTime,
next: null,
previous: null,
};

// Insert the new callback into the list, ordered first by expiration, then
// by insertion. So the new callback is inserted any other callback with
// equal expiration.
if (firstCallbackNode === null) {
// This is the first callback in the list.
firstCallbackNode = newNode.next = newNode.previous = newNode;
ensureHostCallbackIsScheduled();
} else {
var next = null;
var node = firstCallbackNode;
do {
if (node.expirationTime > expirationTime) {
// The new callback expires before this one.
next = node;
break;
}
node = node.next;
} while (node !== firstCallbackNode);

if (next === null) {
// No callback with a later expiration was found, which means the new
// callback has the latest expiration in the list.
next = firstCallbackNode;
} else if (next === firstCallbackNode) {
// The new callback has the earliest expiration in the entire list.
firstCallbackNode = newNode;
ensureHostCallbackIsScheduled();
}

var previous = next.previous;
previous.next = next.previous = newNode;
newNode.next = next;
newNode.previous = previous;
}

return newNode;
}

unstable_runWithPriority

unstable_runWithPriority(priorityLevel, eventHandler) 将 currentPriorityLevel 缓存设置为 priorityLevel,随后再执行 eventHandler,最后调用 flushImmediateWork 函数执行所有优先级为 ImmediatePriority 的任务节点,其余任务节点等待下次重绘后再执行。可以设想,当 eventHandler 为 unstable_scheduleCallback 函数时,将影响所添加任务节点的优先级,并立即执行 ImmediatePriority 优先级的任务。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
function unstable_runWithPriority(priorityLevel, eventHandler) {
switch (priorityLevel) {
case ImmediatePriority:
case UserBlockingPriority:
case NormalPriority:
case LowPriority:
case IdlePriority:
break;
default:
priorityLevel = NormalPriority;
}

var previousPriorityLevel = currentPriorityLevel;
var previousEventStartTime = currentEventStartTime;
currentPriorityLevel = priorityLevel;
currentEventStartTime = getCurrentTime();

try {
return eventHandler();
} finally {
currentPriorityLevel = previousPriorityLevel;
currentEventStartTime = previousEventStartTime;

// Before exiting, flush all the immediate work that was scheduled.
flushImmediateWork();
}
}

unstable_wrapCallback

unstable_wrapCallback(callback) 记录当前的优先级 currentPriorityLevel,返回函数处理效果如 unstable_runWithPriority,对于 callback 中新添加的任务节点将使用所记录的 currentPriorityLevel 作为优先级。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function unstable_wrapCallback(callback) {
var parentPriorityLevel = currentPriorityLevel;
return function() {
// This is a fork of runWithPriority, inlined for performance.
var previousPriorityLevel = currentPriorityLevel;
var previousEventStartTime = currentEventStartTime;
currentPriorityLevel = parentPriorityLevel;
currentEventStartTime = getCurrentTime();

try {
return callback.apply(this, arguments);
} finally {
currentPriorityLevel = previousPriorityLevel;
currentEventStartTime = previousEventStartTime;
flushImmediateWork();
}
};
}

其他

  • unstable_pauseExecution 通过将 isSchedulerPaused 置为 true,打断 scheduler 处理任务节点。
  • unstable_continueExecution 取消打断状态,使 scheduler 恢复处理任务节点。
  • unstable_getFirstCallbackNode 获取双向链表中的首个任务节点。
  • unstable_cancelCallback(callbackNode) 从双向链表中移除指定任务节点。
  • unstable_getCurrentPriorityLevel 获取当前优先级 currentPriorityLevel 缓存。
  • unstable_shouldYield 是否需要被打断。
  • unstable_now 获取当前时间。
1
2
3
4
5
6
7
8
function unstable_shouldYield() {
return (
!currentDidTimeout &&
((firstCallbackNode !== null &&
firstCallbackNode.expirationTime < currentExpirationTime) ||
shouldYieldToHost())
);
}

总结

scheduler 模块代码简短,逻辑复杂,在这篇文章中,难免有理解不当的地方。