Mapping arbtrary object values to function inputs in Javascript - javascript

I have an object of arbitrary functions and a function that takes a method name and an object or an array with parameters and then selects and calls the function accordingly. The problem is that functions have a variable number of inputs and some of them contain optional fields with default values and I can't find a general way to map inputs of the parameters to inputs of the function.
One way to solve the problem when the arguments come in the form of the array is just simply called the function with the ... operator: func(...args), but that still leaves me with a problem, because I can't use these methods on objects. Is there any way to map object values to function inputs by using object keys?
Abstract example of the situation:
const funcs = {
func1: (arg1, arg2, arg3 = 'something') => .....does something
func2: () => ....does something
func3: (anotherArg1) => ...does something
}
function callFunction(method: string, args: unknown[]| object) {
if (Array.isArray(args)) {
return funcs[method](...args)
}
else (if args instanceof Object) {
//... Here I need to parse the args and call the function in "funcs" object.
}
}

Just spread the second argument of callFunction(method, ...args)
const funcs = {
func1: (arg1, arg2, arg3 = 'something') => {
[arg1, arg2, arg3].forEach((a, i) => console.log(`arg${i+1}:`, JSON.stringify(a)));
}
}
function callFunction(method, ...args) {
return funcs[method](...args)
}
const method = 'func1';
callFunction(method, [1, 2], {foo: 'bar'})

Related

JavaScript: Passing different count of parameters to callback function [duplicate]

Can I call a function with array of arguments in a convenient way in JavaScript?
Example:
var fn = function() {
console.log(arguments);
}
var args = [1,2,3];
fn(args);
I need arguments to be [1,2,3], just like my array.
Since the introduction of ES6, you can sue the spread syntax in the function call:
const args = [1,2,3];
fn(...args);
function fn() {
console.log(arguments);
}
Before ES6, you needed to use apply.
var args = [1,2,3];
fn.apply(null, args);
function fn() {
console.log(arguments);
}
Both will produce the equivalent function call:
fn(1,2,3);
Notice that I used null as the first argument of the apply example, which will set the this keyword to the global object (window) inside fn or undefined under strict mode.
Also, you should know that the arguments object is not an array, it's an array-like object, that contains numeric indexes corresponding to the arguments that were used to call your function, a length property that gives you the number of arguments used.
In ES6, if you want to access a variable number of arguments as an array, you can also use the rest syntax in the function parameter list:
function fn(...args) {
args.forEach(arg => console.log(arg))
}
fn(1,2,3)
Before ES6, if you wanted to make an array from your arguments object, you commonly used the Array.prototype.slice method.
function fn() {
var args = Array.prototype.slice.call(arguments);
console.log(args);
}
fn(1,2,3);
Edit: In response to your comment, yes, you could use the shift method and set its returned value as the context (the this keyword) on your function:
fn.apply(args.shift(), args);
But remember that shift will remove the first element from the original array, and your function will be called without that first argument.
If you still need to call your function with all your other arguments, you can:
fn.apply(args[0], args);
And if you don't want to change the context, you could extract the first argument inside your function:
function fn(firstArg, ...args) {
console.log(args, firstArg);
}
fn(1, 2, 3, 4)
In ES5, that would be a little more verbose.
function fn() {
var args = Array.prototype.slice.call(arguments),
firstArg = args.shift();
console.log(args, firstArg);
}
fn(1, 2, 3, 4);
In ECMAScript 6, you can use spread syntax (...) for that purpose. It's way simpler and easier to understand than Function.prototype.apply().
Code example:
const fn = function() {
console.log(arguments);
}
const args = [1,2,3];
fn(...args);

Why do fat arrow functions do not accept single parameter destruction without parentheses?

