Skip to content

Instantly share code, notes, and snippets.

@siddharthvp
Last active July 18, 2020 11:14
Show Gist options
  • Save siddharthvp/8796b9f44a95a3d2fbaf2bb437397706 to your computer and use it in GitHub Desktop.
Save siddharthvp/8796b9f44a95a3d2fbaf2bb437397706 to your computer and use it in GitHub Desktop.
// How to run: copy paste to console in a tab with WP open
Morebits.taskManager = function () {
this.taskDependencyMap = new Map();
this.deferreds = new Map();
this.allDeferreds = []; // Hack: IE doesn't support Map.prototype.values
/**
* Register a task along with its dependencies (tasks which should have finished
* execution before we can begin this one). Each task is a function that must return
* a promise. The function will get the values resolved by the dependency functions
* as arguments.
* @param {function} func - a task
* @param {function[]} deps - its dependencies
*/
this.add = function (func, deps) {
this.taskDependencyMap.set(func, deps);
var deferred = $.Deferred();
this.deferreds.set(func, deferred);
this.allDeferreds.push(deferred);
};
/**
* Run all the tasks. Multiple tasks may be run at once.
*/
this.execute = function () {
var self = this; // proxy for `this` for use inside functions where `this` is something else
this.taskDependencyMap.forEach(function (deps, task) {
var dependencyPromisesArray = deps.map(function (dep) {
return self.deferreds.get(dep);
});
$.when.apply(null, dependencyPromisesArray).then(function () {
console.log('Executing ' + task.name);
task.apply(null, arguments).then(function () {
console.log(task.name + ' is resolved');
self.deferreds.get(task).resolve.apply(null, arguments);
});
});
});
return $.when.apply(null, this.allDeferreds).then(function () { // resolved when eveything is done!
console.log('done; yay!');
});
};
/**
* Check if there are any cyclic dependencies.
* We represent the tasks as the vertices of a directed graph whose edges
* represent dependencies, then perform a depth-first traversal.
* See [[Depth-first search]] for a background on how this works.
*
* XXX: Is this worth it?
* Useful for testing and debugging but shouldn't be required in production code
*/
this.checkCircularDependencies = function () {
var visited = new Set();
var executed = new Set();
var dfs = function (task) {
visited.add(task);
var circularLink;
var deps = this.taskDependencyMap.get(task);
for (var i = 0; i < deps.length; i++) {
if (!visited.has(deps[i])) {
if (circularLink = dfs(deps[i])) {
return circularLink;
}
} else if (!executed.has(deps[i])) {
return deps[i];
}
}
executed.add(task);
}.bind(this);
this.taskDependencyMap.forEach(function (_, task) {
var link;
if (!visited.has(task)) {
if (link = dfs(task)) {
// link.name (which gives the name of the function) won't work in IE 11,
// so you're on your own in finding the circular link
throw new Error('circular dependency detected at ' + link.name);
}
}
});
};
};
var gar = {
tasks: {
getNumber: function getNumber() {
var def = $.Deferred();
setTimeout(function () {
def.resolve(4);
actualTimes.getNumber = new Date().getTime() - starttime;
}, 1500);
return def;
},
createNomPage: function createNomPage() {
var def = $.Deferred();
setTimeout(function () {
def.resolve();
actualTimes.createNomPage = new Date().getTime() - starttime;
}, 2500);
return def;
},
editTalkPage: function editTalkPage(num, randval) {
if (num !== 4) throw new Error('argument mismatch');
if (randval !== 42) throw new Error('argument mismatch');
var def = $.Deferred();
setTimeout(function () {
def.resolve();
actualTimes.editTalkPage = new Date().getTime() - starttime;
}, 1500);
return def;
},
getCreator: function getCreator() {
var def = $.Deferred();
setTimeout(function () {
def.resolve('Example user');
actualTimes.getCreator = new Date().getTime() - starttime;
}, 1000);
return def;
},
notifyAuthor: function notifyAuthor(creator) {
if (creator !== 'Example user') throw new Error('argument mismatch');
var def = $.Deferred();
setTimeout(function () {
def.resolve(42);
actualTimes.notifyAuthor = new Date().getTime() - starttime;
}, 1500);
return def;
},
}
}
/*** Register the functions along with dependencies ***/
var tm = new Morebits.taskManager();
tm.add(gar.tasks.getNumber, []);
tm.add(gar.tasks.notifyAuthor, [gar.tasks.getCreator]);
tm.add(gar.tasks.createNomPage, [gar.tasks.getNumber]);
tm.add(gar.tasks.getCreator, []);
tm.add(gar.tasks.editTalkPage, [gar.tasks.getNumber, gar.tasks.notifyAuthor]);
var actualTimes = {};
// Expected times are calculated as time needed for dependent functions + time needed for the
// function itself.
var expectedTimes = {
getNumber: 1500,
getCreator: 1000,
notifyAuthor: 1500 + 1000,
// 1500 for dependency getNumber, 2500 for createNomPage itself
createNomPage: 1500 + 2500,
// 1500 ms for getNumber, 1500 + 1000 for notifyAuthor
// 1500 ms for editTalkPage itself,
editTalkPage: Math.max(1500, 1500 + 1000) + 1500
};
// all times measured are relative to this time
var starttime = new Date();
// Go!
tm.execute().then(function () {
$.each(expectedTimes, function (funcName, expectedTime) {
var actualTime = actualTimes[funcName];
// allow 50 ms tolerance for actually executing the cdoe
if (Math.abs(actualTime - expectedTime) > 50) {
console.error('time taken for ' + funcName + ' (' + actualTime + ') is not as expected (' + expectedTime + ')');
}
});
console.log('All times as expected! (unless you see any red error above)');
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment