Problem
-
In yesterday’s recipe we saw how to create a function to inspect function calls. For example this code here …
const inspect = fn => function (...args) { console.log(fn.name + ' called with args: ' + args.join(', ')); return fn.apply(this, args); }; function createPerson(firstName, lastName) { return { firstName: firstName, lastName: lastName } } const loggingCreatePerson = inspect(createPerson) loggingCreatePerson('John', 'Doe');
-
… produced this output:
createPerson called with args: John, Doe
-
Today we are gonna improve this recipe and create a generic function
before()
to create functions that execute some code before the original function is called:... const log = (fn, ...args) => console.log( fn.name + ' called with args: ' + args.join(', ') ); const inspect = before(log); const loggingCreatePerson = inspect(createPerson); loggingCreatePerson('John', 'Doe');
Ingredients
- arrow functions
- closures
- partial application
Directions
-
Given: the
inspect()
function from yesterday’s recipe.const inspect = fn => function (...args) { console.log(fn.name + ' called with args: ' + args.join(', ')); return fn.apply(this, args); };
We will now change this function step by step to a generic function.
-
Change the name of the function to
before()
and change it in a way that it takes a parameterinspect
and returns a function (basically you only need to change the partconst inspect = fn
toconst before = inspect => fn
) …const before = inspect => fn => function (...args) { console.log(fn.name + ' called with args: ' + args.join(', ')); return fn.apply(this, args); };
-
… and rename the parameter from
inspect
todecorator
. We will call this function decorator function and this will be the generic way of decorating any function.const before = decorator => fn => function (...args) { console.log(fn.name + ' called with args: ' + args.join(', ')); return fn.apply(this, args); };
-
Inside the returned function now call the decorator function passing the function to decorate and the arguments:
const before = decorator => fn => function (...args) { decorator(fn, args); return fn.apply(this, args); };
-
Voilá, now we have a generic function
before()
that accepts a decorator function and returns another function that accepts the function to decorate.For example this code here …
const before = decorator => fn => function (...args) { decorator(fn, args); return fn.apply(this, args); }; const log = (fn, ...args) => console.log( fn.name + ' called with args: ' + args.join(', ') ); const inspect = before(log); function createPerson(firstName, lastName) { return { firstName: firstName, lastName: lastName } } const loggingCreatePerson = inspect(createPerson); loggingCreatePerson('John', 'Doe');
… produces the known output:
createPerson called with args: John, Doe
Notes
-
Instead of creating a function
inspect()
you can do it like this as well:const loggingCreatePerson = before(log)(createPerson);
-
And with the help of
before()
you now can easily create different decorator functions:const before = decorator => fn => function (...args) { decorator(fn, args); return fn.apply(this, args); }; const log = (fn, ...args) => console.log( fn.name + ' called with args: ' + args.join(', ') ); const logTime = (fn, ...args) => console.log(fn.name + ' called at ' + new Date()); const inspect = before(log); const inspectTime = before(logTime); function createPerson(firstName, lastName) { return { firstName: firstName, lastName: lastName } } const loggingCreatePerson = inspect(createPerson); loggingCreatePerson('John', 'Doe'); // createPerson called with args: John, Doe const loggingTimeCreatePerson = inspectTime(createPerson); loggingTimeCreatePerson('John', 'Doe'); // createPerson called at Wed Jul 20 2016 20:00:00 GMT+0200 (CEST)
Alternative recipes
- Use class decorators, which will be probably be part of a later version of ECMAScript.