194 lines
6.3 KiB
JavaScript
194 lines
6.3 KiB
JavaScript
/**
|
|
* Used to run asynchronous tasks sequentially (ajax mainly). An asynchronous
|
|
* task is composed of different callback types: onRun, onSuccess, onError
|
|
*/
|
|
define([
|
|
"underscore",
|
|
"core",
|
|
"utils",
|
|
"extension-manager"
|
|
], function(_, core, utils, extensionMgr) {
|
|
|
|
var asyncRunner = {};
|
|
|
|
var taskQueue = [];
|
|
var asyncRunning = false;
|
|
var currentTask = undefined;
|
|
var currentTaskRunning = false;
|
|
var currentTaskStartTime = 0;
|
|
|
|
asyncRunner.createTask = function() {
|
|
var task = {};
|
|
task.finished = false;
|
|
task.timeout = ASYNC_TASK_DEFAULT_TIMEOUT;
|
|
task.retryCounter = 0;
|
|
/**
|
|
* 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.
|
|
*/
|
|
// Run callbacks
|
|
task.runCallbacks = [];
|
|
task.onRun = function(callback) {
|
|
task.runCallbacks.push(callback);
|
|
};
|
|
/**
|
|
* onSuccess callbacks are called when every onRun callbacks have
|
|
* succeed.
|
|
*/
|
|
task.successCallbacks = [];
|
|
task.onSuccess = function(callback) {
|
|
task.successCallbacks.push(callback);
|
|
};
|
|
/**
|
|
* onError callbacks are called when error() is called in a onRun
|
|
* callback.
|
|
*/
|
|
task.errorCallbacks = [];
|
|
task.onError = function(callback) {
|
|
task.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.
|
|
*/
|
|
task.chain = function(callback) {
|
|
if(task.finished === true) {
|
|
return;
|
|
}
|
|
// If first execution
|
|
if(task.queue === undefined) {
|
|
// Create a copy of the onRun callbacks
|
|
task.queue = task.runCallbacks.slice();
|
|
}
|
|
// If a callback is passed as a parameter
|
|
if(callback !== undefined) {
|
|
callback();
|
|
return;
|
|
}
|
|
// If all callbacks have been run
|
|
if(task.queue.length === 0) {
|
|
// Run the onSuccess callbacks
|
|
runSafe(task, task.successCallbacks);
|
|
return;
|
|
}
|
|
// Run the next callback
|
|
var runCallback = task.queue.shift();
|
|
runCallback();
|
|
};
|
|
/**
|
|
* error() calls the onError callbacks passing the error parameter and
|
|
* ends the task by throwing an exception.
|
|
*/
|
|
task.error = function(error) {
|
|
if(task.finished === true) {
|
|
return;
|
|
}
|
|
error = error || new Error("Unknown error");
|
|
if(error.message) {
|
|
extensionMgr.onError(error);
|
|
}
|
|
runSafe(task, task.errorCallbacks, error);
|
|
// Exit the current call stack
|
|
throw error;
|
|
};
|
|
/**
|
|
* retry() can be called in an onRun callback to restart the task
|
|
*/
|
|
task.retry = function(error, maxRetryCounter) {
|
|
if(task.finished === true) {
|
|
return;
|
|
}
|
|
maxRetryCounter = maxRetryCounter || 5;
|
|
task.queue = undefined;
|
|
if(task.retryCounter >= maxRetryCounter) {
|
|
task.error(error);
|
|
return;
|
|
}
|
|
// Implement an exponential backoff
|
|
var delay = Math.pow(2, task.retryCounter++) * 1000;
|
|
currentTaskStartTime = utils.currentTime + delay;
|
|
currentTaskRunning = false;
|
|
asyncRunner.runTask();
|
|
};
|
|
return task;
|
|
};
|
|
|
|
// Run the next task in the queue if any and no other running
|
|
asyncRunner.runTask = function() {
|
|
// Use defer to avoid stack overflow
|
|
_.defer(function() {
|
|
|
|
// If there is a task currently running
|
|
if(currentTaskRunning === true) {
|
|
// If the current task takes too long
|
|
if(currentTaskStartTime + currentTask.timeout < utils.currentTime) {
|
|
currentTask.error(new Error("A timeout occurred."));
|
|
}
|
|
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;
|
|
extensionMgr.onAsyncRunning(true);
|
|
}
|
|
}
|
|
|
|
// Run the task
|
|
if(currentTaskStartTime <= utils.currentTime) {
|
|
currentTaskRunning = true;
|
|
currentTask.chain();
|
|
}
|
|
});
|
|
};
|
|
// Run runTask function periodically
|
|
core.addPeriodicCallback(asyncRunner.runTask);
|
|
|
|
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;
|
|
extensionMgr.onAsyncRunning(false);
|
|
}
|
|
else {
|
|
asyncRunner.runTask();
|
|
}
|
|
}
|
|
}
|
|
|
|
// Add a task to the queue
|
|
asyncRunner.addTask = function(task) {
|
|
taskQueue.push(task);
|
|
asyncRunner.runTask();
|
|
};
|
|
|
|
// Change current task timeout
|
|
asyncRunner.setCurrentTaskTimeout = function(timeout) {
|
|
if(currentTask !== undefined) {
|
|
currentTask.timeout = timeout;
|
|
}
|
|
};
|
|
|
|
return asyncRunner;
|
|
});
|