20

I have an array of functions, as in:

funcArray = [func1, func2, func3];

When in a given function, I want to execute the next function in the array. How do I do this? Here is my basic skeleton:

function func1() {
  // I get current function caller
  var currentFunc = func1.caller;

  // I want to execute the next function. Happens to be func2 in the example.

}

I cannot use indexOf function, as one would for an array of strings or numbers. NOTE: This question appears to be similar to this and the one it refers to. However, it is a different question.

I want to alter the sequence of processing by merely modifying the array. That's the goal. A possibly more efficient approach would be appreciated.

Clarification: Based upon some of the comments: funcArray is global.

The goal is to implement middleware for a Node.js HTTP module in as simple and efficient a manner as possible without using any third-party modules.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Sunny
  • 9,245
  • 10
  • 49
  • 79
  • You could have the array of functions and then use `Array.forEach` to loop through them in order and call each one? – frobinsonj Aug 13 '18 at 12:36
  • 3
    I guess, you can't do this. `func1` knows nothing about `funcArray` after you call it. Function is just a reference and it may be stored in an array, in object or wherever else. Caller function should worry about the order of execution in this case. – Yeldar Kurmangaliyev Aug 13 '18 at 12:36
  • 18
    this is a classic exemple of the X Y problem : http://xyproblem.info/ what is your goal at first place and what general problem are you trying to solve? – mpm Aug 13 '18 at 12:38
  • Although (I guess) the first answer correctly guesses the "X", changing the question is not the best way as it invalidates the second answer; while *not* changing the question would make it more obscure. – user202729 Aug 13 '18 at 13:47
  • Referring to answers as "first" and "second" is a classic example of what? **X Y answer**? – Sunny Aug 13 '18 at 13:54
  • 2
    What makes you think that you couldn't use `funcArray.indexOf(func1)`? It works with strings, numbers or objects like functions. – Bergi Aug 13 '18 at 13:55
  • Do not use `var currentFunc = func1.caller;`, the `caller` property is deprecated and doesn't work in strict mode. The current function is `func1`, just refer to it directly. – Bergi Aug 13 '18 at 13:55
  • @Bergi You are right! I was trying to console log it and thereby getting the caller function name wrong! It works! But then is it efficient? What exactly is equality of functions when it searches for a function in an array of functions? Just the _name_ property? – Sunny Aug 13 '18 at 14:05
  • 2
    @Sam Functions are objects and compared by identity. Having multiple different functions with the same name won't make them equal. – Bergi Aug 13 '18 at 14:09
  • @Bergi How expensive or inefficient is checking for function equality? Would it be more sensible to have an array of strings (function names) instead, have functions defined as func1 = function (..), get the current function, get its name as string, then get the next element in the array and then execute it as global[name](). Also, if you can post your comment as an answer, I will accept it. Though Mr. Crowder's answer is VERY informative. – Sunny Aug 13 '18 at 14:13
  • 2
    @Sam Object equality is pretty cheap, it's more or less just a pointer comparison. But still, to suggest an efficient solution we need to know more about your actual problem. Why would the `func1` need to know about `func2` at all? Shouldn't they be executed separately, by the caller? Do you just want to try calling the array functions in a loop until the first returns true or something? – Bergi Aug 13 '18 at 14:16
  • @Bergi. You have understood my problem perfectly. When I get a http request, I go through the (middleware) functions in a series until I serve the request at which time I can stop further processing and skip the remaining functions. However, inside the functions also, there are several conditions to go to the next function. I think you have put me on the right track. I possibly don't need to know the next function name inside the function... – Sunny Aug 13 '18 at 14:24
  • 2
    @Sam Yes, use a return value (or callback call, or promise) to signal to your caller that you either handled the request (and it should stop) or that you didn't (and that it should continue with calling the next function). – Bergi Aug 13 '18 at 14:26
  • 1
    @Bergi I will go ahead and accept Mr. Crowder's answer as you have explained that it is the correct solution. Thanks. – Sunny Aug 13 '18 at 14:28

7 Answers7

40

Unless func1 closes over funcArray, you cannot have it reach out and find func2 and execute it, nor should you. Even if func1 does close over funcArray, it would be poor separation of concerns for func1 to reach out and find itself in funcArray and then execute func2.

