Stackedit/js/async-runner.js

182 lines
4.8 KiB
JavaScript
Raw Normal View History

2013-03-30 11:56:17 +00:00
/**
2013-04-21 00:07:27 +00:00
* Used to run asynchronous tasks sequentially (ajax mainly). An asynchronous
2013-04-20 00:14:20 +00:00
* task is composed of different callback types: onRun, onSuccess, onError
2013-03-30 11:56:17 +00:00
*/
2013-04-21 00:07:27 +00:00
define([ "core", "underscore" ], function(core) {
2013-04-20 00:14:20 +00:00
var asyncRunner = {};
var taskQueue = [];
2013-03-30 11:56:17 +00:00
var currentTask = undefined;
2013-04-01 01:06:52 +00:00
var currentTaskRunning = false;
2013-04-10 18:14:59 +00:00
var currentTaskStartTime = 0;
2013-04-20 00:14:20 +00:00
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
2013-04-21 00:07:27 +00:00
* chain() themselves to chain with next onRun callback or error() to
* throw an exception or retry() to restart the task.
2013-04-20 00:14:20 +00:00
*/
// 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
2013-05-18 13:40:16 +00:00
* finished. The optional callback parameter can be used to pass an onRun
2013-04-20 00:14:20 +00:00
* 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();
};
/**
2013-05-18 13:40:16 +00:00
* error() calls the onError callbacks passing the error parameter and ends
2013-04-21 00:07:27 +00:00
* the task by throwing an exception.
2013-04-20 00:14:20 +00:00
*/
task.error = function(error) {
if (task.finished === true) {
return;
}
2013-04-27 23:16:38 +00:00
error = error || new Error("Unknown error");
if(error.message) {
core.showError(error.message);
}
2013-04-20 00:14:20 +00:00
runSafe(task, task.errorCallbacks, error);
// Exit the current call stack
throw error;
};
/**
* retry() can be called in an onRun callback to restart the task
*/
2013-04-27 23:16:38 +00:00
task.retry = function(error, maxRetryCounter) {
2013-04-20 00:14:20 +00:00
if (task.finished === true) {
return;
}
2013-04-27 23:16:38 +00:00
maxRetryCounter = maxRetryCounter || 5;
2013-04-20 00:14:20 +00:00
task.queue = undefined;
2013-04-27 23:16:38 +00:00
if (task.retryCounter >= maxRetryCounter) {
task.error(error);
2013-04-20 00:14:20 +00:00
return;
}
// Implement an exponential backoff
var delay = Math.pow(2, task.retryCounter++) * 1000;
currentTaskStartTime = core.currentTime + delay;
currentTaskRunning = false;
asyncRunner.runTask();
};
return task;
};
// Run the next task in the queue if any and no other running
2013-05-18 13:40:16 +00:00
asyncRunner.runTask = function() {
// Use defer to avoid stack overflow
_.defer(function() {
2013-04-20 00:14:20 +00:00
2013-05-18 13:40:16 +00:00
// If there is a task currently running
if (currentTaskRunning === true) {
// If the current task takes too long
if (currentTaskStartTime + currentTask.timeout < core.currentTime) {
currentTask.error(new Error("A timeout occurred."));
}
2013-03-30 11:56:17 +00:00
return;
}
2013-04-20 00:14:20 +00:00
2013-05-18 13:40:16 +00:00
if (currentTask === undefined) {
// If no task in the queue
if (taskQueue.length === 0) {
return;
}
2013-04-20 00:14:20 +00:00
2013-05-18 13:40:16 +00:00
// Dequeue an enqueued task
currentTask = taskQueue.shift();
currentTaskStartTime = core.currentTime;
core.showWorkingIndicator(true);
}
2013-04-20 00:14:20 +00:00
2013-05-18 13:40:16 +00:00
// Run the task
if (currentTaskStartTime <= core.currentTime) {
currentTaskRunning = true;
currentTask.chain();
}
});
2013-03-30 11:56:17 +00:00
};
// Run runTask function periodically
core.addPeriodicCallback(asyncRunner.runTask);
2013-04-20 00:14:20 +00:00
function runSafe(task, callbacks, param) {
try {
2013-04-20 00:14:20 +00:00
_.each(callbacks, function(callback) {
callback(param);
});
} finally {
2013-04-20 00:14:20 +00:00
task.finished = true;
if (currentTask === task) {
currentTask = undefined;
currentTaskRunning = false;
}
2013-04-20 00:14:20 +00:00
if (taskQueue.length === 0) {
core.showWorkingIndicator(false);
} else {
asyncRunner.runTask();
}
}
}
2013-04-20 00:14:20 +00:00
// Add a task to the queue
asyncRunner.addTask = function(task) {
taskQueue.push(task);
asyncRunner.runTask();
2013-03-30 11:56:17 +00:00
};
2013-04-20 00:14:20 +00:00
2013-04-16 15:02:24 +00:00
// Change current task timeout
2013-04-20 00:14:20 +00:00
asyncRunner.setCurrentTaskTimeout = function(timeout) {
if (currentTask !== undefined) {
2013-04-16 15:02:24 +00:00
currentTask.timeout = timeout;
}
};
2013-04-20 00:14:20 +00:00
return asyncRunner;
2013-04-02 18:42:47 +00:00
});