2

Let me show you an example of what I want to accomplish:

Say I want to call a functions sub function's sub function's sub function (and so on, lets say for 50+ sub functions), like:

foo(arg).bar(other).foobar(int, str).execute(str)

and imagine that there are 50+ sub functions, so it would be pretty impractical to type out each sub-call.

SO: How do I write a function to call the sub-function of the sub-function etc... (based on the array length)?, based on an array like this (for example):

[["foo",["asdf"]],["bar",["other"]],["foobar",[123,"hi"]],["execute",["today"]]]

To be clear, I'm NOT simply trying to call a each function in the array individually with the corresponding parameters, I could do that easily with:

arr.forEach(x=>functionDictionary(x[0])(...x[1])

I want to simply get this:

foo(arg).bar(other).foobar(int, str).execute(str)

from this:

[["foo",["asdf"]],["bar",["other"]],["foobar",[123,"hi"]],["execute",["today"]]]
Kamil Kiełczewski
  • 85,173
  • 29
  • 368
  • 345

2 Answers2

5

Use reduce to iterate over the array and call each function, and pass along the return value to the next iteration as the accumulator:

// using just a single object to make the indentation much more readable:
const obj = {
  foo(arg) {
    console.log('foo called with ' + arg);
    return this;
  },
  bar(arg2) {
    console.log('bar called with ' + arg2);
    return this;
  },
  foobar(argA, argB) {
    console.log('foobar called with ' + argA + ' ' + argB);
    return this;
  },
  execute(arg5) {
    console.log('execute called with ' + arg5);
    return this;
  }
};

const arr = [
  ["foo", ["asdf"]],
  ["bar", ["other"]],
  ["foobar", [123, "hi"]],
  ["execute", ["today"]]
];
arr.reduce((a, [key, args]) => a[key](...args), obj);

Note that here I'm passing in obj as the initial value, so that the first ["foo"] can access obj.foo, rather than using eval to reference a variable in the current scope named foo.

CertainPerformance
  • 356,069
  • 52
  • 309
  • 320
  • Wow that's really cool, I never even knew about the reduce method! And also I didn't know you can declare functions like that in an object literal... Would it be possible to do the same if each of the functions didn't return a value? – B''H Bi'ezras -- Boruch Hashem Feb 06 '19 at 07:22
  • 1
    If the functions didn't return anything, then your desired output call format of `foo(arg).bar(other).foobar(int, str).execute(str)` doesn't make sense. If all the functions were in an object or something though, like in my answer, then you could simply access the appropriate property of the object on each iteration. – CertainPerformance Feb 06 '19 at 07:25
  • I admit you are right, I was mistaken, however the other answer doesn't need to use reduce (nothing against reduce it's just an extra step) – B''H Bi'ezras -- Boruch Hashem Feb 06 '19 at 07:52
  • I chose `reduce` because it's completely self-contained - there's no need for an extra outside variable. The other answer's outside variable is much more of an extra step – CertainPerformance Feb 06 '19 at 07:55
  • I guess but what if one of the objects happens to not return a variable / not be callable, is there a way to check for that? – B''H Bi'ezras -- Boruch Hashem Feb 06 '19 at 07:57
  • Since you want to imitate your desired `foo(arg).bar(other).foobar(int, str).execute(str)`, then it will result in an error, just like that code does. If you want the chain to fail silently then you can use the same sort of conditional operator logic, `=> a ? a[key](...args) : 0` – CertainPerformance Feb 06 '19 at 08:01
1

Try

arr.forEach( x => r=r[x[0]](...x[1]) );

where arr contains your array with function names-arguments, r contains object with function (and result at the end).

const o = {
  fun1(arg) { console.log('fun1 ' + arg); return this;},
  fun2(arg1,arg2) { console.log('fun2 ' + arg1 +'-'+ arg2); return this; },
  fun3(arg) { console.log('fun3 ' + arg); return this;},  
};

const arr = [
  ["fun1", ["abc"]],  
  ["fun2", [123, "def"]],
  ["fun3", ["ghi"]]
];


let r=o; // result
arr.forEach( x => r=r[x[0]](...x[1]) );

In case if you want to break call chain when function not return next object then use this

arr.forEach( x => r= r ? r[x[0]](...x[1]) : 0 );

const o = {
  fun1(arg) { console.log('fun1 ' + arg); return this;},
  fun2(arg1,arg2) { console.log('fun2 ' + arg1 +'-'+ arg2); },
  fun3(arg) { console.log('fun3 ' + arg); return this;},  
};

const arr = [
  ["fun1", ["abc"]],  
  ["fun2", [123, "def"]],
  ["fun3", ["ghi"]]
];


let r=o; // result
arr.forEach( x => r= r ? r[x[0]](...x[1]) : 0 );
Kamil Kiełczewski
  • 85,173
  • 29
  • 368
  • 345