Skip to content

Instantly share code, notes, and snippets.

@designfrontier
Last active May 13, 2016 17:21
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save designfrontier/5c8624c63d719f5b3da4a77dd0802db5 to your computer and use it in GitHub Desktop.
Save designfrontier/5c8624c63d719f5b3da4a77dd0802db5 to your computer and use it in GitHub Desktop.
const ajax = require('utils/ajax');
const Promise = require('Promise');
/*
* The queue itself...
*
* @param {String} retryConut The number of times that it should retry a request
* before it gives up and rejects it.
*
* @return {object}
*/
const Queue = function (retryCount = 3) {
const saveQueue = {
items: {},
order: []
};
let isRunning = false;
/*
* This little fellow returns the interval for the next attempt
* Basically he lets us increment the interval time as the number of
* failures increases.
*
* @param {Number} attempt The number of the current attempt
*
* @return {Number} The amount of time in ms to wait for the next attempt
*/
const getAttemptInterval = (attempt) => {
if (attempt <= 1) {
return 0;
} else {
return Math.pow(attempt, 2) * 25;
}
};
/*
* Cleans up the items in teh queue so they don't stick around after they are
* done being processed.
* @return {undefined}
*/
const cleanUpQueue = () => {
saveQueue.items = Object.keys(saveQueue.items).reduce((result, key) => {
if (saveQueue.order.indexOf(key) !== -1) {
result[key] = saveQueue.items[key];
}
return result;
}, {});
};
/*
* This is the main engine of the queue it is a recursive function that
* handles chewing through the changes that need to be saved to the server.
*
* It calls itself through setTimeout to prevent it from locking down the
* event loop indefinitely while it processes things.
*
*
* @returns {undefined}
*/
const processQueue = () => {
const itemId = saveQueue.order.shift();
const item = itemId ? saveQueue.items[itemId] : {};
if (typeof itemId === 'undefined') {
isRunning = false;
return;
}
isRunning = true;
item.attempts++;
ajax({
url: item.url,
type: item.verb,
data: item.data,
dataType: 'json'
}).then((result) => {
item.resolve(result);
setTimeout(processQueue, getAttemptInterval(0));
cleanUpQueue();
}, (err) => {
if (item.attempts >= retryCount) {
item.reject(err);
} else {
saveQueue.order.unshift(itemId);
}
setTimeout(processQueue, getAttemptInterval(item.attempts));
});
};
/*
* This little guy starts up the processing of the queue if it is not running
*
* @return undefined
*/
const startQueue = () => {
if (!isRunning) {
setTimeout(processQueue, getAttemptInterval(0));
}
};
/*
* This function adds items to the queue for processing later on
*
* @param {String} url this will also be used as the ID so should be unique
*
* @param {Object} data The data that needs to be saved in the format that
* server expects it in.
*
* @param {String} verb The verb that the server πis expecting. Defaults to PUT
*
* @return {Promise} The promise that will be rejected or resolved when the
* queue is finished doing its thing. Rejected if errors or too many
* retries. Resolved if everything ends up working out in the end.
*/
const add = (url, data, verb = 'PUT') => {
startQueue();
if (typeof saveQueue.items[url] !== 'undefined') {
// this guy is already in the queue
// It might make sense to make this behavior more interesting in the
// future, but for now it is good enough™
return saveQueue.items[url].promise;
} else {
saveQueue.items[url] = {
attempts: 0,
data: data,
url: url,
verb: verb,
};
saveQueue.items[url].promise = new Promise((resolve, reject) => {
saveQueue.items[url].resolve = resolve;
saveQueue.items[url].reject = reject;
});
saveQueue.order.push(url);
return saveQueue.items[url].promise;
}
};
const getQueue = () => {
return saveQueue;
};
return {
add: add,
start: startQueue,
get: getQueue
}
};
module.exports = Queue;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment