Skip to content

Commit

Permalink
[scheduler] Priority levels, continuations, and wrapped callbacks (fa…
Browse files Browse the repository at this point in the history
…cebook#13720)

All of these features are based on features of React's internal
scheduler. The eventual goal is to lift as much as possible out of the
React internals into the Scheduler package.

Includes some renaming of existing methods.

- `scheduleWork` is now `scheduleCallback`
- `cancelScheduledWork` is now `cancelCallback`


Priority levels
---------------

Adds the ability to schedule callbacks at different priority levels.
The current levels are (final names TBD):

- Immediate priority. Fires at the end of the outermost currently
executing (similar to a microtask).
- Interactive priority. Fires within a few hundred milliseconds. This
should only be used to provide quick feedback to the user as a result
of an interaction.
- Normal priority. This is the default. Fires within several seconds.
- "Maybe" priority. Only fires if there's nothing else to do. Used for
prerendering or warming a cache.

The priority is changed using `runWithPriority`:

```js
runWithPriority(InteractivePriority, () => {
  scheduleCallback(callback);
});
```


Continuations
-------------

Adds the ability for a callback to yield without losing its place
in the queue, by returning a continuation. The continuation will have
the same expiration as the callback that yielded.


Wrapped callbacks
-----------------

Adds the ability to wrap a callback so that, when it is called, it
receives the priority of the current execution context.
  • Loading branch information
acdlite committed Oct 5, 2018
1 parent 553f0ee commit 23a959e
Show file tree
Hide file tree
Showing 15 changed files with 913 additions and 301 deletions.
154 changes: 78 additions & 76 deletions fixtures/scheduler/index.html
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
<!DOCTYPE html>
<html style="width: 100%; height: 100%;">
<head>
<meta charset="utf-8">
<title>Scheduler Test Page</title>

<head>
<meta charset="utf-8">
<title>Scheduler Test Page</title>
<style>
.correct {
border: solid green 2px;
Expand All @@ -11,91 +12,92 @@
border: dashed red 2px;
}
</style>
</head>
<body>
<h1>Scheduler Fixture</h1>
<p>
This fixture is for manual testing purposes, and the patterns used in
implementing it should not be used as a model. This is mainly for anyone
working on making changes to the `schedule` module.
</p>
<h2>Tests:</h2>
<ol>
<li>
<button onClick="runTestOne()">Run Test 1</button>
<p>Calls the callback within the frame when not blocked:</p>
<div><b>Expected:</b></div>
<div id="test-1-expected">
</div>
<div> -------------------------------------------------</div>
<div> If you see the same above and below it's correct.
</head>

<body>
<h1>Scheduler Fixture</h1>
<p>
This fixture is for manual testing purposes, and the patterns used in
implementing it should not be used as a model. This is mainly for anyone
working on making changes to the `schedule` module.
</p>
<h2>Tests:</h2>
<ol>
<li>
<button onClick="runTestOne()">Run Test 1</button>
<p>Calls the callback within the frame when not blocked:</p>
<div><b>Expected:</b></div>
<div id="test-1-expected">
</div>
<div> -------------------------------------------------</div>
<div> If you see the same above and below it's correct.
<div> -------------------------------------------------</div>
<div><b>Actual:</b></div>
<div id="test-1"></div>
</li>
<li>
<p>Accepts multiple callbacks and calls within frame when not blocked</p>
<button onClick="runTestTwo()">Run Test 2</button>
<div><b>Expected:</b></div>
<div id="test-2-expected">
</div>
<div> -------------------------------------------------</div>
<div> If you see the same above and below it's correct.
</li>
<li>
<p>Accepts multiple callbacks and calls within frame when not blocked</p>
<button onClick="runTestTwo()">Run Test 2</button>
<div><b>Expected:</b></div>
<div id="test-2-expected">
</div>
<div> -------------------------------------------------</div>
<div> If you see the same above and below it's correct.
<div> -------------------------------------------------</div>
<div><b>Actual:</b></div>
<div id="test-2"></div>
</li>
<li>
<p>Schedules callbacks in correct order when they use scheduleWork to schedule themselves</p>
<button onClick="runTestThree()">Run Test 3</button>
<div><b>Expected:</b></div>
<div id="test-3-expected">
</div>
<div> -------------------------------------------------</div>
<div> If you see the same above and below it's correct.
</li>
<li>
<p>Schedules callbacks in correct order when they use scheduleWork to schedule themselves</p>
<button onClick="runTestThree()">Run Test 3</button>
<div><b>Expected:</b></div>
<div id="test-3-expected">
</div>
<div> -------------------------------------------------</div>
<div> If you see the same above and below it's correct.
<div> -------------------------------------------------</div>
<div><b>Actual:</b></div>
<div id="test-3"></div>
</li>
<li>
<p>Calls timed out callbacks and then any more pending callbacks, defers others if time runs out</p>
<button onClick="runTestFour()">Run Test 4</button>
<div><b>Expected:</b></div>
<div id="test-4-expected">
</div>
<div> -------------------------------------------------</div>
<div> If you see the same above and below it's correct.
</li>
<li>
<p>Calls timed out callbacks and then any more pending callbacks, defers others if time runs out</p>
<button onClick="runTestFour()">Run Test 4</button>
<div><b>Expected:</b></div>
<div id="test-4-expected">
</div>
<div> -------------------------------------------------</div>
<div> If you see the same above and below it's correct.
<div> -------------------------------------------------</div>
<div><b>Actual:</b></div>
<div id="test-4"></div>
</li>
<li>
<p>When some callbacks throw errors, still calls them all within the same frame</p>
<p><b>IMPORTANT:</b> Open the console when you run this! Inspect the logs there!</p>
<button onClick="runTestFive()">Run Test 5</button>
</li>
<li>
<p>When some callbacks throw errors <b> and some also time out</b>, still calls them all within the same frame</p>
<p><b>IMPORTANT:</b> Open the console when you run this! Inspect the logs there!</p>
<button onClick="runTestSix()">Run Test 6</button>
</li>
<li>
<p>Continues calling callbacks even when user switches away from this tab</p>
<button onClick="runTestSeven()">Run Test 7</button>
<div><b>Click the button above, observe the counter, then switch to
another tab and switch back:</b></div>
<div id="test-7">
</div>
<div> If the counter advanced while you were away from this tab, it's correct.</div>
</li>
</ol>
<script src="../../build/dist/react.development.js"></script>
<script src="../../build/node_modules/scheduler/umd/scheduler.development.js"></script>
<script src="https://unpkg.com/babel-standalone@6/babel.js"></script>
<script type="text/babel">
</li>
<li>
<p>When some callbacks throw errors, still calls them all within the same frame</p>
<p><b>IMPORTANT:</b> Open the console when you run this! Inspect the logs there!</p>
<button onClick="runTestFive()">Run Test 5</button>
</li>
<li>
<p>When some callbacks throw errors <b> and some also time out</b>, still calls them all within the same frame</p>
<p><b>IMPORTANT:</b> Open the console when you run this! Inspect the logs there!</p>
<button onClick="runTestSix()">Run Test 6</button>
</li>
<li>
<p>Continues calling callbacks even when user switches away from this tab</p>
<button onClick="runTestSeven()">Run Test 7</button>
<div><b>Click the button above, observe the counter, then switch to
another tab and switch back:</b></div>
<div id="test-7">
</div>
<div> If the counter advanced while you were away from this tab, it's correct.</div>
</li>
</ol>
<script src="../../build/dist/react.development.js"></script>
<script src="../../build/node_modules/scheduler/umd/scheduler.development.js"></script>
<script src="https://unpkg.com/babel-standalone@6/babel.js"></script>
<script type="text/babel">
const {
unstable_scheduleWork: scheduleWork,
unstable_cancelWork: cancelWork,
unstable_scheduleCallback: scheduleCallback,
unstable_cancelCallback: cancelCallback,
unstable_now: now
} = Scheduler;
function displayTestResult(testNumber) {
Expand Down Expand Up @@ -496,4 +498,4 @@ <h2>Tests:</h2>
}
</script type="text/babel">
</body>
</html>
</html>
4 changes: 2 additions & 2 deletions fixtures/tracing/script.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,8 @@ function checkSchedulerAPI() {
if (
typeof Scheduler === 'undefined' ||
typeof Scheduler.unstable_now !== 'function' ||
typeof Scheduler.unstable_scheduleWork !== 'function' ||
typeof Scheduler.unstable_cancelScheduledWork !== 'function'
typeof Scheduler.unstable_scheduleCallback !== 'function' ||
typeof Scheduler.unstable_cancelCallback !== 'function'
) {
throw 'API is not defined';
}
Expand Down
4 changes: 2 additions & 2 deletions fixtures/unstable-async/suspense/src/components/App.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React, {Placeholder, PureComponent} from 'react';
import {unstable_scheduleWork} from 'scheduler';
import {unstable_scheduleCallback} from 'scheduler';
import {
unstable_trace as trace,
unstable_wrap as wrap,
Expand Down Expand Up @@ -38,7 +38,7 @@ export default class App extends PureComponent {
currentId: id,
})
);
unstable_scheduleWork(
unstable_scheduleCallback(
wrap(() =>
trace(`View ${id} (low-pri)`, performance.now(), () =>
this.setState({
Expand Down
6 changes: 3 additions & 3 deletions fixtures/unstable-async/time-slicing/src/index.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React, {PureComponent} from 'react';
import {flushSync, render} from 'react-dom';
import {unstable_scheduleWork} from 'scheduler';
import {unstable_scheduleCallback} from 'scheduler';
import _ from 'lodash';
import Charts from './Charts';
import Clock from './Clock';
Expand Down Expand Up @@ -67,7 +67,7 @@ class App extends PureComponent {
}
this._ignoreClick = true;

unstable_scheduleWork(() => {
unstable_scheduleCallback(() => {
this.setState({showDemo: true}, () => {
this._ignoreClick = false;
});
Expand Down Expand Up @@ -107,7 +107,7 @@ class App extends PureComponent {
this.debouncedHandleChange(value);
break;
case 'async':
unstable_scheduleWork(() => {
unstable_scheduleCallback(() => {
this.setState({value});
});
break;
Expand Down
4 changes: 2 additions & 2 deletions packages/react-art/src/ReactARTHostConfig.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@

export {
unstable_now as now,
unstable_scheduleWork as scheduleDeferredCallback,
unstable_cancelScheduledWork as cancelDeferredCallback,
unstable_scheduleCallback as scheduleDeferredCallback,
unstable_cancelCallback as cancelDeferredCallback,
} from 'scheduler';
import Transform from 'art/core/transform';
import Mode from 'art/modes/current';
Expand Down
4 changes: 2 additions & 2 deletions packages/react-dom/src/client/ReactDOMHostConfig.js
Original file line number Diff line number Diff line change
Expand Up @@ -62,8 +62,8 @@ export type NoTimeout = -1;

export {
unstable_now as now,
unstable_scheduleWork as scheduleDeferredCallback,
unstable_cancelScheduledWork as cancelDeferredCallback,
unstable_scheduleCallback as scheduleDeferredCallback,
unstable_cancelCallback as cancelDeferredCallback,
} from 'scheduler';

let SUPPRESS_HYDRATION_WARNING;
Expand Down
14 changes: 10 additions & 4 deletions packages/react/src/ReactSharedInternals.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,12 @@

import assign from 'object-assign';
import {
unstable_cancelScheduledWork,
unstable_cancelCallback,
unstable_now,
unstable_scheduleWork,
unstable_scheduleCallback,
unstable_runWithPriority,
unstable_wrapCallback,
unstable_getCurrentPriorityLevel,
} from 'scheduler';
import {
__interactionsRef,
Expand Down Expand Up @@ -39,9 +42,12 @@ if (__UMD__) {
// CJS bundles use the shared NPM package.
Object.assign(ReactSharedInternals, {
Scheduler: {
unstable_cancelScheduledWork,
unstable_cancelCallback,
unstable_now,
unstable_scheduleWork,
unstable_scheduleCallback,
unstable_runWithPriority,
unstable_wrapCallback,
unstable_getCurrentPriorityLevel,
},
SchedulerTracing: {
__interactionsRef,
Expand Down
38 changes: 32 additions & 6 deletions packages/scheduler/npm/umd/scheduler.development.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
* LICENSE file in the root directory of this source tree.
*/

/* eslint-disable max-len */

'use strict';

(function(global, factory) {
Expand All @@ -23,23 +25,47 @@
);
}

function unstable_scheduleWork() {
return global.React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.Scheduler.unstable_scheduleWork.apply(
function unstable_scheduleCallback() {
return global.React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.Scheduler.unstable_scheduleCallback.apply(
this,
arguments
);
}

function unstable_cancelCallback() {
return global.React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.Scheduler.unstable_cancelCallback.apply(
this,
arguments
);
}

function unstable_runWithPriority() {
return global.React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.Scheduler.unstable_runWithPriority.apply(
this,
arguments
);
}

function unstable_wrapCallback() {
return global.React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.Scheduler.unstable_wrapCallback.apply(
this,
arguments
);
}

function unstable_cancelScheduledWork() {
return global.React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.Scheduler.unstable_cancelScheduledWork.apply(
function unstable_getCurrentPriorityLevel() {
return global.React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.Scheduler.unstable_getCurrentPriorityLevel.apply(
this,
arguments
);
}

return Object.freeze({
unstable_now: unstable_now,
unstable_scheduleWork: unstable_scheduleWork,
unstable_cancelScheduledWork: unstable_cancelScheduledWork,
unstable_scheduleCallback: unstable_scheduleCallback,
unstable_cancelCallback: unstable_cancelCallback,
unstable_runWithPriority: unstable_runWithPriority,
unstable_wrapCallback: unstable_wrapCallback,
unstable_getCurrentPriorityLevel: unstable_getCurrentPriorityLevel,
});
});
Loading

0 comments on commit 23a959e

Please sign in to comment.