mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
e91dd70ba2
Obviated by Batched Mode.
194 lines
6.6 KiB
JavaScript
194 lines
6.6 KiB
JavaScript
/**
|
|
* Copyright (c) Facebook, Inc. and its affiliates.
|
|
*
|
|
* This source code is licensed under the MIT license found in the
|
|
* LICENSE file in the root directory of this source tree.
|
|
*
|
|
* @flow
|
|
*/
|
|
|
|
// Intentionally not named imports because Rollup would use dynamic dispatch for
|
|
// CommonJS interop named imports.
|
|
import * as Scheduler from 'scheduler';
|
|
import {__interactionsRef} from 'scheduler/tracing';
|
|
import {enableSchedulerTracing} from 'shared/ReactFeatureFlags';
|
|
import invariant from 'shared/invariant';
|
|
|
|
const {
|
|
unstable_runWithPriority: Scheduler_runWithPriority,
|
|
unstable_scheduleCallback: Scheduler_scheduleCallback,
|
|
unstable_cancelCallback: Scheduler_cancelCallback,
|
|
unstable_shouldYield: Scheduler_shouldYield,
|
|
unstable_now: Scheduler_now,
|
|
unstable_getCurrentPriorityLevel: Scheduler_getCurrentPriorityLevel,
|
|
unstable_ImmediatePriority: Scheduler_ImmediatePriority,
|
|
unstable_UserBlockingPriority: Scheduler_UserBlockingPriority,
|
|
unstable_NormalPriority: Scheduler_NormalPriority,
|
|
unstable_LowPriority: Scheduler_LowPriority,
|
|
unstable_IdlePriority: Scheduler_IdlePriority,
|
|
} = Scheduler;
|
|
|
|
if (enableSchedulerTracing) {
|
|
// Provide explicit error message when production+profiling bundle of e.g.
|
|
// react-dom is used with production (non-profiling) bundle of
|
|
// scheduler/tracing
|
|
invariant(
|
|
__interactionsRef != null && __interactionsRef.current != null,
|
|
'It is not supported to run the profiling version of a renderer (for ' +
|
|
'example, `react-dom/profiling`) without also replacing the ' +
|
|
'`scheduler/tracing` module with `scheduler/tracing-profiling`. Your ' +
|
|
'bundler might have a setting for aliasing both modules. Learn more at ' +
|
|
'http://fb.me/react-profiling',
|
|
);
|
|
}
|
|
|
|
export opaque type ReactPriorityLevel = 99 | 98 | 97 | 96 | 95 | 90;
|
|
export type SchedulerCallback = (isSync: boolean) => SchedulerCallback | null;
|
|
|
|
type SchedulerCallbackOptions = {
|
|
timeout?: number,
|
|
};
|
|
|
|
const fakeCallbackNode = {};
|
|
|
|
// Except for NoPriority, these correspond to Scheduler priorities. We use
|
|
// ascending numbers so we can compare them like numbers. They start at 90 to
|
|
// avoid clashing with Scheduler's priorities.
|
|
export const ImmediatePriority: ReactPriorityLevel = 99;
|
|
export const UserBlockingPriority: ReactPriorityLevel = 98;
|
|
export const NormalPriority: ReactPriorityLevel = 97;
|
|
export const LowPriority: ReactPriorityLevel = 96;
|
|
export const IdlePriority: ReactPriorityLevel = 95;
|
|
// NoPriority is the absence of priority. Also React-only.
|
|
export const NoPriority: ReactPriorityLevel = 90;
|
|
|
|
export const shouldYield = Scheduler_shouldYield;
|
|
|
|
let syncQueue: Array<SchedulerCallback> | null = null;
|
|
let immediateQueueCallbackNode: mixed | null = null;
|
|
let isFlushingSyncQueue: boolean = false;
|
|
let initialTimeMs: number = Scheduler_now();
|
|
|
|
// If the initial timestamp is reasonably small, use Scheduler's `now` directly.
|
|
// This will be the case for modern browsers that support `performance.now`. In
|
|
// older browsers, Scheduler falls back to `Date.now`, which returns a Unix
|
|
// timestamp. In that case, subtract the module initialization time to simulate
|
|
// the behavior of performance.now and keep our times small enough to fit
|
|
// within 32 bits.
|
|
// TODO: Consider lifting this into Scheduler.
|
|
export const now =
|
|
initialTimeMs < 10000 ? Scheduler_now : () => Scheduler_now() - initialTimeMs;
|
|
|
|
export function getCurrentPriorityLevel(): ReactPriorityLevel {
|
|
switch (Scheduler_getCurrentPriorityLevel()) {
|
|
case Scheduler_ImmediatePriority:
|
|
return ImmediatePriority;
|
|
case Scheduler_UserBlockingPriority:
|
|
return UserBlockingPriority;
|
|
case Scheduler_NormalPriority:
|
|
return NormalPriority;
|
|
case Scheduler_LowPriority:
|
|
return LowPriority;
|
|
case Scheduler_IdlePriority:
|
|
return IdlePriority;
|
|
default:
|
|
invariant(false, 'Unknown priority level.');
|
|
}
|
|
}
|
|
|
|
function reactPriorityToSchedulerPriority(reactPriorityLevel) {
|
|
switch (reactPriorityLevel) {
|
|
case ImmediatePriority:
|
|
return Scheduler_ImmediatePriority;
|
|
case UserBlockingPriority:
|
|
return Scheduler_UserBlockingPriority;
|
|
case NormalPriority:
|
|
return Scheduler_NormalPriority;
|
|
case LowPriority:
|
|
return Scheduler_LowPriority;
|
|
case IdlePriority:
|
|
return Scheduler_IdlePriority;
|
|
default:
|
|
invariant(false, 'Unknown priority level.');
|
|
}
|
|
}
|
|
|
|
export function runWithPriority<T>(
|
|
reactPriorityLevel: ReactPriorityLevel,
|
|
fn: () => T,
|
|
): T {
|
|
const priorityLevel = reactPriorityToSchedulerPriority(reactPriorityLevel);
|
|
return Scheduler_runWithPriority(priorityLevel, fn);
|
|
}
|
|
|
|
export function scheduleCallback(
|
|
reactPriorityLevel: ReactPriorityLevel,
|
|
callback: SchedulerCallback,
|
|
options: SchedulerCallbackOptions | void | null,
|
|
) {
|
|
const priorityLevel = reactPriorityToSchedulerPriority(reactPriorityLevel);
|
|
return Scheduler_scheduleCallback(priorityLevel, callback, options);
|
|
}
|
|
|
|
export function scheduleSyncCallback(callback: SchedulerCallback) {
|
|
// Push this callback into an internal queue. We'll flush these either in
|
|
// the next tick, or earlier if something calls `flushSyncCallbackQueue`.
|
|
if (syncQueue === null) {
|
|
syncQueue = [callback];
|
|
// Flush the queue in the next tick, at the earliest.
|
|
immediateQueueCallbackNode = Scheduler_scheduleCallback(
|
|
Scheduler_ImmediatePriority,
|
|
flushSyncCallbackQueueImpl,
|
|
);
|
|
} else {
|
|
// Push onto existing queue. Don't need to schedule a callback because
|
|
// we already scheduled one when we created the queue.
|
|
syncQueue.push(callback);
|
|
}
|
|
return fakeCallbackNode;
|
|
}
|
|
|
|
export function cancelCallback(callbackNode: mixed) {
|
|
if (callbackNode !== fakeCallbackNode) {
|
|
Scheduler_cancelCallback(callbackNode);
|
|
}
|
|
}
|
|
|
|
export function flushSyncCallbackQueue() {
|
|
if (immediateQueueCallbackNode !== null) {
|
|
Scheduler_cancelCallback(immediateQueueCallbackNode);
|
|
}
|
|
flushSyncCallbackQueueImpl();
|
|
}
|
|
|
|
function flushSyncCallbackQueueImpl() {
|
|
if (!isFlushingSyncQueue && syncQueue !== null) {
|
|
// Prevent re-entrancy.
|
|
isFlushingSyncQueue = true;
|
|
let i = 0;
|
|
try {
|
|
const isSync = true;
|
|
for (; i < syncQueue.length; i++) {
|
|
let callback = syncQueue[i];
|
|
do {
|
|
callback = callback(isSync);
|
|
} while (callback !== null);
|
|
}
|
|
syncQueue = null;
|
|
} catch (error) {
|
|
// If something throws, leave the remaining callbacks on the queue.
|
|
if (syncQueue !== null) {
|
|
syncQueue = syncQueue.slice(i + 1);
|
|
}
|
|
// Resume flushing in the next tick
|
|
Scheduler_scheduleCallback(
|
|
Scheduler_ImmediatePriority,
|
|
flushSyncCallbackQueue,
|
|
);
|
|
throw error;
|
|
} finally {
|
|
isFlushingSyncQueue = false;
|
|
}
|
|
}
|
|
}
|