I have a question regarding fat arrow functions in JavaScript.
As the title says, fat arrow functions require the actual parentheses when destructing parameters even if only one parameter is being used and thus being destructed and I would like to know if this 'exception' exists on purpose or might it be implemented in ECMAScript in the future?
For example this works as expected:
let func = arg1 => {...}; // Single param
These however don't work as expected:
let func = => {...}; / let func => {...}; // No params
let func = arg1, arg2, ...restArg => {...}; // Multiple params
This way they do work:
let func = () => {...}; // No params
let func = (arg1, arg2, ...restArg) => {...}; // Multiple params
And now lets say we want to map through the entries of an object:
let obj = {
x: 1,
y: 2,
};
let objEntries = Object.entries(obj); // Array containing obj's entries
This works like it should:
objEntries.map(entry => {...}) // entry: ['x', 1], ['y', 2]
In this example I am trying to destruct the entry:
objEntries.map([key, value] => {...}) // Fails even though I am only using 1 param
objEntries.map(([key, value]) => {...}) // Succeeds like expected
So as I mentioned earlier I wonder why destructing inside the parentheses of a fat arrow function requires the actual parentheses even when only using and thus destructing only one parameter? Is that on purpose or might it be implemented in ECMAScript in the future?

Passing arguments to a convenience method on one class that calls method from another

