Reputation: 653
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
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
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
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
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
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