slackgmailskypefacebook-workplaceoutlookemailmicrosoft-teamsdiscordmessengercustom-servicesmacoslinuxwindowsinboxwhatsappicloudtweetdeckhipchattelegramhangouts
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
474 lines
15 KiB
474 lines
15 KiB
// @tag core |
|
/** |
|
* Provides the ability to execute one or more arbitrary tasks in an asynchronous manner. |
|
* |
|
* Generally, you can use the singleton {@link Ext.TaskManager}. Or you can create |
|
* separate TaskRunner instances to start and stop unique tasks independent of one |
|
* another. |
|
* |
|
* Example usage: |
|
* |
|
* @example |
|
* var runner = new Ext.util.TaskRunner(), |
|
* clock, updateClock, task; |
|
* |
|
* clock = Ext.getBody().appendChild({ |
|
* id: 'clock' |
|
* }); |
|
* |
|
* // Start a simple clock task that updates a div once per second |
|
* updateClock = function() { |
|
* clock.setHtml(Ext.Date.format(new Date(), 'g:i:s A')); |
|
* }; |
|
* |
|
* task = runner.start({ |
|
* run: updateClock, |
|
* interval: 1000 |
|
* }); |
|
* |
|
* The equivalent using TaskManager: |
|
* |
|
* @example |
|
* var clock, updateClock, task; |
|
* |
|
* clock = Ext.getBody().appendChild({ |
|
* id: 'clock' |
|
* }); |
|
* |
|
* // Start a simple clock task that updates a div once per second |
|
* updateClock = function() { |
|
* clock.setHtml(Ext.Date.format(new Date(), 'g:i:s A')); |
|
* }; |
|
* |
|
* var task = Ext.TaskManager.start({ |
|
* run: updateClock, |
|
* interval: 1000 |
|
* }); |
|
* |
|
* To end a running task: |
|
* |
|
* task.destroy(); |
|
* |
|
* If a task needs to be started and stopped repeated over time, you can create a |
|
* {@link Ext.util.TaskRunner.Task Task} instance. |
|
* |
|
* var runner = new Ext.util.TaskRunner(), |
|
* task; |
|
* |
|
* task = runner.newTask({ |
|
* run: function() { |
|
* // useful code |
|
* }, |
|
* interval: 1000 |
|
* }); |
|
* |
|
* task.start(); |
|
* |
|
* // ... |
|
* |
|
* task.stop(); |
|
* |
|
* // ... |
|
* |
|
* task.start(); |
|
* |
|
* A re-usable, single-run task can be managed similar to the above: |
|
* |
|
* var runner = new Ext.util.TaskRunner(), |
|
* task; |
|
* |
|
* task = runner.newTask({ |
|
* run: function() { |
|
* // useful code |
|
* }, |
|
* interval: 1000, |
|
* repeat: 1 |
|
* }); |
|
* |
|
* task.start(); |
|
* |
|
* // ... |
|
* |
|
* task.stop(); |
|
* |
|
* // ... |
|
* |
|
* task.start(); |
|
* |
|
* See the {@link #start} method for details about how to configure a Task. |
|
* |
|
* Also see {@link Ext.util.DelayedTask}. |
|
* |
|
* @constructor |
|
* @param {Number/Object} [interval=10] The minimum precision in milliseconds supported by |
|
* this TaskRunner instance. Alternatively, a config object to apply to the new instance. |
|
*/ |
|
Ext.define('Ext.util.TaskRunner', { |
|
// @require Ext.Function |
|
|
|
/** |
|
* @cfg {Boolean} [fireIdleEvent=true] |
|
* This may be configured `false` to inhibit firing of the {@link |
|
* Ext.GlobalEvents#idle idle event} after task invocation. |
|
*/ |
|
|
|
/** |
|
* @cfg {Number} interval |
|
* How often to run the task in milliseconds. Defaults to every 10ms. |
|
*/ |
|
interval: 10, |
|
|
|
/** |
|
* @property timerId |
|
* The id of the current timer. |
|
* @private |
|
*/ |
|
timerId: null, |
|
|
|
constructor: function (interval) { |
|
var me = this; |
|
|
|
if (typeof interval == 'number') { |
|
me.interval = interval; |
|
} else if (interval) { |
|
Ext.apply(me, interval); |
|
} |
|
|
|
me.tasks = []; |
|
me.timerFn = Ext.Function.bind(me.onTick, me); |
|
}, |
|
|
|
/** |
|
* Creates a new {@link Ext.util.TaskRunner.Task Task} instance. These instances can |
|
* be easily started and stopped. |
|
* @param {Object} config The config object. For details on the supported properties, |
|
* see {@link #start}. |
|
* |
|
* @return {Ext.util.TaskRunner.Task} |
|
* Ext.util.TaskRunner.Task instance, which can be useful for method chaining. |
|
*/ |
|
newTask: function (config) { |
|
var task = new Ext.util.TaskRunner.Task(config); |
|
task.manager = this; |
|
return task; |
|
}, |
|
|
|
/** |
|
* Starts a new task. |
|
* |
|
* Before each invocation, Ext injects the property `taskRunCount` into the task object |
|
* so that calculations based on the repeat count can be performed. |
|
* |
|
* The returned task will contain a `destroy` method that can be used to destroy the |
|
* task and cancel further calls. This is equivalent to the {@link #stop} method. |
|
* |
|
* @param {Object} task A config object that supports the following properties: |
|
* @param {Function} task.run The function to execute each time the task is invoked. The |
|
* function will be called at each interval and passed the `args` argument if specified, |
|
* and the current invocation count if not. |
|
* |
|
* If a particular scope (`this` reference) is required, be sure to specify it using |
|
* the `scope` argument. |
|
* |
|
* @param {Function} task.onError The function to execute in case of unhandled |
|
* error on task.run. |
|
* |
|
* @param {Boolean} task.run.return `false` from this function to terminate the task. |
|
* |
|
* @param {Number} task.interval The frequency in milliseconds with which the task |
|
* should be invoked. |
|
* |
|
* @param {Object[]} [task.args] An array of arguments to be passed to the function |
|
* specified by `run`. If not specified, the current invocation count is passed. |
|
* |
|
* @param {Object} [task.scope] The scope (`this` reference) in which to execute the |
|
* `run` function. Defaults to the task config object. |
|
* |
|
* @param {Number} [task.duration] The length of time in milliseconds to invoke the task |
|
* before stopping automatically (defaults to indefinite). |
|
* |
|
* @param {Number} [task.repeat] The number of times to invoke the task before stopping |
|
* automatically (defaults to indefinite). |
|
* |
|
* @param {Number} [task.fireIdleEvent=true] If all tasks in a TaskRunner's execution |
|
* sweep are configured with `fireIdleEvent: false`, then the |
|
* {@link Ext.GlobalEvents#idle idleEvent} is not fired when the TaskRunner's execution |
|
* sweep finishes. |
|
* |
|
* @param {Boolean} [task.fireOnStart=false] True to run the task immediately instead of |
|
* waiting for the _interval's_ initial pass to call the _run_ function. |
|
*/ |
|
start: function(task) { |
|
var me = this, |
|
now = Ext.Date.now(); |
|
|
|
if (!task.pending) { |
|
me.tasks.push(task); |
|
task.pending = true; // don't allow the task to be added to me.tasks again |
|
} |
|
|
|
task.stopped = false; // might have been previously stopped... |
|
task.taskStartTime = now; |
|
task.taskRunTime = task.fireOnStart !== false ? 0 : task.taskStartTime; |
|
task.taskRunCount = 0; |
|
|
|
if (!me.firing) { |
|
if (task.fireOnStart !== false) { |
|
me.startTimer(0, now); |
|
} else { |
|
me.startTimer(task.interval, now); |
|
} |
|
} |
|
|
|
return task; |
|
}, |
|
|
|
/** |
|
* Stops an existing running task. |
|
* @param {Object} task The task to stop |
|
* @return {Object} The task |
|
*/ |
|
stop: function(task) { |
|
// NOTE: we don't attempt to remove the task from me.tasks at this point because |
|
// this could be called from inside a task which would then corrupt the state of |
|
// the loop in onTick |
|
if (!task.stopped) { |
|
task.stopped = true; |
|
|
|
if (task.onStop) { |
|
task.onStop.call(task.scope || task, task); |
|
} |
|
} |
|
|
|
return task; |
|
}, |
|
|
|
/** |
|
* Stops all tasks that are currently running. |
|
*/ |
|
stopAll: function() { |
|
// onTick will take care of cleaning up the mess after this point... |
|
Ext.each(this.tasks, this.stop, this); |
|
}, |
|
|
|
//------------------------------------------------------------------------- |
|
|
|
firing: false, |
|
|
|
nextExpires: 1e99, |
|
|
|
// private |
|
onTick: function () { |
|
var me = this, |
|
tasks = me.tasks, |
|
now = Ext.Date.now(), |
|
nextExpires = 1e99, |
|
len = tasks.length, |
|
globalEvents = Ext.GlobalEvents, |
|
expires, newTasks, i, task, rt, remove, |
|
fireIdleEvent; |
|
|
|
me.timerId = null; |
|
me.firing = true; // ensure we don't startTimer during this loop... |
|
|
|
// tasks.length can be > len if start is called during a task.run call... so we |
|
// first check len to avoid tasks.length reference but eventually we need to also |
|
// check tasks.length. we avoid repeating use of tasks.length by setting len at |
|
// that time (to help the next loop) |
|
for (i = 0; i < len || i < (len = tasks.length); ++i) { |
|
task = tasks[i]; |
|
|
|
if (!(remove = task.stopped)) { |
|
expires = task.taskRunTime + task.interval; |
|
|
|
if (expires <= now) { |
|
rt = 1; // otherwise we have a stale "rt" |
|
|
|
// If all tasks left specify fireIdleEvent as false, then don't do it |
|
if (task.hasOwnProperty('fireIdleEvent')) { |
|
fireIdleEvent = task.fireIdleEvent; |
|
} else { |
|
fireIdleEvent = me.fireIdleEvent; |
|
} |
|
|
|
try { |
|
rt = task.run.apply(task.scope || task, task.args || [++task.taskRunCount]); |
|
} catch (taskError) { |
|
try { |
|
// <debug> |
|
Ext.log({ |
|
fn: task.run, |
|
prefix: 'Error while running task', |
|
stack: taskError.stack, |
|
msg: taskError, |
|
level: 'error' |
|
}); |
|
// </debug> |
|
if (task.onError) { |
|
rt = task.onError.call(task.scope || task, task, taskError); |
|
} |
|
} catch (ignore) { } |
|
} |
|
task.taskRunTime = now; |
|
if (rt === false || task.taskRunCount === task.repeat) { |
|
me.stop(task); |
|
remove = true; |
|
} else { |
|
remove = task.stopped; // in case stop was called by run |
|
expires = now + task.interval; |
|
} |
|
} |
|
|
|
if (!remove && task.duration && task.duration <= (now - task.taskStartTime)) { |
|
me.stop(task); |
|
remove = true; |
|
} |
|
} |
|
|
|
if (remove) { |
|
task.pending = false; // allow the task to be added to me.tasks again |
|
|
|
// once we detect that a task needs to be removed, we copy the tasks that |
|
// will carry forward into newTasks... this way we avoid O(N*N) to remove |
|
// each task from the tasks array (and ripple the array down) and also the |
|
// potentially wasted effort of making a new tasks[] even if all tasks are |
|
// going into the next wave. |
|
if (!newTasks) { |
|
newTasks = tasks.slice(0, i); |
|
// we don't set me.tasks here because callbacks can also start tasks, |
|
// which get added to me.tasks... so we will visit them in this loop |
|
// and account for their expirations in nextExpires... |
|
} |
|
} else { |
|
if (newTasks) { |
|
newTasks.push(task); // we've cloned the tasks[], so keep this one... |
|
} |
|
|
|
if (nextExpires > expires) { |
|
nextExpires = expires; // track the nearest expiration time |
|
} |
|
} |
|
} |
|
|
|
if (newTasks) { |
|
// only now can we copy the newTasks to me.tasks since no user callbacks can |
|
// take place |
|
me.tasks = newTasks; |
|
} |
|
|
|
me.firing = false; // we're done, so allow startTimer afterwards |
|
|
|
if (me.tasks.length) { |
|
// we create a new Date here because all the callbacks could have taken a long |
|
// time... we want to base the next timeout on the current time (after the |
|
// callback storm): |
|
me.startTimer(nextExpires - now, Ext.Date.now()); |
|
} |
|
|
|
// After a tick |
|
if (fireIdleEvent !== false && globalEvents.hasListeners.idle) { |
|
globalEvents.fireEvent('idle'); |
|
} |
|
}, |
|
|
|
// private |
|
startTimer: function (timeout, now) { |
|
var me = this, |
|
expires = now + timeout, |
|
timerId = me.timerId; |
|
|
|
// Check to see if this request is enough in advance of the current timer. If so, |
|
// we reschedule the timer based on this new expiration. |
|
if (timerId && me.nextExpires - expires > me.interval) { |
|
clearTimeout(timerId); |
|
timerId = null; |
|
} |
|
|
|
if (!timerId) { |
|
if (timeout < me.interval) { |
|
timeout = me.interval; |
|
} |
|
|
|
me.timerId = Ext.defer(me.timerFn, timeout); |
|
me.nextExpires = expires; |
|
} |
|
} |
|
}, |
|
function () { |
|
var me = this, |
|
proto = me.prototype; |
|
|
|
/** |
|
* Destroys this instance, stopping all tasks that are currently running. |
|
* @method destroy |
|
*/ |
|
proto.destroy = proto.stopAll; |
|
|
|
/** |
|
* Instances of this class are created by {@link Ext.util.TaskRunner#newTask} method. |
|
* |
|
* For details on config properties, see {@link Ext.util.TaskRunner#start}. |
|
* @class Ext.util.TaskRunner.Task |
|
*/ |
|
me.Task = new Ext.Class({ |
|
isTask: true, |
|
|
|
/** |
|
* This flag is set to `true` by {@link #stop}. |
|
* @private |
|
*/ |
|
stopped: true, // this avoids the odd combination of !stopped && !pending |
|
|
|
fireOnStart: false, |
|
|
|
constructor: function (config) { |
|
Ext.apply(this, config); |
|
}, |
|
|
|
/** |
|
* Restarts this task, clearing it duration, expiration and run count. |
|
* @param {Number} [interval] Optionally reset this task's interval. |
|
*/ |
|
restart: function (interval) { |
|
if (interval !== undefined) { |
|
this.interval = interval; |
|
} |
|
|
|
this.manager.start(this); |
|
}, |
|
|
|
/** |
|
* Starts this task if it is not already started. |
|
* @param {Number} [interval] Optionally reset this task's interval. |
|
*/ |
|
start: function (interval) { |
|
if (this.stopped) { |
|
this.restart(interval); |
|
} |
|
}, |
|
|
|
/** |
|
* Stops this task. |
|
*/ |
|
stop: function () { |
|
this.manager.stop(this); |
|
} |
|
}); |
|
|
|
proto = me.Task.prototype; |
|
|
|
/** |
|
* Destroys this instance, stopping this task's execution. |
|
* @method destroy |
|
*/ |
|
proto.destroy = proto.stop; |
|
}); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|