Last active
February 12, 2024 00:35
-
-
Save unscriptable/5920818 to your computer and use it in GitHub Desktop.
How AOP simplified some code when writing a prototype module loader. Promises would make this code even cleaner and saner. FWIW, a Promises/A+ implementation is the second thing that gets loaded by this code! If promises were native to Javascript, I'd definitely be using them instead of callbacks.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// The following two functions are composed from simpler functions using AOP. | |
// getShim(callback) is the entry point. The composed functions still | |
// require lots of work to test, but since they're composed of simpler, | |
// testable functions, we can be more confident in our test coverage | |
// and test validity. | |
/** | |
* Gets the shim and calls back. Uses before advice to modify the getShim | |
* function to instruct any further calls to just queue callbacks. | |
* @function | |
* @param {Function} cb | |
*/ | |
var getShim = before( | |
function (cb) { | |
// fetch the shim and then save it | |
fetchShim(saveShimImpl); | |
}, | |
function (cb) { | |
// rewrite getShim | |
getShim = waitForShim; | |
// register the first callback as a waiter | |
waitForShim(cb); | |
} | |
); | |
/** | |
* Saves the shim implementation. Uses after advice to modify the getShim | |
* function to stop queueing callbacks and calls them immediately, instead. | |
* All callbacks are called at this point. | |
* @function | |
*/ | |
var saveShimImpl = after( | |
saveShimProto, | |
function () { | |
// rewrite getShim | |
getShim = callShimNow; | |
// call all of the waiting callbacks | |
callShimWaiters(); | |
} | |
); | |
// The following functions are very simple and single-purposed. | |
// These are incredibly easy to test via stubbing / spies, rather | |
// than sophisticated mocking. | |
function fetchShim (callback) { | |
fetch('lib/Loader', callback); | |
} | |
function waitForShim (callback) { | |
shim.waiters.push(callback); | |
} | |
function callShimWaiters () { | |
var waiter; | |
while (waiter = shim.waiters.unshift()) waiter(); | |
} | |
function saveShimProto (impl) { | |
// save the shim's prototype to get at the methods. | |
shim.impl = impl.prototype; | |
} | |
function callShimNow (cb) { cb(shim.impl); } | |
/***** AOP functions *****/ | |
// I wrote some simple, brute-force advising functions since | |
// I could not rely on a module system in this code, but | |
// you should use a more elegant AOP solution, if possible. | |
// meld.js is my favorite: https://github.com/cujojs/meld/ | |
function before (func, advice) { | |
return function () { | |
advice.apply(null, arguments); | |
return func.apply(this, arguments); | |
} | |
} | |
function after (func, advice) { | |
return function () { | |
var result = func.apply(this, arguments); | |
advice(result); | |
return result; | |
} | |
} | |
// This is not important to the AOP, but is here for | |
// completeness. | |
var shim = { | |
impl: null, | |
waiters: [] | |
}; | |
function fetch () { /* ... */ } |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// This is the original set of functions. | |
// They're doing many things at once and require sophisticated mocking | |
// to test them. | |
// Note: most of the complexity in these functions is due to the side-effects. | |
// Specifically, the `getShim` variable is reassigned at specific times. | |
// In the AOP version, the side-effects are moved to functions that are | |
// composed into the main functions via AOP. | |
var getShim = function () { | |
// rewrite getShim | |
getShim = waitForShim; | |
// register the first callback as a waiter | |
waitForShim(cb); | |
// fetch the shim | |
fetch('lib/Loader', onGotLoader); | |
}; | |
function onGotLoader (Loader) { | |
// save shim | |
shim.impl = Loader.prototype; | |
// rewrite getShim | |
getShim = callShimNow; | |
// notify waiters | |
callShimWaiters(); | |
} | |
// These functions are the same. They are easy to test. | |
function waitForShim (callback) { | |
shim.waiters.push(callback); | |
} | |
function callShimWaiters () { | |
var waiter; | |
while (waiter = shimWaiters.unshift()) waiter(); | |
} | |
function callShimNow (cb) { cb(shim.impl); } | |
// This is not important to the AOP, but is here for | |
// completeness. | |
var shim = { | |
impl: null, | |
waiters: [] | |
}; | |
function fetch () { /* ... */ } |
Simple AOP:
var origMethod = someObject.method;
someObject.method = function () {
// do something here
var result = origMethod.apply(this, arguments);
// do something else here
return result;
};
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
i guess it'd be easy enough to write the functions like this:
However, the AOP approach is more declarative and may work in more situations where you may not be in control of all of the functions and/or methods.