I've got a prototype method like so:
ProjectClient.prototype = {
connect: function() {
console.log('ARGS: ' + Array.prototype.slice.call(arguments)
// this bit takes a data object, a relationship string, and another data object as arguments,
// e.g. client.connect(user1, 'likes', user2):
var options = helpers.build.connection(this, Array.prototype.slice.call(arguments))
request({
uri: options.uri,
method: 'POST',
json: true
}, function(error, response) {
var customResponse = new CustomResponse(response)
options.callback(error, customResponse)
})
}
}
This relies on an instance of ProjectClient being passed to the helpers.build.connection method. I've also got a 'singleton' shared instance of ProjectClient that is in use. For convenience, I'm adding a copy of this connect() method to ProjectEntity like so:
ProjectEntity.prototype = {
connect: function() {
var args = Array.prototype.slice.call(arguments)
args.unshift(this)
return Project.connect(args)
}
}
It's not working properly though -- this gets me a nested array of arguments when doing console.log('ARGS: ' + Array.prototype.slice.call(arguments):
ARGS: [ [ arg1, arg2, arg3 ] ]
Where I'd expect:
ARGS: [ arg1, arg2, arg3 ]
What's a more consistent way of passing arguments to ProjectClient.prototype.connect() so that I can get what I'm expecting? I tried using Project.connect.apply(args) too, but since I'm returning the function (not actually calling it here) Array.prototype.slice.call(arguments) ends up an empty array. If there is no better way of doing this, what's the best workaround?
You probably want to use .apply() like this to pass an arbitrary set of arguments to a function:
ProjectEntity.prototype = {
connect: function() {
var args = Array.prototype.slice.call(arguments);
args.unshift(this);
return Project.connect.apply(Project, args);
}
}
.apply() takes two arguments itself. The first is whatever you want the this argument to be inside the function you are calling. The second is an array of arguments that you want to be passed to the function as individual arguments (not passed as the array itself).
More documentation on .apply() here on MDN.

javascript: always pass the nth argument in a function as fixed value by default

the function takes 3 parameters like
function p(x,y,z){
console.log(arguments);
}
so when we call it like
p(12,21,32)
a fourth argument should pass as say 56
so effectively the call should be p(12,21,32,56)
How to do this?
Condition We cannot change the function definition. I need to partially bind the fourth argument as 56 something like
p=p.bind(this,'','','',56);
or use lodash
and then call p later like
p(12,21,32);
such that 56 should pass by default
You can use _.partialRight() to create a new function that appends arguments to the end of the original function:
function p(a, b, c)
{
alert([].join.call(arguments, ','));
}
p = _.partialRight(p, 56);
p(1,2,3); // 1,2,3,56
<script src="https://raw.githubusercontent.com/lodash/lodash/3.9.3/lodash.js"></script>
To exactly specify the position of the extra argument(s) you can use placeholders:
p = _.partialRight(p, _, _, _, _, _, _, 56); // add 56 as 7th arg
p(1,2,3); // 1,2,3,,,,56
p = (function() {
var old_p = p;
return function(a, b, c) {
return old_p(a, b, c, 56);
};
})();
We remember the old version of p under the name old_p so we can invoke it even after we've redefined p. We do this inside the IIFE so that old_p does not pollute the global scope. Then, we return a function (which is assigned to p) which returns the result of calling old_p with the extra argument.
We can make this more general, to create "bound" functions which add extra arguments to any function call. Below I use ES6 syntax, especially the spread ... operator. However, you can accomplish the same thing by manipulating the arguments object and using apply:
function bind_with_arguments_at_end(f, ...extra_args) {
return function(...args) {
return f(...args, ...extra_args);
}
}
Where the function involved is a method on an object, it makes sense to "pass through" this, so the new function can be called as this.bound_func and things continue to work. Do do this, we can use call:
function bind_with_arguments_at_end(f, ...extra_args) {
return function(...args) {
return f.call(this, ...args, ...extra_args);
^^^^^^^^^^^
}
}
You can create a new function which uses apply to redirect its arguments to the original one, but using Object.assign to overwrite some of them:
function fixArguments(f, args) {
return function() {
return f.apply(this, Object.assign([].slice.call(arguments), args));
};
}
p = fixArguments(p, {3: 56}); // Overwrite the 4th argument with 56
function fixArguments(f, args) {
return function() {
return f.apply(this, Object.assign([].slice.call(arguments), args));
};
}
function p(x,y,z){
console.log(arguments);
}
p = fixArguments(p, {3: 56});
p(12,21,32);
Make a copy of the original and override the name, and call the original with the new arguments.
function p(a,b,c,d) {
console.log(arguments);
}
(function (){
var org_p = p; //copy original
p = function() { //override p
var args = [].slice.call( arguments ); //turn arguments in to array
args.push(56); //add the 4th argument
return org_p.apply( this, args ); //call the original with the updated arguments.
}
}());
p(1,2,3);

Override the arity of a function

I would like to make a generic function wrapper that (for example) prints the called function and its arguments.
Doing so is easy through the arguments quasi-array and simple calls. For example:
function wrap(target, method) {
return function() {
console.log(Array.prototype.slice.call(arguments).join(', '));
return method.apply(target, arguments);
}
}
However, this way of doing of course completely loses the arity of the called function (if you didn't know, one can obtain the arity (number of arguments) of a JavaScript function through its length property).
Is there any way to dynamically create a wrapper function that would copy the arguments of the wrapped function to itself?
I've thought about creating a new Function object, but I don't see any way to statically extract the arguments list, since the arguments property is deprecated.
Here's a solution using Function:
// could also generate arg0, arg1, arg2, ... or use the same name for each arg
var argNames = 'abcdefghijklmnopqrstuvwxyz';
var makeArgs = function(n) { return [].slice.call(argNames, 0, n).join(','); };
function wrap(target, method) {
// We can't have a closure, so we shove all our data in one object
var data = {
method: method,
target: target
}
// Build our function with the generated arg list, using `this.`
// to access "closures"
f = new Function(makeArgs(method.length),
"console.log(Array.prototype.slice.call(arguments).join(', '));" +
"return this.method.apply(this.target, arguments);"
);
// and bind `this` to refer to `data` within the function
return f.bind(data);
}
EDIT:
Here's a more abstract solution, which fixes the closure problem:
function giveArity(f, n) {
return new Function(makeArgs(n),
"return this.apply(null, arguments);"
).bind(f);
}
And a better one, that preserves context when invoked:
function giveArity(f, n) {
return eval('(function('+makeArgs(n)+') { return f.apply(this, arguments); })')
}
Used as:
function wrap(target, method) {
return giveArity(function() {
console.log(Array.prototype.slice.call(arguments).join(', '));
return method.apply(target, arguments);
}, method.length)
}

Categories

Resources