main-thread-scheduling
Advanced tools
Comparing version 6.0.0-0 to 6.0.0
{ | ||
"name": "main-thread-scheduling", | ||
"version": "6.0.0-0", | ||
"description": "Consistently responsive apps while staying on the main thread", | ||
"version": "6.0.0", | ||
"description": "Faster apps using a simple API instead of Web Workers", | ||
"license": "MIT", | ||
@@ -17,2 +17,4 @@ "repository": "astoilkov/main-thread-scheduling", | ||
"background", | ||
"user-visible", | ||
"task", | ||
"idle", | ||
@@ -26,2 +28,5 @@ "yield", | ||
"scheduling", | ||
"scheduler", | ||
"isInputPending", | ||
"MessageChannel", | ||
"main-thread", | ||
@@ -38,3 +43,3 @@ "event loop", | ||
"lint": "eslint --cache --format=pretty --ext=.ts ./", | ||
"test": "yarn run build && yarn run lint && [[ -z $CI ]] && jest --coverage --coverageReporters=text || jest --coverage", | ||
"test": "yarn run build && yarn run lint && if [[ -z $CI ]]; then jest --coverage --coverageReporters=text; else jest --coverage; fi", | ||
"release": "yarn run build && np", | ||
@@ -41,0 +46,0 @@ "prettier": "prettier --write --config .prettierrc.yaml {*.ts,**/*.ts,*.json,**.json}" |
@@ -11,3 +11,3 @@ <br> | ||
<p align="center"> | ||
Consistently responsive apps while staying on the main thread | ||
Faster apps using a simple API instead of Web Workers | ||
</p> | ||
@@ -17,3 +17,3 @@ | ||
<a href="https://bundlephobia.com/result?p=main-thread-scheduling"> | ||
<img src="https://badgen.net/bundlephobia/minzip/main-thread-scheduling" alt="Gzipped Size" /> | ||
<img src="https://img.shields.io/bundlephobia/minzip/main-thread-scheduling" alt="Gzipped Size" /> | ||
</a> | ||
@@ -62,4 +62,4 @@ <a href="https://codeclimate.com/github/astoilkov/main-thread-scheduling/test_coverage"> | ||
- **Not a weekend project.** I've already been using it for over a year in the core of two of my products — [Nota](https://nota.md) and [iBar](https://ibar.app). | ||
- **This is the future.** Browsers are probably going to support scheduling tasks on the main thread in the future. Here is the [spec](https://github.com/WICG/scheduling-apis). This library will still be relevant in the future because it provides an easier API. | ||
- **Simple.** 90% of the time you only need `yieldOrContinue(priority)` function. The API has two more functions for more advanced cases. | ||
- **This is the future.** Browsers are probably going to support scheduling tasks on the main thread in the future. Here is the [spec](https://github.com/WICG/scheduling-apis). This library will still be relevant in the future — [explanation](#scheduler-yield-alternative). | ||
- **Simple.** 90% of the time you only need the `yieldOrContinue(priority)` function. The API has two more functions for more advanced cases. | ||
- **High quality.** Aiming for high-quality with [my open-source principles](https://astoilkov.com/my-open-source-principles). | ||
@@ -121,6 +121,8 @@ | ||
**Web Workers** are a great alternative if you have: 1) heavy code (e.g. image processing), 2) something that isn't a task but a process (runs through a big time of the app lifecycle). However, in reality, it's rare to see people using them. That's because they require significant investment of time due to the complexity that can't be avoided when working with CPU threads regardless of the programming language. This library can be used as a gateway before transitioning to Web Workers. In reality, a lot of the times, you would discover the doing it on the main thread is good enough. | ||
**Web Workers** are a great fit if you have: 1) heavy algorithm (e.g. image processing), 2) heavy process (runs for a long time, big part of the app lifecycle). However, in reality, it's rare to see people using them. That's because they require significant investment of time due to the complexity that can't be avoided when working with CPU threads regardless of the programming language. This library can be used as a gateway before transitioning to Web Workers. In reality, a lot of the times, you would discover the doing it on the main thread is good enough. | ||
<div id="scheduler-yield-alternative"></div> | ||
**[`scheduler.yield()`](https://github.com/WICG/scheduling-apis/blob/main/explainers/yield-and-continuation.md)** will probably land in browsers at some point. However, is `scheduler.yield()` enough? The spec isn't very clear on how it will work exactly so I'm not sure. My guess is that it would be possible go without this library but you will need extra code to do so. That's because you will need to reimplement the `isTimeToYield()` method for which I don't see an alternative in the [spec](https://github.com/WICG/scheduling-apis). | ||
**[React scheduler](https://github.com/facebook/react/blob/main/packages/scheduler/README.md)** is a similar implementation. They plan to make it more generic (for use outside of React) but there doesn't seem to be a public roadmap for that. |
@@ -1,3 +0,2 @@ | ||
import { getLastIdleDeadline } from './idleFrameTracking'; | ||
import { getPerFrameScheduleStartTime } from './animationFrameTracking'; | ||
import state from './state'; | ||
// #performance | ||
@@ -16,3 +15,3 @@ // calling `isTimeToYield()` thousand of times is slow. `lastCall` helps to run logic inside of | ||
const now = Date.now(); | ||
if (now - lastCallTime === 0) { | ||
if (!lastResult && now - lastCallTime === 0) { | ||
return lastResult; | ||
@@ -23,7 +22,9 @@ } | ||
now >= calculateDeadline(priority) || ((_b = (_a = navigator.scheduling) === null || _a === void 0 ? void 0 : _a.isInputPending) === null || _b === void 0 ? void 0 : _b.call(_a)) === true; | ||
if (lastResult) { | ||
state.frameTimeElapsed = true; | ||
} | ||
return lastResult; | ||
} | ||
function calculateDeadline(priority) { | ||
const perFrameScheduleStartTime = getPerFrameScheduleStartTime(); | ||
if (perFrameScheduleStartTime === undefined) { | ||
if (state.frameWorkStartTime === undefined) { | ||
// silentError() | ||
@@ -35,10 +36,9 @@ return -1; | ||
// Math.round(100 - (1000/60)) = Math.round(83,333) = 83 | ||
return perFrameScheduleStartTime + 83; | ||
return state.frameWorkStartTime + 83; | ||
} | ||
case 'background': { | ||
const lastIdleDeadline = getLastIdleDeadline(); | ||
const idleDeadline = lastIdleDeadline === undefined | ||
const idleDeadline = state.idleDeadline === undefined | ||
? Number.MAX_SAFE_INTEGER | ||
: Date.now() + lastIdleDeadline.timeRemaining(); | ||
return Math.min(perFrameScheduleStartTime + 5, idleDeadline); | ||
: Date.now() + state.idleDeadline.timeRemaining(); | ||
return Math.min(state.frameWorkStartTime + 5, idleDeadline); | ||
} | ||
@@ -45,0 +45,0 @@ // istanbul ignore next |
@@ -1,2 +0,1 @@ | ||
import requestLaterMicrotask from './requestLaterMicrotask'; | ||
let globalId = 0; | ||
@@ -12,7 +11,9 @@ const running = new Set(); | ||
running.add(id); | ||
requestLaterMicrotask(() => { | ||
if (running.has(id)) { | ||
callback(); | ||
running.delete(id); | ||
} | ||
queueMicrotask(() => { | ||
queueMicrotask(() => { | ||
if (running.has(id)) { | ||
callback(); | ||
running.delete(id); | ||
} | ||
}); | ||
}); | ||
@@ -19,0 +20,0 @@ globalId += 1; |
@@ -1,7 +0,6 @@ | ||
import nextTask from './nextTask'; | ||
import state from './state'; | ||
import isTimeToYield from './isTimeToYield'; | ||
import requestLaterMicrotask from './requestLaterMicrotask'; | ||
import { notifyScheduleComplete } from './animationFrameTracking'; | ||
import requestNextTask from './requestNextTask'; | ||
import { createTask, nextTask, removeTask } from './tasks'; | ||
import { cancelPromiseEscape, requestPromiseEscape } from './promiseEscape'; | ||
import { createDeferred, isDeferredLast, nextDeferred, removeDeferred } from './deferred'; | ||
let promiseEscapeId; | ||
@@ -20,6 +19,6 @@ /** | ||
cancelPromiseEscape(promiseEscapeId); | ||
const deferred = createDeferred(priority); | ||
const task = createTask(priority); | ||
await schedule(priority); | ||
if (!isDeferredLast(deferred)) { | ||
await deferred.ready; | ||
if (state.tasks[0] !== task) { | ||
await task.ready; | ||
if (isTimeToYield(priority)) { | ||
@@ -29,31 +28,31 @@ await schedule(priority); | ||
} | ||
removeDeferred(deferred); | ||
removeTask(task); | ||
cancelPromiseEscape(promiseEscapeId); | ||
promiseEscapeId = requestPromiseEscape(() => { | ||
nextDeferred(); | ||
nextTask(); | ||
}); | ||
} | ||
async function schedule(priority) { | ||
var _a, _b; | ||
if (state.frameTimeElapsed) { | ||
await state.onAnimationFrame.promise; | ||
} | ||
if (priority === 'user-visible' || typeof requestIdleCallback === 'undefined') { | ||
await waitCallback(requestLaterMicrotask); | ||
while (true) { | ||
await waitCallback(nextTask); | ||
notifyScheduleComplete(); | ||
if (!isTimeToYield(priority)) { | ||
break; | ||
} | ||
await new Promise((resolve) => requestNextTask(resolve)); | ||
// istanbul ignore if | ||
if (((_b = (_a = navigator.scheduling) === null || _a === void 0 ? void 0 : _a.isInputPending) === null || _b === void 0 ? void 0 : _b.call(_a)) === true) { | ||
await schedule(priority); | ||
} | ||
else if (state.frameWorkStartTime === undefined) { | ||
state.frameWorkStartTime = Date.now(); | ||
} | ||
} | ||
else { | ||
await waitCallback(requestLaterMicrotask); | ||
await waitCallback(requestIdleCallback); | ||
notifyScheduleComplete(); | ||
await state.onIdleCallback.promise; | ||
// not checking for `navigator.scheduling?.isInputPending?.()` here because idle callbacks | ||
// ensure no input is pending | ||
if (state.frameWorkStartTime === undefined) { | ||
state.frameWorkStartTime = Date.now(); | ||
} | ||
} | ||
} | ||
async function waitCallback(callback) { | ||
return await new Promise((resolve) => { | ||
callback(() => { | ||
resolve(); | ||
}); | ||
}); | ||
} |
@@ -0,3 +1,3 @@ | ||
import yieldControl from './yieldControl'; | ||
import isTimeToYield from './isTimeToYield'; | ||
import yieldControl from './yieldControl'; | ||
// disabling ESLint otherwise `requestPromiseEscape()` in `yieldControl()` won't work | ||
@@ -4,0 +4,0 @@ // eslint-disable-next-line @typescript-eslint/promise-function-async |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Found 1 instance in 1 package
26
0
125
22114
349