UNPKG

lrt

Version:

Module to split long-running tasks into chunks with limited budget

202 lines (191 loc) 7.42 kB
'use strict'; 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;