Purpose: learn how to wrap the interfaces and make wrapper flexible enough to
wrap unknown interface with just one assumption that callbacks should be last
argument of asynchronous functions. As an example, we have setTimeout wrapped
and as a result we will wrap a whole interface of the file system fs.
framework.js- small piece of the framework, just to demonstrate wrapperapplication.js- small piece of the application for wrapper demonstration
From the command line, type: node application then node framework, compare
output, read code in framework.js and application.js, try to understand
how wrapper works for setTimeout.
- Learn how
setTimeoutis wrapped in example:framework.js. Now we will try to wrap module fs. We can iterate all of its functions by following code:for (const key in fs) {...}and replace its function with wrapped ones. We need a closure function and it should be universal to wrap any function in fs interface. The purpose of this example wrapper is to log all calls to the file system in a file indicating the time, a function name, its arguments, and if the function has also callback, it is necessary to intercept it too and wrap this callback, logging callback calls.
This task can be divided into a few steps. - Remove
setTimeoutexample fromapplication.jsand replace it with the following code:
const fileName = './README.md';
console.log('Application going to read ' + fileName);
fs.readFile(fileName, (err, src) => {
console.log('File ' + fileName + ' size ' + src.length);
});This example contains a call to fs.readFile. In next steps we will change the
behavior of the code changing framework.js and wrapping all fs functions.
Let's run node framework and make sure that it reads the file and displays its
length.
3. Next step is preparing function cloneInterface(interfaceName) for cloning
all keys from given library into new interface. So we can pass its result
(cloned fs) to sandbox instead of fs. Cloning function example:
const cloneInterface = (anInterface) => {
const clone = {};
for (const key in anInterface) {
clone[key] = anInterface[key];
}
return clone;
};- After that we can add wrapper
wrapFunction(fnName, fn)with 2 arguments: name of the function and link to a function itself. It returnswrapper— closure function. Closurewrapperis a newly created function with the help of functional inheritance, so it will seefnName,fnin its context. Thus we can pass all arguments from wrapper into original function as you see in example:
const wrapFunction = (fnName, fn) => (...args) => {
console.log('Call: ' + fnName);
console.dir(args);
return fn(...args);
}- Now should detect do we have
callbackargument as a last argument of function call, we can do that bytypeof()comparing tofunction. If we havecallback, we need to wrap it too, so pass ours function instead ofcallbackand then call originalcallbackfrom this function. - Then we can call other functions of
fsinterface fromapplication.jsand try to run wrapped code. - Add timers in
application.jsand make multiple calls working with files. So we will model a real application random file system access. Then we can collect some statistics fromframework.jsand print it every 30 seconds. For example, you can collect following parameters:
- number of function calls,
- number of callbacks,
- average function completion speed,
- average return rate of callbacks,
- total amount of data read from the disk,
- total amount of recorded data,
- average read and write speed, etc.
Save your results to github, we will need it in next labs, for example we can transfer overriden wrapped calls (fs operations) to another process and another server. In such a way we can create distributed application.