lrt
Version:
Module to split long-running tasks into chunks with limited budget
202 lines (191 loc) • 7.42 kB
JavaScript
Object.defineProperty(exports, '__esModule', { value: true });
var animationFrameChunkScheduler = typeof window !== 'undefined' &&
typeof window.requestAnimationFrame === 'function' &&
typeof window.cancelAnimationFrame === 'function' ?
{
request: function (fn) { return window.requestAnimationFrame(fn); },
cancel: function (token) { return window.cancelAnimationFrame(token); }
} :
null;
var idleCallbackChunkScheduler = typeof window !== 'undefined' &&
typeof window.requestIdleCallback === 'function' &&
typeof window.cancelIdleCallback === 'function' ?
{
request: function (fn) { return window.requestIdleCallback(fn); },
cancel: function (token) { return window.cancelIdleCallback(token); }
} :
null;
var immediateChunkScheduler = typeof setImmediate === 'function' &&
typeof clearImmediate === 'function' ?
{
request: function (fn) { return setImmediate(fn); },
cancel: function (token) { return clearImmediate(token); }
} :
null;
var postMessageScheduler = typeof window === 'undefined' ?
null :
(function () {
var msg = '__lrt__' + Math.random();
var fns = [];
window.addEventListener('message', function (e) {
if (e.data === msg) {
var fnsToCall = fns;
fns = [];
for (var i = 0; i < fnsToCall.length; i++) {
fnsToCall[i]();
}
}
}, true);
return {
request: function (fn) {
fns.push(fn);
if (fns.length === 1) {
window.postMessage(msg, '*');
}
}
};
})();
var timeoutChunkScheduler = {
request: function (fn) { return setTimeout(fn, 0); },
cancel: function (token) { return clearTimeout(token); }
};
var BUILTIN_CHUNK_SHEDULERS = {
auto: idleCallbackChunkScheduler ||
animationFrameChunkScheduler ||
immediateChunkScheduler ||
postMessageScheduler,
animationFrame: animationFrameChunkScheduler,
idleCallback: idleCallbackChunkScheduler,
immediate: immediateChunkScheduler,
postMessage: postMessageScheduler,
timeout: timeoutChunkScheduler
};
function getChunkScheduler(type) {
if (typeof type === 'string') {
return BUILTIN_CHUNK_SHEDULERS[type] || timeoutChunkScheduler;
}
if (Array.isArray(type)) {
for (var i = 0; i < type.length; i++) {
var item = type[i];
if (typeof item === 'string') {
var chunkScheduler = BUILTIN_CHUNK_SHEDULERS[item];
if (chunkScheduler) {
return chunkScheduler;
}
}
else {
return item;
}
}
return timeoutChunkScheduler;
}
return type;
}
var now = typeof performance === 'object' && typeof performance.now === 'function' ?
function () { return performance.now(); } :
(function () {
return Date.now;
})();
var microtaskPromise = Promise.resolve();
function microtask(fn) {
microtaskPromise.then(fn);
}
var DEFAULT_CHUNK_SCHEDULER_TYPE = 'auto';
var DEFAULT_CHUNK_BUDGET = 10;
function createScheduler(_a) {
var _b = _a === void 0 ? {} : _a, _c = _b.chunkScheduler, chunkSchedulerType = _c === void 0 ? DEFAULT_CHUNK_SCHEDULER_TYPE : _c, _d = _b.chunkBudget, chunkBudget = _d === void 0 ? DEFAULT_CHUNK_BUDGET : _d;
var pendingTasks = new Map();
var chunkScheduler = getChunkScheduler(chunkSchedulerType);
var chunkSchedulerToken = null;
var tasksOrder = [];
function chunk() {
chunkSchedulerToken = null;
var iterationStartTime = now();
var checkBudget = false;
var restChunkBudget = chunkBudget;
var nextTasksOrder = [];
while (tasksOrder.length > 0) {
var taskPromise = tasksOrder.shift();
var task = pendingTasks.get(taskPromise);
var iterated = false;
if (checkBudget && restChunkBudget < task.meanIterationElapsedTime) {
nextTasksOrder.push(taskPromise);
}
else {
checkBudget = true;
try {
var _a = task.iterator.next(task.value), value = _a.value, done = _a.done;
iterated = true;
task.value = value;
if (done) {
pendingTasks.delete(taskPromise);
task.resolve(value);
}
else {
tasksOrder.push(taskPromise);
}
}
catch (err) {
pendingTasks.delete(taskPromise);
task.reject(err);
}
}
var iterationElapsedTime = now() - iterationStartTime;
if (iterated) {
task.iterationCount++;
task.totalElapsedTime += iterationElapsedTime;
task.meanIterationElapsedTime = task.totalElapsedTime / task.iterationCount;
}
restChunkBudget -= iterationElapsedTime;
iterationStartTime += iterationElapsedTime;
}
if (nextTasksOrder.length > 0) {
tasksOrder = nextTasksOrder;
chunkSchedulerToken = chunkScheduler.request(chunk);
}
}
return {
runTask: function (taskIterator) {
var task;
var taskPromise = new Promise(function (resolve, reject) {
task = {
value: undefined,
iterator: taskIterator,
iterationCount: 0,
meanIterationElapsedTime: 0,
totalElapsedTime: 0,
resolve: resolve,
reject: reject
};
});
pendingTasks.set(taskPromise, task);
microtask(function () {
// check if it's not already aborted
if (!pendingTasks.has(taskPromise)) {
return;
}
tasksOrder.push(taskPromise);
if (tasksOrder.length === 1) {
chunkSchedulerToken = chunkScheduler.request(chunk);
}
});
return taskPromise;
},
abortTask: function (taskPromise) {
if (pendingTasks.delete(taskPromise)) {
var taskOrderIdx = tasksOrder.indexOf(taskPromise);
// task can be absent if it's added to pending tasks via `runTask` but then
// `abortTask` is called synchronously before invoking microtask callback
if (taskOrderIdx > -1) {
tasksOrder.splice(taskOrderIdx, 1);
if (tasksOrder.length === 0 && chunkScheduler.cancel && chunkSchedulerToken !== null) {
chunkScheduler.cancel(chunkSchedulerToken);
chunkSchedulerToken = null;
}
}
}
}
};
}
exports.createScheduler = createScheduler;
;