Stackedit/res/classes/AsyncTask.js

203 lines
5.9 KiB
JavaScript
Raw Normal View History

2013-06-16 10:47:45 +00:00
define([
"underscore",
"utils",
2013-07-30 08:46:36 +00:00
"eventMgr",
2013-06-16 10:47:45 +00:00
"config",
"libs/stacktrace",
2013-08-04 00:53:46 +00:00
], function(_, utils, eventMgr) {
2013-06-16 10:47:45 +00:00
var taskQueue = [];
function AsyncTask() {
this.finished = false;
this.timeout = ASYNC_TASK_DEFAULT_TIMEOUT;
this.retryCounter = 0;
this.runCallbacks = [];
this.successCallbacks = [];
this.errorCallbacks = [];
}
/**
* onRun callbacks are called by chain(). These callbacks have to call
* chain() themselves to chain with next onRun callback or error() to
* throw an exception or retry() to restart the task.
*/
AsyncTask.prototype.onRun = function(callback) {
this.runCallbacks.push(callback);
};
/**
* onSuccess callbacks are called when every onRun callbacks have
* succeed.
*/
AsyncTask.prototype.onSuccess = function(callback) {
this.successCallbacks.push(callback);
};
/**
* onError callbacks are called when error() is called in a onRun
* callback.
*/
AsyncTask.prototype.onError = function(callback) {
this.errorCallbacks.push(callback);
};
/**
* chain() calls the next onRun callback or the onSuccess callbacks when
* finished. The optional callback parameter can be used to pass an
* onRun callback during execution, bypassing the onRun queue.
*/
2013-08-22 19:10:57 +00:00
var currentTaskStartTime = 0;
2013-06-16 10:47:45 +00:00
AsyncTask.prototype.chain = function(callback) {
2013-08-22 19:10:57 +00:00
currentTaskStartTime = utils.currentTime;
2013-06-16 18:29:54 +00:00
utils.logStackTrace();
2013-06-16 10:47:45 +00:00
if(this.finished === true) {
return;
}
// If first execution
if(this.queue === undefined) {
// Create a copy of the onRun callbacks
this.queue = this.runCallbacks.slice();
}
// If a callback is passed as a parameter
if(callback !== undefined) {
callback();
return;
}
// If all callbacks have been run
if(this.queue.length === 0) {
// Run the onSuccess callbacks
runSafe(this, this.successCallbacks);
return;
}
// Run the next callback
var runCallback = this.queue.shift();
runCallback();
};
/**
* error() calls the onError callbacks passing the error parameter and
* ends the task by throwing an exception.
*/
AsyncTask.prototype.error = function(error) {
2013-06-16 18:29:54 +00:00
utils.logStackTrace();
2013-06-16 10:47:45 +00:00
if(this.finished === true) {
return;
}
2013-06-16 18:29:54 +00:00
error = error || new Error("Unknown error");
2013-06-16 10:47:45 +00:00
if(error.message) {
2013-07-30 08:46:36 +00:00
eventMgr.onError(error);
2013-06-16 10:47:45 +00:00
}
runSafe(this, this.errorCallbacks, error);
// Exit the current call stack
throw error;
};
/**
* retry() can be called in an onRun callback to restart the task
*/
2013-08-22 19:10:57 +00:00
var currentTaskRunning = false;
2013-06-16 10:47:45 +00:00
AsyncTask.prototype.retry = function(error, maxRetryCounter) {
if(this.finished === true) {
return;
}
maxRetryCounter = maxRetryCounter || 5;
this.queue = undefined;
if(this.retryCounter >= maxRetryCounter) {
this.error(error);
return;
}
// Implement an exponential backoff
var delay = Math.pow(2, this.retryCounter++) * 1000;
currentTaskStartTime = utils.currentTime + delay;
currentTaskRunning = false;
runTask();
};
/**
* enqueue() has to be called to add the task in the running task queue
*/
AsyncTask.prototype.enqueue = function() {
taskQueue.push(this);
runTask();
};
var asyncRunning = false;
var currentTask = undefined;
2013-08-04 00:53:46 +00:00
// Determine if user is real by listening to his activity
var isUserReal = false;
eventMgr.addListener("onUserActive", function() {
isUserReal = true;
});
2013-06-16 10:47:45 +00:00
// Run the next task in the queue if any and no other running
function runTask() {
2013-07-21 22:18:35 +00:00
// Wait for user first interaction before running first task
2013-08-04 00:53:46 +00:00
if(isUserReal === false) {
2013-07-21 22:18:35 +00:00
return
}
2013-06-16 10:47:45 +00:00
// Use defer to avoid stack overflow
2013-08-24 23:42:35 +00:00
//_.defer(function() {
2013-06-16 10:47:45 +00:00
// If there is a task currently running
if(currentTaskRunning === true) {
// If the current task takes too long
if(currentTaskStartTime + currentTask.timeout < utils.currentTime) {
2013-06-16 18:29:54 +00:00
currentTask.error(new Error("A timeout occurred."));
2013-06-16 10:47:45 +00:00
}
return;
}
if(currentTask === undefined) {
// If no task in the queue
if(taskQueue.length === 0) {
return;
}
// Dequeue an enqueued task
currentTask = taskQueue.shift();
currentTaskStartTime = utils.currentTime;
if(asyncRunning === false) {
asyncRunning = true;
2013-07-30 08:46:36 +00:00
eventMgr.onAsyncRunning(true);
2013-06-16 10:47:45 +00:00
}
}
// Run the task
if(currentTaskStartTime <= utils.currentTime) {
currentTaskRunning = true;
currentTask.chain();
}
2013-08-24 23:42:35 +00:00
//});
2013-06-16 10:47:45 +00:00
}
2013-06-16 18:29:54 +00:00
2013-08-04 00:53:46 +00:00
// Call runTask periodically
eventMgr.addListener("onPeriodicRun", runTask);
2013-06-16 10:47:45 +00:00
function runSafe(task, callbacks, param) {
try {
_.each(callbacks, function(callback) {
callback(param);
});
}
finally {
task.finished = true;
if(currentTask === task) {
currentTask = undefined;
currentTaskRunning = false;
}
if(taskQueue.length === 0) {
asyncRunning = false;
2013-07-30 08:46:36 +00:00
eventMgr.onAsyncRunning(false);
2013-06-16 10:47:45 +00:00
}
else {
runTask();
}
}
}
return AsyncTask;
});