tweetdeckhipchattelegramhangoutsslackgmailskypefacebook-workplaceoutlookemailmicrosoft-teamsdiscordmessengercustom-servicesmacoslinuxwindowsinboxwhatsappicloud
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.
475 lines
15 KiB
475 lines
15 KiB
9 years ago
|
// @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;
|
||
|
});
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|