You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
// callback -> performAsyncWork, deprecated_options -> { timeout }functionunstable_scheduleCallback(callback,deprecated_options){// 即 Date.now()varstartTime=currentEventStartTime!==-1 ? currentEventStartTime : getCurrentTime();varexpirationTime;if(typeofdeprecated_options==='object'&&deprecated_options!==null&&typeofdeprecated_options.timeout==='number'){// expirationTime 的逻辑将来可能全部移入到 Scheduler 包中, 目前只会进入这个判断// FIXME: Remove this branch once we lift expiration times out of React.expirationTime=startTime+deprecated_options.timeout;}else{switch(currentPriorityLevel){caseImmediatePriority:
expirationTime=startTime+IMMEDIATE_PRIORITY_TIMEOUT;break;caseUserBlockingPriority:
expirationTime=startTime+USER_BLOCKING_PRIORITY;break;caseIdlePriority:
expirationTime=startTime+IDLE_PRIORITY;break;caseNormalPriority:
default:
expirationTime=startTime+NORMAL_PRIORITY_TIMEOUT;}}varnewNode={// performAsyncWork
callback,// 目前用不到priorityLevel: currentPriorityLevel,// timeout + now()
expirationTime,// 存储链表结构next: null,previous: null,};// firstCallbackNode 是用来维护的双向链表的头部if(firstCallbackNode===null){// This is the first callback in the list.firstCallbackNode=newNode.next=newNode.previous=newNode;// firstCallbackNode 改变需要调用 进入调度过程ensureHostCallbackIsScheduled();}else{// 如果有一个/多个 node 存在链表结构里// next表示下一个要执行的nodevarnext=null;// 原来的varnode=firstCallbackNode;do{// 根据 expirationTime 排序,把优先级高的放在前面,每次都跟 firstCallbackNode 比较if(node.expirationTime>expirationTime){// The new callback expires before this one.// 表示新的node的优先级高于原来的,将原来的(firstCallbackNode)赋值给 next, 退出循环next=node;break;}node=node.next;}while(node!==firstCallbackNode);// 如果 nex t为null,说明 node.expirationTime < expirationTime, 即新的node的优先级是最小的if(next===null){// No callback with a later expiration was found, which means the new// callback has the latest expiration in the list.// next为原来的nodenext=firstCallbackNode;// 表示新的node的优先级 高于原来node的优先级,}elseif(next===firstCallbackNode){// The new callback has the earliest expiration in the entire list.// 重新将 firstCallbackNode 赋值为新的node,即保证firstCallbackNode 为优先级最高的那个nodefirstCallbackNode=newNode;// firstCallbackNode 改变需要调用 进入调度过程ensureHostCallbackIsScheduled();}// 原来 指向firstCallBakcNode.previous 和 指向firstCallBakcNode.next 都指向自己// previous、next 都是指向firstCallBakcNodevarprevious=next.previous;// 现在将 都是指向firstCallBakcNode.previous 和 都是指向firstCallBakcNode.next 指向 newNodeprevious.next=next.previous=newNode;// 将 newNode.previous 和newNode.next 指向firstCallBakcNode, 形成环状双向链表结构newNode.next=next;newNode.previous=previous;}returnnewNode;}
ensureHostCallbackIsScheduled
检查是否有 callbackNode 在执行,否则停止执行
判断 hostCallback 是否在调度,已经调度就取消
执行 requestHostCallback
functionensureHostCallbackIsScheduled(){// 表示已经有一个 callbackNode 在调用了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.varexpirationTime=firstCallbackNode.expirationTime;// 判断这个 hostCallback 有没有进入调度if(!isHostCallbackScheduled){isHostCallbackScheduled=true;}else{// Cancel the existing host callback.cancelHostCallback();}requestHostCallback(flushWork,expirationTime);}
requestHostCallback
// callback 为 flushWork 函数requestHostCallback=function(callback,absoluteTimeout){scheduledHostCallback=callback;timeoutTime=absoluteTimeout;// absoluteTimeout < 0 已经超时,直接强制执行if(isFlushingHostCallback||absoluteTimeout<0){// Don't wait for the next frame. Continue working ASAP, in a new event.window.postMessage(messageKey,'*');// isAnimationFrameScheduled = false 表示还没进入调度循环}elseif(!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;// 进入调度, 竞争调用 animationTickrequestAnimationFrameWithTimeout(animationTick);}};
varANIMATION_FRAME_TIMEOUT=100;varrAFID;varrAFTimeoutID;varrequestAnimationFrameWithTimeout=function(callback){// schedule rAF and also a setTimeout// requestAnimationFrame 执行则取消 setTimeoutrAFID=localRequestAnimationFrame(function(timestamp){// cancel the setTimeoutlocalClearTimeout(rAFTimeoutID);callback(timestamp);});// 100ms 内 requestAnimationFrame 内的 callback 还未执行,则取消requestAnimationFrame,rAFTimeoutID=localSetTimeout(function(){// cancel the requestAnimationFramelocalCancelAnimationFrame(rAFID);// 传入当前时间callback(getCurrentTime());},ANIMATION_FRAME_TIMEOUT);};
animationTick
varframeDeadline=0;// We start out assuming that we run at 30fps but then the heuristic tracking// will adjust this value to a faster fps if we get more frequent animation// frames.// 假设以 30fps 运行,然后进行跟踪,如果获得更频繁的帧,则会将此值调整为更快的fpsvarpreviousFrameTime=33;varactiveFrameTime=33;varanimationTick=function(rafTime){if(scheduledHostCallback!==null){// 立马请求下一帧调用自己,不停的调用,队列里有很多 callbackrequestAnimationFrameWithTimeout(animationTick);}else{// No pending work. Exit.// 没有方法要被调度,返回isAnimationFrameScheduled=false;return;}// rafTime 是调用的当前时间,frameDeadline 为0,activeFrameTime 为 33,即保持浏览器一秒 30帧,每一帧的执行一帧完整的时间// 用来计算下一帧可以执行的时间是多少,即nextFrameTimevarnextFrameTime=rafTime-frameDeadline+activeFrameTime;// 下一个方法调用进来 下一帧重新计算nextFrameTime,rafTime - frameDeadline 如果小于 0 ,说明浏览器的刷新频率要大于 30hz,帧时间是要小于 33ms的// 如果连续两次帧的调用计算出来的时间是小于 33ms,就设置帧时间变小,// 主要针对不同平台的刷新频率的问题,比如 vr 120帧,根据平台的刷新频率设置 activeFrameTime, 1000 / 120 -> 8if(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, 来达到减少React运行占用时间的目的activeFrameTime=nextFrameTime<previousFrameTime ? previousFrameTime : nextFrameTime;}else{// 前一帧运行时间和下一帧运行时间相同previousFrameTime=nextFrameTime;}// 第一个 frameDeadlineframeDeadline=rafTime+activeFrameTime;// isMessageEventScheduled 会在 cancelHostCallback 和 idleTick 里都会设置为 falseif(!isMessageEventScheduled){isMessageEventScheduled=true;// 通过 requestAnimationFrame 调用完 callback 后立马会进入浏览器动画更新的设定,往任务队列里插入react任务来模拟 requestIdleCallback 方法// postMessage属于 macrotask,等待浏览器执行完再执行队列里的任务,即使每一帧的时间为33ms,但等到 message 接收时,实际留给react的时间没有 33ms// 给任务队列插入 react 任务,等浏览器执行完自己的任务再执行这里队列里的window.postMessage(messageKey,'*');}};
// 接受 react 任务window.addEventListener('message',idleTick,false);varidleTick=function(event){// 判断 key 和 判断 message 源if(event.source!==window||event.data!==messageKey){return;}isMessageEventScheduled=false;// 先赋值 再重置,// scheduledHostCallback -> flushWork// timeoutTime -> firstCallbackNode.expirationTime 到期时间varprevScheduledCallback=scheduledHostCallback;varprevTimeoutTime=timeoutTime;scheduledHostCallback=null;timeoutTime=-1;varcurrentTime=getCurrentTime();vardidTimeout=false;// 表示浏览器动画或用户反馈操作超过 33ms, 把这一帧的时间用完了,react 没有时间去更新了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.// 没有过期,且 isAnimationFrameScheduled 为 false 的情况,则直接调用 requestAnimationFrameWithTimeout// 在 animationTick 函数中 判断 scheduledHostCallback === null 时才会赋值其为false,// 而 scheduledHostCallback === null 只有在 cancelHostCallback 函数 和 idleTick 函数中执行if(!isAnimationFrameScheduled){// Schedule another animation callback so we retry later.// 调度另一个动画回调,以便我们稍后重试isAnimationFrameScheduled=true;requestAnimationFrameWithTimeout(animationTick);}// Exit without invoking the callback.// 没有过期,则退出而不执行callback,将scheduledHostCallback、tiemoutTime 恢复原来值,直接returnscheduledHostCallback=prevScheduledCallback;timeoutTime=prevTimeoutTime;return;}}if(prevScheduledCallback!==null){// 正在调用这个 callbackisFlushingHostCallback=true;try{// 根据 didTimeout 判断 是否强制执行更新prevScheduledCallback(didTimeout);}finally{isFlushingHostCallback=false;}}};
vardeadlineObject={
timeRemaining,didTimeout: false,};// 默认 hasNativePerformanceNow = truevartimeRemaining=function(){// ...// We assume that if we have a performance timer that the rAF callback// gets a performance timer value. Not sure if this is always true.// frameDeadline - now() > 0 这一帧的渲染剩余多少时间, 在 shouldYield 中使用并判断// 模拟传给 requestIdleCallback方法的回调函数的 IdleDeadline 参数 https://developer.mozilla.org/zh-CN/docs/Web/API/IdleDeadline 表示当前闲置周期的预估剩余毫秒数varremaining=getFrameDeadline()-performance.now();returnremaining>0 ? remaining : 0;}// didTimout -> firstCallbackNode 是否已经超时的判断functionflushWork(didTimeout){// 开始调用 callback -> ensureHostCallbackIsScheduled 函数会直接returnisExecutingCallback=true;deadlineObject.didTimeout=didTimeout;try{// firstCallbackNode的任务已经过期if(didTimeout){// Flush all the expired callbacks without yielding.// 循环while(firstCallbackNode!==null){// 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.varcurrentTime=getCurrentTime();// 第一个 expirationTime 肯定小于 currentTime, 为过期任务if(firstCallbackNode.expirationTime<=currentTime){// 循环执行callback链表,直到第一个没有过期的任务为止, 已经过期的任务都强制输出do{// flushFirstCallback 会将 firstCallbackNode.next 赋值给 firstCallbackNodeflushFirstCallback();}while(// firstCallbackNode 变成下一个,循环判断链表任务是否过期firstCallbackNode!==null&&firstCallbackNode.expirationTime<=currentTime);// 已经过期的任务都输出了,下一次判断if (firstCallbackNode.expirationTime <= currentTime) 为false,直接breakcontinue;}break;}}else{// Keep flushing callbacks until we run out of time in the frame.// 任务没有过期, 继续刷新回调,直到帧中的时间不足为止。if(firstCallbackNode!==null){do{flushFirstCallback();}while(// 帧还有时间空闲(还有剩余时间)才执行 callbackfirstCallbackNode!==null&&getFrameDeadline()-getCurrentTime()>0);}}}finally{isExecutingCallback=false;if(firstCallbackNode!==null){// There's still work remaining. Request another callback.// 还剩有 firsrCallbackNode 时,调用ensureHostCallbackIsScheduled ,此时 isHostCallbackScheduled 为 true,则会执行 cancelHostCallback 重置所有的调度常量,将 scheduledHostCallback重置为null,避免在 animationTick 中将老的 callback 重复执行一边ensureHostCallbackIsScheduled();}else{isHostCallbackScheduled=false;}// Before exiting, flush all the immediate work that was scheduled.// firstCallbackNode.priorityLevel === currentPriorityLevel 这个函数不会执行flushImmediateWork();}}
flushFirstCallback
将 firstCallbackNode 指向它的next处理链表表头,执行callback
functionflushFirstCallback(){varflushedNode=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.varnext=firstCallbackNode.next;// 表示只有一个 callbackNodeif(firstCallbackNode===next){// This is the last callback in the list.firstCallbackNode=null;next=null;}else{// 替换 firstCallbackNode 的操作varlastCallbackNode=firstCallbackNode.previous;// 将 firstCallbackNode 指向 firstCallbackNode.nextfirstCallbackNode=lastCallbackNode.next=next;next.previous=lastCallbackNode;}// 清空老 firstCallbackNode 的 next 和 previousflushedNode.next=flushedNode.previous=null;// Now it's safe to call the callback.varcallback=flushedNode.callback;varexpirationTime=flushedNode.expirationTime;varpriorityLevel=flushedNode.priorityLevel;varpreviousPriorityLevel=currentPriorityLevel;varpreviousExpirationTime=currentExpirationTime;currentPriorityLevel=priorityLevel;currentExpirationTime=expirationTime;varcontinuationCallback;try{// 执行 callbackcontinuationCallback=callback(deadlineObject);}finally{currentPriorityLevel=previousPriorityLevel;currentExpirationTime=previousExpirationTime;}// performAsyncWork 没有返回则下面不会执行if(typeofcontinuationCallback==='function'){// ....}}
The text was updated successfully, but these errors were encountered:
reactScheduler 异步任务调度
reactScheduler 核心功能
时间片概念
scheduleCallbackWithExpirationTime 异步任务调度
在
requestWork
中中如果判断是异步调度的方法就会执行 scheduleCallbackWithExpirationTimescheduleCallback
scheduler
模块下unstable_scheduleCallback
函数firstCallbackNode
是维护的双向链表结构的头部ensureHostCallbackIsScheduled
callbackNode
在执行,否则停止执行hostCallback
是否在调度,已经调度就取消requestHostCallback
requestHostCallback
requestAnimationFrameWithTimeout
animationTick
idleTick
flushWork
flushFirstCallback
The text was updated successfully, but these errors were encountered: