PaulK
PaulK

Reputation: 653

Call function from string in nodejs

What is the equivalent to window[my_func_name] in nodejs? I'm reading a string from stdin and if it's a function name I want to execute it. I thought global[my_func_name] might work but unfortunately it doesn't.

Upvotes: 20

Views: 35421

Answers (5)

Christos Sirmakezis
Christos Sirmakezis

Reputation: 96

This does not answer the PO question directly, still it's a way to call a function based on it's string name manually.

I wanted to achieve something like this

where I knew the arguments beforehand

It is for a pre-configured script.

npm run $function_name

so I decided to go this way.

Let's say you have implemented some functions named : fun1, fun2, fun3

Store them inside an object:

const RUNNERS = { fun1, fun2, fun3 };

Create one file (or many) containing the args (in my case is 1 but works for more), like this:

args.ts

export const args = {
 arg1: 3,
 arg2: "string",
 arg3: { propA: "hey"},
};

Create a driver function like this:

/**
 * Called from terminal like this
 *
 * npm run {fun1|fun2|fun3}
 * @param runner The function to run - Set internally
*/
const driver = async runner => await runner(args)

In package.json create a script:

"fun1": "RUNNER=fun1 npm start"

And call driver function like this

driver(RUNNERS[process.env.RUNNER])

This sets the name of the function internally, but you can pass it yourself to the npm script as an environment variable as well. You can also programmatically over-write the arguments you need based on their keys (names) as well, or through env

The only caveat here is that you must destructure the correct args for each function yourself plus its a bit messy for hard typing eg:

fun1 may only use args.arg1, fun 2 may only use args.arg2 and args.arg3 etc..

This will do the trick

const fun1 = async({ arg1 }: { [key: string]: any }) => { ... }

const fun2 = async({ arg2, arg3 }: { [key: string]: any }) => { ... }

You can call it from terminal like this

npm run fun1

Hope this will help someone with some modifications

Upvotes: 0

user14745438
user14745438

Reputation: 1

Came across this with the same question myself, and I'm a newbie too, so take this with a grain of salt - from other posts here on SO mentioning the global object, I also discovered that module seems to be the local object in Node.js, on which exports are defined.

So if you have in a module:

exports.myFunction = myFunction;

And at some point, after you've declared the export (you can write your exports at the top of the file/script, even before you've declared myFunction()), you call:

module.exports['myFunction']()

That will essentially call myFunction()

Again, newbie, so take with a grain of salt.

Upvotes: 0

Rupinder Singh
Rupinder Singh

Reputation: 243

Write your functions in a separate file and export them and use with name reference of that to call them, Like

//    functions.js
var funcOne = function(){
                   console.log('function ONE called')
              }
module.exports={
    //  name_exported : internal_name
    funcOne : funcOne
}

Use function defined in functions.js in index.js :

//    index.js
var methods = require('./functions.js')   // path to functions.js
methods['funcOne']()

OUTPUT :

> node index.js
> function ONE called

Upvotes: 15

taggartJ
taggartJ

Reputation: 287

var theTests = require('./tests'); 
//@TODO CaseID should be caseId
var CaseID = 5678;
// use dynamic functions
var functionName = 'theTests.TEST_' + CaseID;
var functionString = String('TEST_' + CaseID);

var check = eval( 'typeof ' + functionName ); // bad
if ( check == 'function' ) {
    // run the function
    // testResult = eval( functionName ); nope
    testResult = theTests[functionString](); //yep :)
    console.log(testResult);
}
else {
    console.log( 'No test functions for ' + CaseID );
}

in this example in tests.js will be looking for...

TEST_5678: function(){
     return some thing;
},

Upvotes: 0

Mulan
Mulan

Reputation: 135247

It works just fine

global.foo = function foo () {
  console.log("foo was called");
}

process.stdin.on("data", function(input) {

  // don't forget to call .trim() to remove the \n
  var fn = input.toString().trim();

  // function exists
  if (fn in global && typeof global[fn] === "function") {
    global[fn]();
  }

  // function does not exist
  else {
    console.log("could not find " + fn + " function");
  }
});

process.stdin.resume();

Output

foo
foo was called
bar
could not find bar function

Whether or not this is a good idea... well, that's an entirely different discussion.


EDIT — ≈18 months later... Yeah, it's a horrible idea to call a global function like that.

In saying that, here's one way you could approach the problem in a much better way. Below we're going to build a little REPL (read-eval-print loop). In order to understand it better, I'll break it down into a couple parts.

First, we want to make sure our REPL waits for the user to press enter before we try running their command. To do that, we'll create a transform stream that waits for a "\n" character before sending a line down the pipe

The code below is written using ES6. If you're having trouble finding a compatible environment to run the code, I suggest you check out babel.

// line-unitizer.js
import {Transform} from 'stream';

class LineUnitizer extends Transform {
  constructor(delimiter="\n") {
    super();
    this.buffer = "";
    this.delimiter = delimiter;
  }
  _transform(chunk, enc, done) {
    this.buffer += chunk.toString();
    var lines = this.buffer.split(this.delimiter);
    this.buffer = lines.pop();
    lines.forEach(line => this.push(line));
    done();
  }
}

export default LineUnitizer;

Don't get too hung up on LineUnitizer if you're new to stream processing and it doesn't quite make sense. This kind of stream transform is extremely common. The general idea is this: once we pipe process.stdin into a receiving stream, process.stdin will be emitting data every time the user presses a key. However, our Repl (implemented below) can't act on a command until the user has finished typing the command. LineUnitizer is the part that waits for the user to press enter (which inserts a "\n" into the stream) and then signals to _transform that the command is ready to be sent to repl for processing!

Let's look at Repl now

// repl.js
import {Writable} from 'stream';

class Repl extends Writable {
  _parse(line) {
    var [cmd, ...args] = line.split(/\s+/);
    return {cmd, args};
  }
  _write(line, enc, done) {
    var {cmd, args} = this._parse(line.toString());
    this.emit(cmd, args);
    done();
  }
}

export default Repl;

Well hey, that was easy! What does it do tho? Each time repl receives a line, it emits an event with some args. Here's a visual way to see how a command is parsed

The user enters       emit event      args
-------------------------------------------------------------
add 1 2 3             "add"           ["1", "2", "3"]
hens chocobo cucco    "hens"          ["chocobo", "cucco"]
yay                   "yay"           []

Ok, now let's wire everything together to see it work

// start.js
import LineUnitizer from './line-unitizer';
import Repl         from './repl';

process.stdin
  .pipe(new LineUnitizer())
  .pipe(
    (new Repl())
      .on("add", function(args) {
        var sum = args.map(Number).reduce((a,b) => a+b, 0);
        console.log("add result: %d", sum);
      })
      .on("shout", function(args) {
        var allcaps = args.map(s => s.toUpperCase()).join(" ");
        console.log(allcaps);
      })
      .on("exit", function(args) {
        console.log("kthxbai!");
        process.exit();
      }));

Run it

$ node start.js

Output

Lines prefixed with > are user input. The > will not actually be visible in your terminal.

> add 1 2 3
add result: 6
> shout I can see it in your face!
I CAN SEE IT IN YOUR FACE!
> exit
kthxbai!

If you think that's awesome, we're not even done yet. The benefits of writing our program this way is that we can act on the commands regardless of how they arrive at our program.

Consider this commands.txt file

add 100 200
shout streams are the bee's knees
exit

Now run it like this

$ cat commands.txt | node start.js

Output

300
STREAMS ARE THE BEE'S KNEES
kthxbai!

Ok, so that's pretty fricken great. Now consider that the commands could come from anywhere. Could be a database event, something across the network, a CRON job, etc. Because everything is nicely separated, we could easily adapt this program to accept a variety of inputs with ease.

Upvotes: 22

Related Questions