Neues Node.js-Buch
Alle Artikel

Create a generic function for inspecting function calls

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

  1. 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.

  2. Change the name of the function to before() and change it in a way that it takes a parameter inspect and returns a function (basically you only need to change the part const inspect = fn to const before = inspect => fn) …

    const before = inspect => fn => function (...args) {
      console.log(fn.name + ' called with args: ' + args.join(', '));
      return fn.apply(this, args);
    };
  3. … and rename the parameter from inspect to decorator. 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);
    };
  4. 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);
    };
  5. 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.