/**
* TaskScheduler helps manage tasks and ensures they are executed in
* sequential order. When a task of a certain type is enqueued, all pending
* tasks of the same type in the queue are removed. This avoids redundant
* queries and improves user perceived performance.
*
* @module modules/base/task_scheduler
*/
define(function(require) {
'use strict';
var Defer = require('modules/defer');
var Module = require('modules/base/module');
var Observable = require('modules/mvvm/observable');
/**
* @class TaskScheduler
* @returns {TaskScheduler}
*/
var TaskScheduler = Module.create(function TaskScheduler() {
this.super(Observable).call(this);
this.isLocked = false;
this._tasks = [];
}).extend(Observable);
Observable.defineObservableProperty(TaskScheduler.prototype, 'isLocked', {
value: false
});
const TASK_TYPE = {
GENERAL: 0
};
/**
* We will keep some constants here as a static variable
*
* @access public
* @memberOf TaskScheduler
*/
Object.defineProperty(TaskScheduler, 'TASK_TYPE', {
enumerable: true,
configurable: false,
get: function() {
return TASK_TYPE;
}
});
/**
* Change the internal state to make sure we are locked.
*
* @access private
* @memberOf TaskScheduler
*/
TaskScheduler.prototype._lock = function() {
this.isLocked = true;
};
/**
* Change the internal state to unlocked and execute next task.
*
* @access private
* @memberOf TaskScheduler
*/
TaskScheduler.prototype._unlock = function() {
this.isLocked = false;
this._executeNextTask();
};
/**
* We will remove any redundant same-type tasks and keep the remaining ones
* including the others which are not cancelable (general use task).
*
* @access private
* @memberOf TaskScheduler
* @param {String} type
* @return {Array} array of tasks
*/
TaskScheduler.prototype._removeRedundantTasks = function(type) {
return this._tasks.filter((task) => {
if (!task.cancelable) {
return true;
} else {
return task.type !== type;
}
});
};
/**
* Execute the next task when unlocked.
*
* @access private
* @returns {Promise}
* @memberOf TaskScheduler
*/
TaskScheduler.prototype._executeNextTask = function() {
if (this.isLocked) {
return;
}
var nextTask = this._tasks.shift();
if (nextTask) {
this._lock();
return nextTask.func().then((result) => {
nextTask.defer.resolve(result);
this._unlock();
}, () => {
nextTask.defer.reject();
this._unlock();
});
}
};
/**
* This is the only entry point for caller, and we will enqueue all tasks
* one by one and execute them in order.
*
* @access public
* @memberOf TaskScheduler
* @param {Object} task
* @param {String} task.type
* @param {Function} task.func - make sure the func would return a promise
* @param {Boolean} task.cancelable
* @return {Promise}
*/
TaskScheduler.prototype.enqueue = function(task) {
var defer = Defer();
// if there is no func, let's directly resolve this task
if (!task.func) {
return Promise.resolve();
}
if (!task.type) {
// copy the task and make sure we won't change the previous one
task = Object.assign({}, task);
task.type = TaskScheduler.TASK_TYPE.GENERAL;
}
task.defer = defer;
this._tasks = this._removeRedundantTasks(task.type);
this._tasks.push(task);
this._executeNextTask();
return defer.promise;
};
return TaskScheduler;
});