Instead, have other code that's in charge of running the functions.

If they're synchronous

If the functions complete their work synchronously, then it's simply:

funcArray.forEach(fn => fn());

or

for (const fn of funcArray) {
    fn();
}

or if the result of one function should be passed to the next, you can use reduce:

const finalResult = funcArray.reduce((previousResult, fn) => fn(previousResult), undefined);

...where undefined is the value to pass to func1.

If they're asynchronous

If they don't do their work synchronously, you'll need to provide them a way to notify their caller that they've completed their work. Promises are a good, standard way to do that, but you could use simple callbacks instead.

If you make them return promises, for instance, you can use the old promise reduce trick:

funcArray.reduce((p, fn) => {
    return p.then(() => {
        fn();
    });
}, Promise.resolve());

or if the result of one function should be passed to the next:

funcArray.reduce((p, fn) => {
    return p.then(fn);
}, Promise.resolve());

You can provide an argument to Promise.resolve to set the value to pass to func1 (without one, it'll receive undefined).

T.J. Crowder
  • 1,031,962
  • 187
  • 1,923
  • 1,875
  • 2
    i think it's worth mentioning `Promise.all`. It's not the answer to this question, but for those just looking to run all the functions in any order and do something when they're done, it's good. – Nic Aug 13 '18 at 17:19
  • You could convert to async function - funcArray.reduce(async (p, fn) => { await p; fn(); }, Promise.resolve()); – user992731 Jun 01 '19 at 02:05
  • @user992731 - In environments that support it, yes, although until a recent change to the spec that would have added an extra async "tick" to the process. – T.J. Crowder Jun 01 '19 at 06:28
  • @T.J.Crowder - Good Point – user992731 Jun 01 '19 at 15:11
4

You can bind to the function the index where it is in the array so you can use this index to get and call the next function:

var funcArray = [func1, func2];
var boundFuncArray = funcArray.map((f, i) => f.bind(null, i));

boundFuncArray[0](); 

function func1(nextFunctionIndex) {
    console.log('func1 called');
    // Execute next function:
    var nextFunc = boundFuncArray[nextFunctionIndex + 1];
    nextFunc && nextFunc();
}

function func2(nextFunctionIndex) {
    console.log('func2 called');
    // Execute next function:
    var nextFunc = boundFuncArray[nextFunctionIndex + 1];
    nextFunc && nextFunc();
}

As T.J Crowder stated in the comment below, you can also bind the next function to the current one:

var funcArray = [func1, func2];
var boundFuncArray= funcArray.map((f, i, arr) => f.bind(null, arr[i + 1]));

boundFuncArray[0](); 

function func1(nextFunc) {
    console.log('func1 called');
    // Execute next function:
    nextFunc && nextFunc();
}

function func2(nextFunc ) {
    console.log('func2 called');
    // Execute next function:
    nextFunc && nextFunc();
}
Faly
  • 13,291
  • 2
  • 19
  • 37
3

You can get the current function's name with arguments.callee.name, loop through the array of functions, and call the next function:

funcArray = [func1, func2, func3];

// Only func1() and func2() will be documented since the others have repeating code

function func1() {
    // show the current function name
    console.log(arguments.callee.name);

    // loop the array of functions
    for(var i = 0; i < funcArray.length; ++i)
    {
        // when the current array item is our current function name and
        // another function exists after this then call it and break
        if(funcArray[i] === arguments.callee && funcArray[i+1])
        {
            funcArray[i+1]();
            break;
        }
    }
}

function func2() {
    console.log(arguments.callee.name);
    
    // some logic which switches our next function to be func4()
    funcArray[2] = func4;
    
    for(var i = 0; i < funcArray.length; ++i)
    {
        if(funcArray[i] === arguments.callee && funcArray[i+1])
        {
            funcArray[i+1]();
            break;
        }
    }
}

function func3() {
    console.log(arguments.callee.name);
    for(var i = 0; i < funcArray.length; ++i)
    {
        if(funcArray[i] === arguments.callee && funcArray[i+1])
        {
            funcArray[i+1]();
            break;
        }
    }
}

function func4() {
    console.log(arguments.callee.name);
    for(var i = 0; i < funcArray.length; ++i)
    {
        if(funcArray[i] === arguments.callee && funcArray[i+1])
        {
            funcArray[i+1]();
            break;
        }
    }
}

// call the first function
funcArray[0]();

Output:

func1
func2
func4
MonkeyZeus
  • 20,375
  • 4
  • 36
  • 77
  • Havent even checked if this works, but either way, if you are using the name just for comparison, you can compare functions directly. Which will work much more reliably in minified/postprocessed environments. – This company is turning evil. Aug 13 '18 at 13:32
  • @Kroltan Thanks for the suggestion, looks like it works in pure JS so I updated my answer. Not sure why my answer is still getting downvoted though; it works :/ – MonkeyZeus Aug 13 '18 at 13:37
  • 1
    @MonkeyZeus I upvoted your answer and is the simple solution I tried but failed to get function equality for a mistake. It most certainly works. I had probably not worded by question properly and the accepted answer does provide another approach too for the question asked earlier. – Sunny Aug 14 '18 at 07:50
  • I appreciate it. Your question is certainly a bit vague but a fun challenge nevertheless so I just wanted to provide a solution which others didn't. I think that the answer you accepted is the right way to go in general because it allows your code to be [DRY](https://en.wikipedia.org/wiki/Don%27t_repeat_yourself). – MonkeyZeus Aug 14 '18 at 12:05
1

I have solved it this way:

// Adding next options to array
function addNext(array) {
   array.last = 1
   Object.defineProperty(array, 'next', {get: 
      function() {
         if(this.last < this.length) {
            this.last++
            return this[this.last-1]
         } else {
            this.last = 1
            return () => {}
         }
      }
   });
}

// The functions for array (has to be function and not arrow function)
function first(param) {
   console.log('first',param)
   return this.next(param)
}
function second(param) {
   console.log('second',param)
   return this.next(param)
}
function third(param) {
   console.log('third',param)
   return this.next(param)
}

// The array
let fns = [first,second,third]

// Adding next option to array
addNext(fns)

// Run first function from array 
fns[0]('test')
Alex Sorkin
  • 101
  • 3
0

I dont know if your functions require certain parameters but this is the first thing that came to my mind.

var functArray = [
  function() {
    console.log("function1 executed");
  },
   function() {
    console.log("function2 executed");
  },
    function() {
    console.log("function3 executed");
  },
    function() {
    console.log("function4 executed");
  }];
  
  functArray.forEach(function(x){
    x();
  });
0

The accepted answer and other comments did help me, but the way I implemented it is as follows:

//The functions are defined as variables. 
//They do not get hoisted, so must be defined first.
func1 = function (arg1, arg2) {
  //Code to do whatever...
  ...
  //Execute the next function. 
  //The name of the function is returned by executing nextFunc()
  global[nextFunc()](arg1, arg2, arg3);

}
func2 = function (arg1)  { //Note different type of args
  ...
}

//Note that this is an array of strings representing function names.
funcArray = ["func1", "func2", "func3",...]

//Start the execution...
func1(arg1, arg2);

function nextFunc() {
  var currentFuncName = nextFunc.caller.name;  
  var index = funcArray.indexOf(currentFuncName);
  if (index < funcArray.length)
    return funcArray[index+1];
}

The sequence of functions to be executed is easily managed through the array funcArray. The number or type of arguments is not fixed for each function. Additionally, the functions control if they should stop the chain or continue with the next function.

It is very simple to understand requiring basic Javascript skills. No overheads of using Promises.

"global" gets replaced by "window" for browser. This is a Node.js implementation. The use of function names in the array will, however, break if you minify the JS code. As I am going to use it on the server, I do not expect to minify it.

Sunny
  • 9,245
  • 10
  • 49
  • 79
0

You can do it in this way with promise.all if your functions to be executed in parallel.

let toBeExecutedList = [];
toBeExecutedList.push(() => this.addTwoNumber(2, 3));
toBeExecutedList.push(()=>this.square(2));

And Then wherever you want to use them, do it like this:

const resultArr = await Promise.all([
      toBeExecutedList.map(func => func()),
    ]);
pride
  • 85
  • 1
  • 11