Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

JS: Call certain function before calling each of other functions in file

I have a question about better code reuse in JS.

E.g I have file functions.js with next functions:

export const a = ()=>{...}
export const b = ()=>{...}
export const c = ()=>{...}
....
const foo = ()=>{...}

I would like to call foo() function before each function in this class called. The simple solution would be:

export const a = ()=>{foo() ...}
export const b = ()=>{foo() ...}
export const c = ()=>{foo() ...}

But what if i have more then 3 function? How to optimise foo() function calls and call it each time before each file functions are called?

like image 805
AlexBerd Avatar asked Jul 30 '19 07:07

AlexBerd


4 Answers

You could use a Proxy where the target is one object with all of your functions, and then you could use get trap to catch every function call from that proxy. Then instead of exporting each function you could just export that proxy object.

const a = (paramA, paramB) => console.log('a', paramA, paramB)
const b = () => console.log('b')
const c = () => console.log('c')

const foo = () => console.log('Foo')

const functions = new Proxy({a, b, c}, {
  get() {
    foo();
    return Reflect.get(...arguments);
  }
})

functions.a('foo', 'bar')
functions.c()

If you want to catch called function arguments and pass those arguments to foo you could return a proxy function from the get method where you have access to provided arguments and then inside you call foo and called function.

const a = (paramA, paramB) => console.log('a', paramA, paramB)
const b = () => console.log('b')
const c = () => console.log('c')

const foo = (...params) => {
  console.log('Foo', params)
}

const functions = new Proxy({a, b, c}, {
  get(target, prop) {
    return function(...params) {
      foo(...params);
      target[prop].apply(null, params)
    }
  }
})

functions.a('foo', 'bar')
functions.c('baz')
like image 124
Nenad Vracar Avatar answered Nov 14 '22 22:11

Nenad Vracar


You can use classes, extend Foo and run a super() on the constructor.

class Foo {
   constructor(){
     //do something
   }
}

export class A extends Foo {
   constructor(){
      super();
   }
}

export class B extends Foo {
   constructor(){
      super();
   }
}

But all in all, it's almost just the same as calling Foo() over and over again. You still have to explicitly write the super and extends for each function.

But what if i have more then 3 function? How to optimise foo() function calls and call it each time before each file functions are called?

There is just no issues calling Foo() for each function. It's not like you are dynamically creating your function and have to find a way to decorate it somehow.

But hey you can add all your functions into an iterable and decorate foo() on them before you export.

const foo = ()=>{ console.log('foo') }
const a = ()=>{console.log('a');}
const b = ()=>{console.log('b')}
const c = ()=>{console.log('c')}

//make sure to create the collection which we will iterate later on.
//you can also implicitly add the functions here instead
const collection = {a, b, c}; 


//this runs at the bottom of the script
const obj = {};
for(let col in collection){
    obj[col] = function(f){
       return (args) => {
          foo(args); f(args);
       }
    }(collection[col]);
}

//then export
export obj;
like image 34
Abana Clara Avatar answered Nov 14 '22 23:11

Abana Clara


You can wrap the functions you have in another function which will call foo before invoking the original function.

Here in the snippet below, I have put all the functions in an array and using Array.prototype.map I have created another function wrapper which invokes the foo function and returns the wrapped functions in a new array: :

const a = ()=>{
    console.log("a")
}
const b = ()=>{
    console.log("b")
}
const c = ()=>{
    console.log("c")
}
const foo = ()=>{
    console.log("foo");
}

function wrap(funArr, foo) {
     return funArr.map((fun) => {
        return function() {
            foo();
            fun();
        }            
    });
}
for( let f of wrap([a, b, c], foo)){
    f();
}
like image 22
Fullstack Guy Avatar answered Nov 14 '22 23:11

Fullstack Guy


You can use higher order functions to derive other and thus re-use the code. Simple case:

//your functions as they appear currently but you won't export them that way.
const _a = ()=>{...}
const _b = ()=>{...}
const _c = ()=>{...}

const foo = ()=>{...}

//this will take two functions and generate a new one that calls the second before the first
const myDecorator = (mainFn, preCallFn) => () => { preCallFn(); return mainFn(); }

//now decorated and export the functions
export const a = myDecorator(_a, foo);
export const b = myDecorator(_b, foo);
export const c = myDecorator(_c, foo);

This way you don't have to change what the a (or _a in this case) function does at all and you can change what your exported function does without touching either foo or _a - say, you can later decide that a = myDecorator(_a, bar) but leave everything else in place. Or maybe you change to a different decorator that calls the foo function after _a. In all cases, you only have to change one thing and it's not the logic of your functions. They don't know nor care about each other.

A more generalised decorator might look like this

//take one main function and any number of things to call before it
const decorator = (mainFn, ...preCallFns) => {
  //generate a new function with any number of arguments
  return function(...args) {
    //call each function that you want to call before
    preCallFns.forEach(fn => fn());

    //call the decorated function forwarding any arguments and the current context
    return mainFn.apply(this, args)
  }
}

This is more robust and will allow more flexibilty - you can accept functions with or without arguments and it would still work. An even better idea is to use currying:

//take the functions first and the function to decorate second
const decorator = (...preCallFns) => mainFn => {
  return function(...args) {
    preCallFns.forEach(fn => fn());

    return mainFn.apply(this, args)
  }
}

Now you can do reusable decorators, for example:

const firstDecorator = decorator(foo);
const secondDecorator = decorator(bar);
const thirdDecorator = decorator(foo, bar);

export const a = firstDecorator(_a);
export const b = firstDecorator(_b);
export const c = firstDecorator(_c);
export const d = secondDecorator(_d);
export const e = secondDecorator(_e);
export const f = thirdDecorator(_f);

The advantage here is that you can alter the decorators without changing any logic. For example, you can change firstDecorator to instead be decorator(foo, bar, baz) and you don't need to alter anything. You know that a, b, and c still need the same common logic to be run, so they still use firstDecorator but you don't change how they are made - you've just changed the decorator. However, since the decorator itself is merely a list of functions to call before the main function, then you haven't actually changed any logic in it, either. Just how it's derived - by using a different list of functions to call. Your code is decoupled, and generic, thus the entire difference is a list of arguments you supply. Everything would still work and would still be easy to understand and test.


If you're using Lodash, then you can instead use _.wrap which is even more flexible, as it allows you to define any wrapping function:

const _a = ()=>{...}
const _b = ()=>{...}
const _c = ()=>{...}

const foo = ()=>{ ... }

const wrapper = (fn, ...args) => { foo(); return fn(...args); }

export const a = _.wrap(_a, wrapper);
export const b = _.wrap(_b, wrapper);
export const b = _.wrap(_b, wrapper);

If you prefer a curried version similar to what my last custom example did, then can use the FP version of Lodash and then the invocation will look like this

const fooDecorator = _.wrap(wrapper);

export const a = fooDecorator(_a);

Or if you already have the normal version of Lodash, then you can can just make your own FP version of _.wrap:

const fpWrap = _.curry(_.rearg(_.ary(_.wrap, 2), [1, 0]));

const fooDecorator = fpWrap(wrapper);
like image 26
VLAZ Avatar answered Nov 15 '22 00:11

VLAZ